The Layout System

The previous XAML example indicates an interesting aspect of XAML interfaces. The XAML didn't set the width of the Border, the StackPanel, the Button, the Image, or the TextBox. Only the UserControl's width was declared. Everything else automatically sized itself, based on the width of its container.

You can create XAML interfaces in which all sizes and positions are explicitly declared, just like you can in older technologies such as Windows Forms. For most applications, that is most assuredly not recommended. You'll take maximum advantage of XAML by using its built-in features for sizing and positioning visual elements. Used properly, these features can allow your application to run at varying resolutions and sizes. (Wouldn't it be nice to stop those arguments about whether to support 800×600 or 1024×768 or 1280×1024?)

Measurement Units

To explicitly set size and position of units in XAML, a special type of unit is used. It can be thought of as a “virtual pixel.” That is, under default conditions (default desktop dots per inch, for example), one unit will correspond to one pixel. But there are some important differences.

First, the properties holding the units are not integers, as would be common with older pixel-based technologies. The properties are of type Double. This allows applications to have a finer grain, which is important if an element is zoomed, for example.

Second, changing factors such as desktop dpi or the browser zoom factor for Silverlight applications will cause the units to change in size accordingly. If the WPF desktop dpi changes from the default of 96 to 120, then a unit becomes 1.25 pixels in width instead of a single pixel in width.

Panels

If you've done ASP.NET development, you're probably comfortable with the idea that you place your visual elements in a container that does the final arrangement. XAML takes that concept and adds many more options for controlling the final layout result.

If you've concentrated on Windows Forms development, you're probably accustomed to the idea that you should position all of your visual elements precisely, using properties for Left and Top. In that case, you'll need to adjust to a different technique. You must learn to do layout with a combination of explicit positioning and various kinds of automatic layout.

Different Panels for Different Purposes

In XAML, you can choose from several different panels to contain your visual elements, each performing a different type of layout. Combining and nesting these panels gives even more flexibility.

That means you can't just pick one type of panel and stick with it. Instead, you need to learn what each panel can do and often use the principle of composition to choose the combination of panels that will give you the particular user interface layout that you want.

First, let's discuss some characteristics shared by all panels. Then you'll briefly review three commonly used panels that all three XAML platforms share: StackPanel, Grid, and Canvas.

The Panel Class

All panels in XAML descend from a class named Panel. Since Panel is an abstract class, it can't be directly instantiated. It just serves as the base class for all panels, encompassing those included in various XAML libraries and those developed by third parties.

As you saw in our simple example, a panel such as StackPanel can contain multiple child elements. Those elements are placed in the panel's Children collection. The elements in the Children collection must descend from the FrameworkElement class.

In code, you can add elements to a panel by using the Add method of the Children collection, the same way that you would add elements to any other collection. For routine development, you won't need to do that very often. You'll likely be creating interfaces either by doing drag-and-drop in a visual designer, or by typing XAML directly.

When a panel's XAML definition contains other nested visual elements, those visual elements are added to the panel's Children collection automatically. You saw that in our first XAML example, which is reproduced here (code file: FirstExample.xaml):

<UserControl x:Class="SampleUC"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300" >
    <Border BorderThickness="4" BorderBrush="Blue">
        <StackPanel>
            <Button Margin="5">I'm a button</Button>
            <Image Margin="5" Source="http://billyhollis.com/TextureWavy.jpg" />
            <TextBox Margin="5">Text for editing</TextBox>
        </StackPanel>
    </Border>
</UserControl>

The Button, Image, and TextBox element definitions are inside the definition for StackPanel. When the XAML is compiled into a running program, those elements become children of the StackPanel.

The order of the elements in the Children collection is important. The previous example illustrates this. The final rendered UserControl showed the elements stacked in the order in which they were added to the panel.

StackPanel

You've seen the basic behavior of StackPanel. It stacks elements vertically by default, making space for each element.

StackPanel can also stack elements horizontally by changing the Orientation property to Horizontal.

Attached Properties

