The problem

Writing UIs in XAML allows you to create them in a declarative way, however XAML can be verbose. Sometimes too verbose.
Have you ever noticed how in a layout based on the Grid control, event though your controls are aligned on rows or columns you have to set both the Grid.Column and Grid.Row attached properties on every* Child?

That gives us a lot of verbose code. Verbose code clutters the code we are interested in. That makes it hard to understand. Hard to maintain. That is not good.

Just imagine if you want to make room at the top of a column: You have to increment Grid.Row for every Control in that column. not good

To illustrate here is a simple Form
Anyone remember Forms?

and the XAML code
<Grid>
    <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
    <Grid.RowDefinitions>...</Grid.RowDefinitions>

    <Label Grid.Column="0" Grid.Row="0" Content="First name" />
    <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Firstname}" />
    <Label Grid.Column="0" Grid.Row="1" Content="Last name" />
    <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Lastname}" />
    <Label Grid.Row="2" Grid.Column="0" Content="Birthday" />
    <DatePicker Grid.Column="1" Grid.Row="2" SelectedDate="{Binding Birthday}" />
    <Label Grid.Row="0" Grid.Column="2" Content="Created" />
    <DatePicker Grid.Column="3" Grid.Row="0" SelectedDate="{Binding Created, Mode=OneWay" IsEnabled="False" />
    <Label Grid.Row="1" Grid.Column="2" Content="Last edited" />
    <DatePicker Grid.Column="3" Grid.Row="1" SelectedDate="{Binding LastEdited, Mode=OneWay}" IsEnabled="False" />
</Grid>

Grid you silly control, why do you have to be so verbose?

Not the solution

We can stick to a convention for where we put attached properties, so we get a little more order into the chaos.
By looking at the XAML code, it should be easy to identify where a control is going to be on the form, so let's put the attached properties up front.
The order Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan seems to make sense. So if there is a button spanning two columns ...

    <Button Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Content="Cancel" Command="{Binding CancelCommand}" />

With this convention it's easier to see where the button will be, but the content has moved quite a bit horizontally, so now it is much harder to see what the button will do.

This convention doesn't cut it. It is still not good.

The solution

Let's do away with the attached properties where we don't need them.
Wouldn't it be nice to have pseudo-controls to position items in a more declarative way?
Indeed it would be and indeed it is. Behold:

<Grid>
    <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
    <Grid.RowDefinitions>...</Grid.RowDefinitions>
    
    <y:GridColumn>
        <Label Content="First name" />
        <Label Content="Last name" />
        <Label Content="Birthday" />
    </y:GridColumn>
    <y:GridColumn>
        <TextBox Text="{Binding Firstname}" />
        <TextBox Text="{Binding Lastname}" />
        <DatePicker SelectedDate="{Binding Birthday}" />
    </y:GridColumn>
    <y:GridColumn>
        <Label Content="Created" />
        <Label Content="Last edited" />
    </y:GridColumn>
    <y:GridColumn>
        <DatePicker SelectedDate="{Binding Created, Mode=OneWay}" IsEnabled="False" />
        <DatePicker SelectedDate="{Binding LastEdited, Mode=OneWay}" IsEnabled="False" />
    </y:GridColumn>
</Grid>

This code positions the elements the same, but now it is much more readable and maintainable.

GridColumns calculate the Grid.Column values of their contents based on the GridColumn's relative position in the Grid.
Similarly the Grid.Row values of their contents get calculated by the children's relative position in the GridColumn.


There is also a GridRow pseudo-control which operates in exactly the opposite way.

Even better: We can easily move all the controls in a column down or wherever we want.
And heck, we don't really have to move them down to make space, we can just insert the new column at the top or anywhere really:

<Grid>
    <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
    <Grid.RowDefinitions>...</Grid.RowDefinitions>
    
    <y:GridColumn>
        <Label Content="First name" />
        <Label Content="Middle name" /> <!-- This control is new -->
        <Label Content="Last name" />
        <Label Content="Birthday" />
    </y:GridColumn>
    <y:GridColumn>
        <TextBox Text="{Binding Firstname}" />
        <TextBox Text="{Binding Middlename}" /> <!-- This control is new -->
        <TextBox Text="{Binding Lastname}" />
        <DatePicker SelectedDate="{Binding Birthday}" />
    </y:GridColumn>
    <y:GridColumn>
        <Label Content="Created" />
        <Label Content="Last edited" />
    </y:GridColumn>
    <y:GridColumn>
        <DatePicker SelectedDate="{Binding Created, Mode=OneWay}" IsEnabled="False" />
        <DatePicker SelectedDate="{Binding LastEdited, Mode=OneWay}" IsEnabled="False" />
    </y:GridColumn>
</Grid>

Now wasn't that what you always wanted Grid to do?
In HTML people move away from Tables, we have a lot of catching up to do

Unfortunately there are still a few features missing.
Most of all, currently you can't nest a GridRow in a GridColumn or vice versa. The reason for this is that they are hacks. Very elaborate hacks which hack so many aspects they feel right, but I haven't found a good solution to hack nesting yet.
It will definitely be supported at some point, though.

Also, a more concise syntax for specifying rows and columns will make your life easier at some point in the future.

* except for the first

TODO: split the documentation, add some examples, add an index explaining the scope of the project

Last edited Jul 24, 2014 at 4:52 AM by MStramm, version 19