Follow @PDSAInc August 2011 - Posts - Paul Sheriff's Blog for the Real World

Paul Sheriff's Blog for the Real World

This blog is to share my tips and tricks garnered over 25+ years in the IT industry

Paul's Favorites

August 2011 - Posts

Understanding XAML Screen Layout

When you first start designing XAML screens in either WPF or Silverlight there are several approaches you can take. You can just draw controls on a Grid and let the controls figure out where they are in relation to one another and the Window via the Margin property. You can also a Grid and create a set of Row and Column definitions similar to an HTML table. Another method is to use absolute positioning with a Canvas control. Finally, you could use a series of StackPanel controls with styles. Which one you choose has ramifications to how well your application looks and feels across a wide variety of resolution sizes. This article will explore the impact of each of these.

Setup Global Styles

In order to have a consistent look and feel across many windows in your application it is a good idea to open your App.xaml/Application.xaml file and add some application resources. In this sample you will use buttons, text boxes and text blocks, so create the following resources.

<Application.Resources>
  <Style TargetType="Button">
    <Setter Property="Margin"
            Value="4" />
  </Style>
  <Style TargetType="TextBox">
    <Setter Property="MinWidth"
            Value="200" />
    <Setter Property="Margin"
            Value="4" />
    <Setter Property="HorizontalAlignment"
            Value="Stretch" />
  </Style>
  <Style TargetType="TextBlock">
    <Setter Property="Margin"
            Value="4" />
  </Style>
</Application.Resources>

The above resources tell WPF/Silverlight that when rendering a Button set the Margin property to 4. When rendering a TextBox control to set the MinWidth property to 200, set the Margin to 4 and set HorizontalAlignment to Stretch. When rendering a TextBlock set the Margin property to 4.

Using Grid and Margin

If you create a new WPF/Silverlight Project and just start dragging and dropping controls onto the design surface. Each of the controls is put into the <Grid> and the margin of each control is set to a specific value that controls how far away the top and left of the control is from the <Grid>. Each control also sets a default height and width and sometimes a horizontal and vertical alignment property. All of these properties can render a form that looks like Figure 1 at design time.

Drag controls into the Grid without rows and columns

Figure 1: Drag controls into the Grid without rows and columns

However, when you run the particular control it could look different based on your monitor. On my monitor Figure 2 is what was rendered. The arrows point out where things are chopped off due to the resolution of my monitor.

Controls can render differently at runtime

Figure 2: Controls can render differently at runtime

Let’s look at the XAML that rendered this particular form.

<Grid>
  <TextBlock Height="28"
              Margin="12,18,0,0"
              Name="label1"
              VerticalAlignment="Top"
              HorizontalAlignment="Left"
              Width="120"
              Text="First Name" />
  <TextBox Height="24"
            Margin="0,18,61,0"
            Name="textBox1"
            VerticalAlignment="Top"
            HorizontalAlignment="Right"
            Width="192" />
  <TextBlock Height="28"
              HorizontalAlignment="Left"
              Margin="12,52,0,0"
              Name="label2"
              VerticalAlignment="Top"
              Width="120"
              Text="Last Name" />
  <TextBox Height="24"
            Margin="125,52,61,0"
            Name="textBox2"
            VerticalAlignment="Top" />
  <Button Height="23"
          Margin="125,86,191,0"
          Name="button1"
          VerticalAlignment="Top"
          Content="Close" />
</Grid>

If you were to draw this form, your actual values would vary slightly, but would be similar to the above. The problem with this XAML is you have hard-coded height and width properties and hard-coded margins. If the user tries to resize this form, changes the font size in their OS, changes their screen resolution, then this form will most likely not render correctly. Some of the text could get chopped or clipped depending on what changed. This is not acceptable. Thus, just dragging and dropping controls onto a XAML form is NOT the way to design screens.

Using a Canvas Control

Another method of doing absolute positioning similar to what you did above is to use a Canvas control. The canvas control does not use the Margin property to position your controls however. Instead it will use an attached property to each control to specify the Top and Left coordinates on the Canvas to draw that particular control. The good thing about the Canvas is it will not generate all of the XAML you saw in the previous example and will still generate a form that looks similar as shown in Figure 3:

A Canvas control gives you precise top and left coordinates for each control

Figure 3: A Canvas control gives you precise top and left coordinates for each control

The XAML is much cleaner as shown below:

<Canvas>
  <TextBlock Canvas.Left="5"
              Canvas.Top="10"
              Text="First Name" />
  <TextBox Canvas.Left="110"
            Canvas.Top="10"
            Name="txtFirst" />
  <TextBlock Canvas.Left="5"
              Canvas.Top="50"
              Text="Last Name" />
  <TextBox Canvas.Left="110"
            Canvas.Top="50"
            Name="txtLast" />
  <Button Canvas.Left="110"
          Canvas.Top="90"
          Name="btnClose"
          Content="Close" />
</Canvas>