To discuss the Grid panel, first you need to understand a XAML concept called attached properties. These are used to allow the XAML definition of a child element to also contain information the parent panel needs to know about the child.

Attached properties are used for a wide variety of other purposes, too. Some of those will be covered later, but many of them are beyond the scope of this book.

Attached properties have the following form in XAML:

<Button FontSize=20 Grid.Column="2" Grid.Row=3>Cancel</Button>

Contrast the first two attributes after the Button tag. The FontSize attribute sets a property value on this instance of the Button. However, the Grid.Column attribute does not set a property on the Button. It sets an attached property value on the Grid class. The XAML parser automatically handles this difference, handing the value of “2” over to the Grid class to store for the Button instance.

Setting Attached Properties in Code

It's occasionally necessary to set attached properties in code. That brings up an interesting twist. The attached property isn't really a property at all; it's a set of shared methods on the class that implements the attached property.

For every attached property, there is a Get method on the implementing class and a Set method. The methods have a standard naming convention. The Get method for Column must be named GetColumn, for example, and the Set method for Column is similarly required to be SetColumn.

These Get and Set methods for an attached property take an argument to specify the control to which the property value is “attached.” For example, to get the current value of the Grid.Column setting for Button1, you might use a line of code like this:

Dim d As Integer = Grid.GetColumn(Button1)

The Set method needs one more argument. It contains the property value that is being set. To set the Grid.Column attached property on a button named Button1 to a value of 3, the following code would be used:

Grid.SetColumn(Button1, 3)

Grid

The most flexible panel for most XAML applications is the Grid. The Grid is so flexible and useful that the Visual Studio XAML visual designers use it as the default layout container for a new Window, Page, or UserControl.

For many developers, “grid” is synonymous with “data grid.” That association does not apply in XAML. The Grid panel in XAML is not a data grid in any sense.

The Grid panel allocates its space to cells. Those cells are the intersections of rows and columns. Typically, the first thing you do for a Grid is to define the rows and columns of the Grid.

The rows do not need to be the same height, and the columns do not need to be the same width. They can be, of course.

A Grid has a property called ColumnDefinitions, which contains a collection of ColumnDefinition elements, each of which is the definition for one column. Similarly there is a RowDefinitions property for RowDefinition elements. Naturally, you can create these collections in XAML. Here is simple XAML showing ColumnDefinitions and RowDefinitions for a Grid, in this case defining three columns and two rows:

<Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition />
    <ColumnDefinition />
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
</Grid.RowDefinitions>

Sizing Grid Rows and Columns

The previous simple example has no sizing information for the rows or columns. Before presenting the XAML for sizing rows and columns, the sizing options need to be explained. First you'll look at sizing columns, and then you'll relook at the discussion for rows, because they both use the same sizing concepts.

Columns in a Grid can be a fixed width, a proportional width, or an automatic size. These settings can be mixed and matched as needed for different columns in the Grid:

  • Fixed width works just as you would expect. The width of a column can be set to a specific number of units.
  • Automatic width means that the column is sized to the largest element contained in a cell of that column. If the column does not contain any elements, it has zero width.
  • Proportional width relates the width of a column to the width of other columns. After fixed width columns and automatically sized columns have been sized, any remaining width is divided among proportional width columns.

The amount each proportional column gets depends on a number associated with that column. All the numbers are added up for the proportional width columns to give a sum. Then each column's width is calculated by dividing that column's number by the sum, and multiplying times the available width for all the proportional columns.

Math always sounds complex when expressed in words, so let's look at an example. Suppose you have three proportional width columns, and the numbers for them are 1, 4, and 5. Then the sum of the numbers for all columns is 10. Column one gets 1/10 of the available width, column two gets 4/10 (or two-fifths), and column three gets 5/10 (or a half). If the amount of width left in the Grid for proportional columns were 200 units, column one would be 20 units wide, column two would be 80 units, and column three would be 100 units.

