Getting started with Silverlight: Part 2 - Defining the UI layout and Navigation
| Comments- | Posted in
- silverlight
- xaml
- ria
- datagrid
This is part 2 in a series on getting started with Silverlight. To view the index to the series click here. You can download the completed project files for this sample application in C# or Visual Basic.
Understanding layout management in XAML applications is an important aspect in being successful in Silverlight development. For most coming from the web world, this will be one of the bigger challenges if you are not a CSS wizard.
Understanding Layout Options
Silverlight provides a flexible system for laying out UI elements on a surface. There are layout models to support both a dynamic and absolute layout style. There are several layout controls provided but the most commonly used will be:
- Canvas
- StackPanel
- Grid
Let’s take a look at each of these using elements placed within them to see how they work. We’ll use a simple Button element to demonstrate the purpose. We’re using the same project we started with in step 1 and will show these on the Home.xaml page for simplicity (this is throw away code so just pick somewhere to play around for now).
Canvas
The Canvas is the most basic layout and would be used for positioning elements in an absolute manner using coordinates. You position elements in the Canvas using Attached Properties. Attached properties allow the parent container (in this case Canvas) to extend the property of the controls within it (in our example Button). We can position several buttons on a Canvas like this:
1: <Canvas>
2: <Button Canvas.Top="50" Canvas.Left="50" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Canvas.Top="150" Canvas.Left="20" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Canvas.Top="70" Canvas.Left="80" Canvas.ZIndex="99" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </Canvas>
and when rendered it would show something like this:
As you can see, this is the absolute positioning approach to layout. Notice in the code I can also specify an attached property for ZIndex which is why one Button is overlapping the other in this example. This might be helpful in game development or high physics situations where the calculations are very specific. Canvas is useful when things don’t move around much or you are pretty in control of the sizing of the application. Otherwise, Canvas can sometimes be difficult to leverage in favor of things like StackPanel or Grid.
StackPanel
A StackPanel is a layout control which stacks the elements either vertically or horizontally (vertical by default). Using the sample with 3 Buttons and this code:
1: <StackPanel>
2: <Button Margin="10" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </StackPanel>
the layout rendered would be:
or if we change the default Orientation attribute to horizontal using this code (notice only difference is Orientation attribute on StackPanel):
1: <StackPanel Orientation="Horizontal">
2: <Button Margin="10" Content="Button 1" FontSize="18" Width="150" Height="45" />
3: <Button Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
4: <Button Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
5: </StackPanel>
it would render like:
StackPanel provides a simple way to layout elements on top of each other or alongside each other without much challenge of worrying about the positioning of the elements within this container.
Grid
Grid is going to usually be the most flexible layout for most scenarios (notice I said most, not all). It is exactly what it sounds like, a Grid structure using rows and columns. Unlike what web developers may be used to with the <table> element where the content is within the <tr>,<td> tags, the XAML Grid is different. You define the overall structure of the Grid and then use attached properties to tell the elements where to place themselves.
Consider this code (notice I’m explicitly showing grid lines but that isn’t something you’d normally do generally…just showing here for better visualization):
1: <Grid ShowGridLines="True">
2: <Grid.RowDefinitions>
3: <RowDefinition Height="60" />
4: <RowDefinition Height="60" />
5: <RowDefinition Height="60" />
6: </Grid.RowDefinitions>
7:
8: <Grid.ColumnDefinitions>
9: <ColumnDefinition Width="175" />
10: <ColumnDefinition Width="175" />
11: <ColumnDefinition Width="175" />
12: </Grid.ColumnDefinitions>
13:
14: <Button Grid.Column="0" Grid.Row="0" Content="Button 1" FontSize="18" Width="150" Height="45" />
15: <Button Grid.Column="2" Grid.Row="0" Margin="10" Content="Button 2" FontSize="18" Width="150" Height="45" />
16: <Button Grid.Column="1" Grid.Row="2" Margin="10" Content="Button 3" FontSize="18" Width="150" Height="45" />
17: </Grid>
We are defining the Grid to have 3 columns and 3 rows with specific width and heights. Notice then in the Button elements we are positioning them in specific places within the Grid using the attached properties. The resulting rendering looks like this:
Notice how the attached properties on the Button (Grid.Column, Grid.Row) tell the element where to position itself within the container. Working with layouts is where Expression Blend can be extremely helpful. Notice how in Blend you can use the guides to specify the column/row definitions visually and it will generate the XAML for you:
The arrows show you the guides as well as a visual indicator if the row/column are a fixed size (the lock). We’ll use a combination of layout controls in our application. In fact, the default template we chose makes use of all the different types of layout controls already for our basic shell for our application.
Building our Twitter Application
Now we can start building out our application. In general, this is the mockup that we’ll be going after:
Notice that we’ll have a place for the user to enter a search term, and the results will display in some list layout. We’ll also have navigation areas to go to different views such as previous search term history and possibly some statistics.
Luckily the navigation template we chose already gives us some heavy lifting of our overall layout. In MainPage.xaml I’m going to make a few changes. On about line 29 in MainPage.xaml, I’m removing the application logo, and then changing the ApplicationnameTextBlock below it to “Twitter Search Monitor” for my application.
We’ll get back to navigation in a minute, but let’s recreate our Views. In the project structure in the Views folder, create a new Page by right-clicking in Visual Studio and choosing to create a new Silverlight Page and name it Search.xaml:
Now you should have a blank XAML page with a default Grid layout. This is where we’ll create the search screen seen above. We get the header information from our MainPage.xaml because we are using a Frame element to host our navigation.
Silverlight Navigation Framework
At this point let’s take a tangent and understand the Silverlight navigation framework. If you recall we started using the navigation application template. The default template gave us MainPage.xaml and some views of Home, About. The navigation framework is fundamentally made up of 3 parts: UriMapper, Frame and Page.
UriMapper
I like to think of the UriMapper element as a routing engine of sorts. It is not required by any means but I think obfuscates and simplifies your navigation endpoint. Instead of exposing the literal /Views/Home.xaml URI endpoint, you can map that to a simpler “/Home” endpoint that is more readable and doesn’t give away and technical configuration…and can change later to map to something else. You can see the UriMapper as an element of the Frame in MainPage.xaml:
1: <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
2: Source="/Home" Navigated="ContentFrame_Navigated"
3: NavigationFailed="ContentFrame_NavigationFailed">
4: <navigation:Frame.UriMapper>
5: <uriMapper:UriMapper>
6: <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
7: <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
8: </uriMapper:UriMapper>
9: </navigation:Frame.UriMapper>
10: </navigation:Frame>
Now this UriMapper is in XAML like above, but it could also have been a resource and if done that way then you would add it to the Frame element like this (assuming the resource was UriMapperRoutes):
<code/>
We’ll stick to keeping what the template has provided us though at this time.
Frame
If you are an ASP.NET developer, you can think of the Frame element as like the ContentPlaceholder control. The Frame is the area defined as the area that can be navigated. You would specify a default view but then any navigation can occur within that area which we will see later. Looking at the code above, you can see that the default view, the Source attribute of the Frame, is the “/Home” route for our application.
Page
The final fundamental area of navigation is the Page element, which we just created in our last step. These are basically the content areas that are displayed in the Frame element. They are very similar to the basic UserControl element that you might normally add (what MainPage is), but are special in that they can interact with navigation. We will consider our Views in our application as our Page elements.
You can learn more about navigation specifically in this video walk-through:
It’s fairly simple to understand once you dig around in it and can be powerful to use. This framework is what allows deep-linking into Silverlight applications to exist.
Creating the UI layout for our search view
Let’s finish creating the UI in our Search.xaml page we just created. At this point you may be wondering what all the {StaticResource XXXXXXXX} elements are in the XAML. We’ll get to that in the styling/templating section in step 5, so try not to let it bother you for now.
Looking at our mockup, we are going to need a text input area, button and data display grid. Let’s start laying that out using Blend in our Search.xaml page. To do this, from Visual Studio, right-click on the Search.xaml page and choose Edit in Expression Blend:
Since Blend and Visual Studio share the same project structures you will be able to open the file at the same time to do the visual editing of the XAML before we start coding away.
While in blend we’ll layout our Grid to have 2 rows, one for the search input/button and the second for the results view. In the top row we’ll drag a StackPanel in there and add a TextBox and Button into the StackPanel, setting it for Orientation=Horizontal.
The next thing we’ll do is add a DataGrid to show our data. Since DataGrid is not a core control, it is in the SDK libraries and we’ll need to add a reference to it. You can do this in various ways. Blend will actually do this automatically for you. In the toolbox pallette for Blend, click the double arrow and search for DataGrid:
Once you see it, select it and drag it into the second row. This automatically added the reference to the System.Windows.Controls.Data.dll for you and changed the markup in the XAML:
1: <navigation:Page
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6: mc:Ignorable="d"
7: xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
8: xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="TwitterSearchMonitor.Views.Search"
9: d:DesignWidth="640" d:DesignHeight="480"
10: Title="Twitter Search Page">
11: <Grid x:Name="LayoutRoot">
12: <Grid.RowDefinitions>
13: <RowDefinition Height="32"/>
14: <RowDefinition/>
15: </Grid.RowDefinitions>
16: <StackPanel HorizontalAlignment="Left" Margin="0,-32,0,0" VerticalAlignment="Top" Grid.Row="1" Orientation="Horizontal">
17: <TextBox x:Name="SearchTerm" FontSize="14.667" Margin="0,0,10,0" Width="275" TextWrapping="Wrap"/>
18: <Button x:Name="SearchButton" Width="75" Content="SEARCH"/>
19: </StackPanel>
20: <data:DataGrid x:Name="SearchResults" Margin="0,8,0,0" Grid.Row="1"/>
21: </Grid>
22: </navigation:Page>
Notice the xmlns:data in the top. This is how, after adding a reference to the assembly, you add non-core controls to the XAML. Then to use them you’ll see the data:DataGrid element in the Grid. Your XAML now should look a bit like mine and visually it looks like this:
Notice in the XAML that I gave x:Name’s to my TextBox (SearchTerm), Button (SearchButton) and DataGrid (SearchResults). This will help us later easily program against these elements.
Now if you go back to Visual Studio you may see a prompt to reload the project. This is because Blend altered the project file by adding a reference to the DataGrid control. You can go ahead and reload it. This shows how integrated the tools are at the project file level. Now we can start coding again using VS.
Changing our UriMapper to default to Search.xaml
Now that we just created the Search page (which is essentially our home page of the application), let’s make a few changes to the navigation framework. In MainPage.xaml find the Frame and make a few changes to change the default from Home.xaml to our Search and making some other default changes as well. Your Frame XAML should now look like this:
1: <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
2: Source="/Search" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
3: <navigation:Frame.UriMapper>
4: <uriMapper:UriMapper>
5: <uriMapper:UriMapping Uri="" MappedUri="/Views/Search.xaml"/>
6: <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
7: </uriMapper:UriMapper>
8: </navigation:Frame.UriMapper>
9: </navigation:Frame>
Because we don’t need the Home.xaml anymore, go ahead and delete it from the project. Also add a new view called History.xaml and alter the MainPage.xaml LinksBorder area to include a link to that:
1: <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}">
2: <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}">
3:
4: <HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}"
5: NavigateUri="/Search" TargetName="ContentFrame" Content="home"/>
6:
7: <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/>
8:
9: <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}"
10: NavigateUri="/History" TargetName="ContentFrame" Content="history"/>
11:
12: <Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>
13:
14: <HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}"
15: NavigateUri="/About" TargetName="ContentFrame" Content="about"/>
16:
17: </StackPanel>
18: </Border>
Now our rendering if we run it should look like this:
Now that we’ve got the basics of the layout, let’s start adding data in part 3.
Please enjoy some of these other recent posts...
Comments