The Grid panel is by far the most commonly used when it comes to laying out typical UI controls. It is the most versatile and enables us to perform a number of tricks to end up with the layout that we require. It offers a flexible row- and column-based layout system that we can use to build UIs with a fluid layout. Fluid layouts are able to react and change size when users resize their application windows.
The Grid is one of the few panels that can resize all of its child elements depending on the space available, which makes it one of the most performance-intensive panels. Therefore, if we don't need the functionality that it provides, we should use a more performant panel, such as a Canvas or StackPanel.
The children of a Grid panel can each set their Margin property to be laid out using absolute coordinates, in a similar fashion to the Canvas panel. However, this should be avoided wherever possible, because that will break the fluidity of our UI. Instead, we typically define our desired layout using the grid's RowDefinitions and ColumnDefinitions collections and the Grid.Row and Grid.Column Attached Properties.
While we can again hard code exact widths and heights for our rows and columns, we usually try to avoid doing so for the same reason. Instead, we generally take advantage of the grid's sizing behavior and declare our rows and columns, predominantly using one of two values.
The first is the Auto value, which takes its size from its content and the second is the default * star-sized value, which takes all of the remaining space. Typically, we set all columns or rows to Auto except the one(s) that contain(s) the most important data, which is/are set to *.
Note that if we have more than one star-sized column, then the space is normally divided equally between them. However, if we need unequal divisions of the remaining space, then we can specify a multiplier number with the asterisk, which will multiply the proportion of space that that row or column will be provided with. Let's see an example to help to clarify this:
<Grid TextElement.FontSize="14" Width="300" Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition Width="2.5*" /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Are you sure you want to continue?" Margin="40" />
<Button Grid.Row="1" Grid.Column="1" Content="OK" IsDefault="True"
Height="26" Margin="0,0,2.5,0" />
<Button Grid.Row="1" Grid.Column="2" Content="Cancel" IsCancel="True"
Height="26" Margin="2.5,0,0,0" /> </Grid>
This example demonstrates a number of points, so let's see the rendered output before continuing:
Here, we have a very basic confirmation dialog control. It is formed with a Grid panel with three columns and two rows. Note that a single star-sizing is used as the default width and height values for the ColumnDefinition and RowDefinition elements respectively; we do not need to explicitly set them and can simply declare empty elements. Also note that star-sizing will only work when the Grid panel has some size set on it, as we have done here.
Therefore, in our example, the second and third columns and the first row will use star-sizing and take all of the remaining space. The first column also uses star-sizing, however, it specifies a multiplier value of 2.5. As such, it will be provided with two and a half times the amount of space that the other two columns will each have.
Note that this first column is only used to push the buttons in the other two columns to the correct position. While the TextBlock element is declared in the first column, it does not only reside in that column, because it has also specified the Grid.ColumnSpan Attached Property, which allows it to spread out across multiple columns. The Grid.RowSpan Attached Property does the same for rows.
The Grid.Row and Grid.Column Attached Properties are used by each element to specify which cell they should be rendered in. However, the default value for these properties is zero and so, when we want to declare an element within the first column or row of the panel, we can omit the setting of these properties, as has been done for the TextBlock in our example.
The OK button has been declared in the second row and column and sets the IsDefault key to true, which enables users to invoke it by pressing the Enter key on their keyboards. It is also responsible for the blue border on the button and we can use this property to style the default button differently in our own templates. The Cancel button sits next to it in the third column and sets the IsCancel property to true, which enables the users to select it by pressing the Esc key on their keyboards.
Note that we could have set the lower RowDefinition.Height property to 26 instead of setting that on each button explicitly and the end result would have been the same, as the Auto value would be calculated from their height anyway. Also, note that the Margin property has been set on a few elements here for spacing purposes only, rather than for absolute positioning purposes.
There are two other useful properties declared by the Grid class. The first is the ShowGridLines property, which as you can imagine, shows the borders of the rows and columns in the panel when set to true. While not really required for simple layouts as in the previous example, this can be useful while developing more complicated layouts. However, due to its poor performance, this feature should never be utilized in production XAML:
<Grid TextElement.FontSize="14" Width="300" Margin="10"
ShowGridLines="True"> ... </Grid>
The other useful property is the IsSharedSizeScope Attached Property, which enables us to share sizing information between two or more Grid panels. We can achieve this by setting this property to true on a parent panel and then specifying the SharedSizeGroup property on the relevant ColumnDefinition and/or RowDefinition elements of the inner Grid panels.
There are a few conditions that we need to adhere to in order to get this to work and the first relates to scope. The IsSharedSizeScope property needs to be set on a parent element, but if that parent element is within a resource template and the definition elements that specify the SharedSizeGroup property are outside that template then it will not work. It will, however, work in the opposite direction.
The other point to be aware of is that star-sizing is not respected when sharing sizing information. In these cases, the star values of any definition elements will be read as Auto, so we do not typically set the SharedSizeGroup property on our star-sized column. However, if we set it on the other columns, then we will be left with our desired layout. Let's see an example of this:
<Grid TextElement.FontSize="14" Margin="10" IsSharedSizeScope="True"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <Grid TextElement.FontWeight="SemiBold" Margin="0,0,0,3" ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" SharedSizeGroup="Name" /> <ColumnDefinition /> <ColumnDefinition Width="Auto" SharedSizeGroup="Age" /> </Grid.ColumnDefinitions> <TextBlock Text="Name" /> <TextBlock Grid.Column="1" Text="Comments" Margin="10,0" /> <TextBlock Grid.Column="2" Text="Age" /> </Grid> <Separator Grid.Row="1" /> <ItemsControl Grid.Row="2" ItemsSource="{Binding Users}"> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type DataModels:User}"> <Grid ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" SharedSizeGroup="Name" /> <ColumnDefinition /> <ColumnDefinition Width="Auto" SharedSizeGroup="Age" /> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Name}" /> <TextBlock Grid.Column="1" Text="Star-sized column takes all remaining space" Margin="10,0" /> <TextBlock Grid.Column="2" Text="{Binding Age}" /> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid>
In this example, we have an ItemsControl that is data bound to a slightly edited version of our Users collection from our earlier examples. Previously, all of the user names were of a similar length, so one has been edited to demonstrate this point more clearly. The ShowGridLines property has also been set to true on the inner panels for the same reason.
In the example, we first set the IsSharedSizeScope Attached Property to true on the parent Grid panel and then apply the SharedSizeGroup property to the definitions of the inner Grid controls, which are declared inside the outer panel and within the DataTemplate element. Let's see the rendered output of this code before continuing:
Note that we have provided the same number of columns and group names for the columns inside and outside of the DataTemplate element, which is essential for this functionality to work. Also note that we have not set the SharedSizeGroup property on the middle column, which is star-sized.
Grouping just the other two columns will have the same visual effect as grouping all three, but without losing the star-sizing on the middle column. However, let's see what would happen if we also set the SharedSizeGroup property on the middle column definitions:
<ColumnDefinition SharedSizeGroup="Comments" />
As expected, we have lost the star-sizing on our middle column and the remaining space has now been applied to the last column:
The Grid panel within the template will be rendered for each item in the collection and so this will actually result in several panels, each with the same group names and therefore, also column spacing. It is important that we set the IsSharedSizeScope property to true on the Grid panel that is the common parent to all of the inner panels that we wish to share sizing information between.