The XAML for all three sizing options is straightforward. The ColumnDefinition's Width property is used. For a fixed size, Width is set to number of units desired. For automatic size, Width is set to Auto. For a proportional size, Width is set to a number with an asterisk at the end.

The following XAML example contains a set of column definitions showing each sizing option. The first column has a fixed width of 60. The second column has an automatic width. The third and fourth columns receive 40% and 60%, respectively, of the remaining width of the Grid.

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="60" />
    <ColumnDefinition Width ="Auto"/>
    <ColumnDefinition Width ="4*" />
    <ColumnDefinition Width ="6*" />
</Grid.ColumnDefinitions>

If you create a Grid with only one row and the columns as defined in the previous XAML, and then place buttons in each cell of the Grid, the result will look much like Figure 12.2. That example is rendered in Windows 8 XAML on the left and in WPF XAML on the right. The width of the first column is 60 unconditionally, while the width of the second column is based on the width of the button placed in it. The third and fourth columns share what's left, in a ratio of 2 to 3.

Figure 12.2 A Grid with four columns defined by the XAML example shown earlier, and with four buttons included to show the sizes and locations of the columns

12.2

Notice how narrow the second column is. The content of the button, namely the numeral “2,” is quite narrow, and the button automatically sizes to that narrow width. The Grid column then sizes itself to contain that Button element. You'll see much more in the Sizing and Layout section later in the chapter.

If you do this example in Windows 8, you'll need to tell the Button elements to stretch horizontally and vertically using the HorizontalAlignment and VerticalAlignment properties. The default for Button in WPF is to stretch, but the default for Windows 8 XAML is not to stretch.

The numbers on the last two columns could have been “6*” and “9*,” or “100*” and “150*,” or any other numbers with a ratio of 2 to 3, and the end result on the screen would be exactly the same. For proportional widths, it doesn't matter what the numbers are. It only matters what fractional portion a column's number makes up of the sum for proportional columns.

The exact same sizing options are available for rows. The only difference is that you use the Height property of a RowDefinition in place of the Width property of a ColumnDefinition. To avoid boredom, this won't go through a virtually identical example for rows.

The Visual Studio visual designer contains various ways to define columns and rows. One way is to use special editors. You can get to those editors in the Properties window for the Grid. The entry in the Properties window for ColumnDefinitions has a button on the right with an ellipsis. Clicking that button brings up the ColumnDefinitions editor, which gives you precise control over all the different properties of each ColumnDefinition. The ColumnDefinition editor is shown in Figure 12.3, with four columns as discussed in the earlier example. There is a similar editor for RowDefinitions.

Figure 12.3 The ColumnDefinition editor in Visual Studio 2012. It is accessed by pressing the button next to the ColumnDefinitions property in the Properties window

12.3

Placing Elements in a Grid

To specify the column and row for an element, you use the Grid.Column and Grid.Row attached properties. The numbering for rows and columns is zero-based, just like all collections in .NET. To place a Button in the second row and third column of a Grid, the Button would be defined inside a Grid with a line of XAML like this:

<Button Grid.Row="1" Grid.Column="2">Button text</Button>

The default for Grid.Row and Grid.Column are zero. Elements that don't set a row or column end up in the top left cell of the Grid.

You can place multiple elements in the same Grid cell. Those elements, if their areas intersect, are layered on top of one another. Elements later in the Children collection, which means further down in the XAML definition, are rendered on top of earlier elements in the Children collection.

Spanning Columns and Rows

By default, an element is placed in a single cell of a Grid. However, an element can begin in the cell assigned by Grid.Column and Grid.Row, and then span additional rows and/or columns.

The attached properties Grid.ColumnSpan and Grid.RowSpan determine the number of columns and rows an element spans. Their default is, of course, 1. You can set either or both for an element. Setting only Grid.ColumnSpan restricts an element to a single row, but extends the element into extra columns. Similarly, setting only Grid.RowSpan extends through multiple rows in the same column. Setting both causes an element to span a rectangular block of cells.