While this XAML is much cleaner, we still have the problem that the controls will not resize if the form resizes. However since there are no hard-coded width or height properties if you change the FontSize property the control will grow or shrink. The bad part is that if it grows too much it will overlap any of the other controls since they are hard coded to start at a specific coordinate as shown in Figure 4:

Changing the FontSize can cause controls to render behind other controls

Figure 4: Changing the FontSize can cause controls to render behind other controls

This behavior can serve to your advantage however if you want controls to overlap. In fact, the Canvas does add a ZIndex property that you can set on controls to determine which control is in front or in back of other controls.

Using Stack Panels

Another method of creating the same form you have seen is to use one StackPanel to get other StackPanels to stack vertically. Each pair of TextBlock and TextBox combination you want to place into the outer stack panel is placed inside of another StackPanel control with the Orientation property set to Horizontal. Figure 5 shows the same screen rendered using stack panel controls.

Stack Panels with the use of styles can render flexible forms. However, they cannot be resized

Figure 5: Stack Panels with the use of styles can render flexible forms. However, they cannot be resized.

In this WPF Window, you first add a Keyed resource to control the size of the TextBlock controls on this particular screen. This will ensure that each text block will take up the same size.

<Window.Resources>
  <Style TargetType="TextBlock"
         x:Key="LabelStyle">
    <Setter Property="Width"
            Value="100" />
    <Setter Property="Margin"
            Value="4" />
  </Style>
</Window.Resources>

Below is the XAML for this screen.

<StackPanel>
  <StackPanel Orientation="Horizontal">
    <TextBlock Style="{StaticResource LabelStyle}"
                Text="First Name" />
    <TextBox Name="txtFirst" />
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <TextBlock Style="{StaticResource LabelStyle}"
                Text="Last Name" />
    <TextBox Name="txtLast" />
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <TextBlock Style="{StaticResource LabelStyle}" />
    <Button Name="btnClose"
            Content="Close" />
  </StackPanel>
</StackPanel>

Notice the use of the Style property on the TextBlock controls to ensure that the width of each TextBlock is set to the same value. This works for most scenarios, but if you try to change the FontSize you will again get clipping of your controls as shown in Figure 6.

Changes to FontSize will still clip using Stack Panels

Figure 6: Changes to FontSize will still clip.

So using a StackPanel control is also not an ideal situation for an overall page layout. StackPanels are very useful as a portion of a page however.

Using Grid with Rows and Columns

For creating business application forms that are resizable and respond well to changes in font size and screen resolution changes, the best approach is to use a Grid with Row and Column definitions. This is very similar to using a Table in HTML, however, the syntax is very different than HTML. Figure 7 shows the same screen created using rows and columns in a Grid.

Use a Grid with Row and Column Definitions for the most flexibility

Figure 7: Use a Grid with Row and Column Definitions for the most flexibility.

The XAML to create this screen is shown below:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <TextBlock Grid.Column="0"
              Grid.Row="0"
              Name="label1"
              Text="First Name" />
  <TextBox Grid.Column="1"
            Grid.Row="0"
            Name="textBox1" />
  <TextBlock Grid.Column="0"
              Grid.Row="1"
              Name="label2"
              Text="Last Name" />
  <TextBox Grid.Column="1"
            Grid.Row="1"
            Name="txtLast" />
  <Button Grid.Column="1"
          Grid.Row="2"
          Name="button1"
          Content="Close" />
</Grid>

Within a Grid control you create a set of RowDefinition and ColumnDefinition constructs to specify how many rows and columns. You also specify on each row and column the Width and Height. To be truly resizable you want to make sure you never hard code a number into either of these properties. You always want to use “Auto” or “*”, or maybe “1*” or “2*”. Using Auto specifies that the row or column will size all rows or columns according to the largest child control within that row or column. An asterisk means to use all available remaining space. Typically the asterisk is used when you have a List control such as a ListBox, ListView or DataGrid within a row.

If you run this screen you can change font sizes, change screen resolution and resize the screen and all the controls will grow and shrink appropriately. Of course, you can always shrink the screen to a size that is too small for any of the controls. You can control this with the MinWidth and MinHeight properties on the Window or User Control where these controls are hosted.

Summary

Knowing the different methods to layout a screen in XAML is very important. Choosing the correct layout method will ensure that you have an application that is flexible to respond to your users’ differing hardware and OS configuration settings. Of course your layout needs may vary based on your application needs. Where a Canvas won’t work on one screen, it may work very well on another. StackPanels and Grids can also be used together and in combination with ScrollViewers to ensure that even if the user shrinks the screen too much, at least they can still scroll to the rest of the controls.

NOTE: You can download this article and many samples like the one shown in this blog entry at my website. http://www.pdsa.com/downloads. Select “Tips and Tricks”, then “XAML Layout” from the drop down list.

Good Luck with your Coding,
Paul Sheriff

** SPECIAL OFFER FOR MY BLOG READERS **
We frequently offer a FREE gift for readers of my blog. Visit http://www.pdsa.com/Event/Blog for your FREE gift!

More Posts