To see Grid.Column, Grid.Row, Grid.ColumnSpan, and Grid.RowSpan in action, let's look at an example. The following XAML defines a Grid with several Buttons, using varying rows, columns, and spans. Figure 12.4 shows what that Grid would look like in design view in Visual Studio for a WPF project. The design view is shown so that you can see the blue lines that define rows and columns, and how buttons span the rows and columns based on their settings in XAML (code file: GridWithButtons.xaml):

Figure 12.4 A design view of a Grid, showing elements positioned in various cells and some elements spanning multiple cells. This example is rendered in WPF on Windows 7

12.4
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="50*" />
        <RowDefinition Height="20*" />
        <RowDefinition Height="30*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20*" />
        <ColumnDefinition Width="20*" />
        <ColumnDefinition Width="45*" />
        <ColumnDefinition Width="15*" />
    </Grid.ColumnDefinitions>
    <Button>1 cell</Button>
    <Button Grid.Column="1" Grid.RowSpan="2">2 cells</Button>
    <Button Grid.Row="2" Grid.ColumnSpan="2">2 cells</Button>
    <Button Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="2">4 cells</Button>
    <Button Grid.Column="3" Grid.Row="2">1 cell</Button>
</Grid>

Canvas

The Canvas panel is perhaps the easiest panel to understand. It is placed it at the end of the panel discussion, though, because it should be used sparingly in XAML interfaces.

If you're a Windows Forms developer, you will probably feel an affinity for the Canvas panel. It positions children in almost exactly the way as a Form in Windows Forms. That may tempt you to rely on the Canvas so that you can avoid understanding the complexity of the other panels.

You should resist that temptation. While the Canvas certainly has valid uses, it should not normally be the dominant element on a XAML user interface. If it is, the interface will not possess many of the best characteristics of a XAML interface. It won't automatically adjust children to fit different aspect ratios, for example. This would be a particularly major drawback for Windows 8 XAML applications.

However, Canvas is a good choice for several application scenarios. Here are a few examples:

  • Graphing and charting surfaces
  • Animation of positions of elements
  • Surfaces that allow users to move elements around
  • Positioning elements in certain types of control

Positioning Child Elements on a Canvas

Children are positioned on a Canvas with the attached properties Canvas.Top and Canvas.Left. These correspond to the Top and Left properties of Windows Forms controls in the way they are used for positioning. The two properties position the upper left corner of an element; Canvas.Top determines the distance down from the top of the Canvas, and Canvas.Left determines the distance over from the left side.

WPF also has Canvas.Bottom and Canvas.Right attached properties, but since Silverlight and Windows 7 lack them, they are not discussed in this chapter.

Nesting Panels

It is common to nest panels to gain more flexible control over layout. For example, you might want a stack of buttons in a Grid cell. That is achieved by making a StackPanel a child element of a Grid, and then placing the buttons you want as children of the StackPanel. Here is a XAML example (code file: GridContainingStackPanel.xaml):

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Column="2" Grid.Row="1" >
        <Button Margin="4">Save</Button>
        <Button Margin="4">Cancel</Button>
    </StackPanel>
</Grid>

As another example, you could put a Canvas in a cell of a Grid to gain pixel-by-pixel positioning just inside that cell.

Sizing and Layout of Elements

One of the primary goals of XAML is allowing creation of interfaces that scale themselves to the space they are given. With widely varying resolutions, plus the fact that an application window can be in a wide range of sizes, many different factors affect the final rendered size and position of a XAML element. Windows 8 introduces some new complexities in the form of “snapped” application windows.

This section presents an overview of the major factors that affect sizing and layout of XAML elements. It doesn't try to tell you everything there is to know about the subject—just enough to handle the common cases you will run into during application programming.

Don't be surprised if you have to read this section a couple of times to sort out all of the factors involved in sizing and layout. It's a complex subject, but a necessary one to understand if you're going to design anything beyond very simple interfaces in XAML.

What Is Layout?

In XAML, layout means sizing and arranging the children of a panel. This is a complicated, recursive process. Since panels may contain other panels as children, sizing and positioning must be done at several “levels” before a complete Window or Page can be rendered. The process starts in the root element, and goes down through the tree of child elements. At each level, there is interaction between child elements and their parent to decide on size and position.

If you don't understand some of the mechanisms used for this process, you'll constantly be producing XAML interfaces that don't act the way you expect.

First Step: Measuring the Child Elements

Measurement is the process of determining the size that an element wants to be when it is rendered. That size is usually called the desired size, and in fact elements have a property named DesiredSize. It may or may not be the actual rendered size; that depends on several other factors, including whether the height and width values are hard-coded, and what the container holding the element wants to do with the element.

The base FrameworkElement class contains several properties that furnish information to the measurement process, including:

  • Height and Width
  • MinHeight, MaxHeight, MinWidth, and MaxWidth
  • Margin

Using Height and Width Properties

Height and Width work exactly as you would expect. They hard-code the size of an element, just as they do in other user interface technologies. If an element with hard-coded size is placed in a panel, the panel will respect that size and will not change it, even if there's not enough room to show the element and the element must be clipped.

Since these two properties work in a way you'll find familiar, at first you may be tempted to bypass the whole process of understanding XAML's complex sizing process, and just set the sizes you want. For most XAML applications, that's a mistake. It requires giving up much of the flexibility of XAML to respond to different conditions. You should be conservative in using explicit settings for Height and Width; do it only if automatic sizing doesn't fit your needs.

One common place to explicitly set Height and Width is on the root element of your interface. For example, when you create a new WPF Window in Visual Studio, it will have Height and Width explicitly set to default values.

Applying Minimum and Maximum Values

The next level of control over size is to set a range of values for the height and width of an element. The four properties MinHeight, MaxHeight, MinWidth, and MaxWidth contain the range settings. This discussion is presented in terms of width, but the exact same discussion applies to height.

If MinWidth is set for an element, it won't be sized less than that amount. If a MaxWidth is set, the element won't be made wider than that amount.

Within that range, an element will be sized based on other factors, including the space available within the element's container and the size of child elements (if any).

Applying a Margin

For the purposes of fitting an element within its container, the element's Margin setting must also be taken into account. The container will have a certain size available for the element; that size must then be reduced by any Margin on the element. If an element has a Margin, room is always made for it, even if that means truncating the control.

Margin can be set uniformly around an element, or Margin can be different for all four sides. Consider a Button with the following XAML definition:

<Button Margin="5,10,0,-10">Save</Button>

This instructs the layout engine to place 5 units of margin on the left side, 10 units on the top, and no margin on the right side. It also includes a negative margin for the bottom of the Button, which allows the Button to flow outside its normal area by 10 units on the bottom.

Second Step: Arranging the Child Elements in a Panel

After the measurement phase, an element has either an explicit size or a desired size, and possibly a Margin that must be included. Using that information, the parent panel then arranges each child element inside a space in the panel allocated for the element. For example, for a Grid, the allocated space includes the cell or group of cells determined by the elements Grid.Row, Grid.Column, Grid.Rowspan, and Grid.Columnspan properties.

Arrangement includes placement of the element in the space available, and may include additional adjustments to the size. Any necessary adjustments are made independently for width and height.

The arrangement process depends on several factors:

  • The type of panel involved
  • The sizing results from the measurement phase
  • The values of the HorizontalAlignment and VerticalAlignment properties for an element

Each of these factors can have many different possibilities. The number of combinations of factors is consequently quite large. To explain the process, let's simplify it a bit and leave out some of the less common cases. While the resulting explanation isn't complete, it should be enough for you to understand what happens for most of the situations you are likely to encounter as you begin developing XAML interfaces.

Because height and width are sized and arranged independently, the discussion is focused on an explanation on height. The exact same principles apply to width, with changes for the names of the properties involved.

Desired or Required Height vs. Available Height

During the arrangement phase, the final rendered height for an element must be decided. The first factor to consider is whether the element's explicit or desired height will fit in the space allocated for the element. In proper Goldilocks fashion, the allocated height in the panel might be too big, too small, or just right.

If the allocated height happens to be just what the element wants for a height, then the process is finished. That's the height that's used, and the element exactly fills the allocated space from top to bottom.

If the allocated height is too small, then the element must be sized bigger than the allocated height. In that case, the visual part of the element is a truncated portion of the element. How the truncation is done depends on the vertical positioning of the element, which you'll take up later.

If the allocated height is bigger than the element needs, then the actual height of the element depends on whether the height was explicitly set. For an explicit height, that's the height used for rendering. For a desired height, the actual rendered height depends on the value of the VerticalAlignment property. That property is also used for vertical positioning of the element.

Vertical Positioning and the VerticalAlignment Property

The VerticalAlignment property has four possible values: Top, Center, Bottom, or Stretch. Stretch is the default.

The vertical positioning of an element depends on this setting. Top, Center, and Bottom position the edges of the control. The Stretch setting causes more complex decision making, so the other settings will be considered first.

As you would probably expect, if you set your element with a VerticalAlignment of Top, the top edge of the element will be at the top of the allocated space. That's true whether the element is too tall or too short for its space. If the element is too tall for its space, then the top of the element is at the top of the space and the element is truncated at the bottom. If the element is too short for its space, then the element is positioned at the top of the space and any space that's left over is simply unused by the element. Figure 12.5 shows the visual effect of both cases. Examples are shown in Figure 12.5 of height set both with the Height property and the MinHeight property, and as you can see, the results are the same for a VerticalAlignment of Top.

Figure 12.5 Four Buttons with VerticalAlignment of Top and a Margin of 10, all contained in a Grid

12.5

The left Buttons are too short for their allocated space. The right Buttons are too tall for their allocated height, so they are truncated. A Border decorator around the Grid is used for clarity, and the Grid has ShowGridLines set to True to let you see the Grid's cells.

Similarly, a VerticalAlignment of Bottom places the bottom edge at the bottom of the allocation space, and clips or leaves empty space at the top, as necessary. A VerticalAlignment of Center places the center of the control at the center of the allocated space, and clips or leaves empty space at both top and bottom.

If VerticalAlignment is set to Stretch, and the height is too big, then the result is the same as if VerticalAlignment has been set to Top. If VerticalAlignment is set to Stretch, and the height is smaller than the allocated height, then the result depends on whether the height was explicitly set or just a desired height. For a desired height, the element will be stretched to fill the entire vertical space available to it.

For height that is explicitly set but shorter than the allocated height, the effect is the same as if VerticalAlignment had been set to Center. The element gets its required height and is centered vertically in the allocated space, leaving extra space at the top and bottom.

There are a lot of combinations, and it's hard to keep them straight at first. However, most XAML developers find that predicting the effects of sizing and alignment becomes second nature after a while.

The process for arriving at a final width is conceptually identical to the one for height. Of course, the properties used are different. The property used for final position is named HorizontalAlignment instead of VerticalAlignment, and it has values of Left, Center, Right, and Stretch. However, if you understand the discussion on height, you should have no problem mapping the concepts to width.

Arrangement in a StackPanel

The arrangement phase is somewhat different in a StackPanel. If a StackPanel's Orientation property is at the default of Vertical, then a StackPanel will only stretch elements horizontally. Even if VerticalAlignment for an element is set to Stretch, the element won't be stretched vertically beyond its desired or hard-coded size.

This makes complete sense, because the StackPanel expects other elements to possible be stacked underneath a child, so it would not be appropriate to stretch a child arbitrarily.

However, a vertical StackPanel will stretch horizontally if the HorizontalAlignment is set to Stretch. If the HorizontalAlignment is set to anything else, the element will not be stretched. It will remain at its desired or hard-coded width, and positioned according the HorizontalAlignment, much as an element in a Grid cell would be positioned horizontally.

If the StackPanel's Orientation is set to Horizontal, then the vertical and horizontal dimensions merely swap the way they work in the above description.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset