The WPF / Silverlight syntax is long and cumbersome. This blog post describe a simple attached property that allows you to specify row and column widths / heights as a simple comma separated list, e.g. RowDefinitions=”Auto,,3*,,,,2*”
The Grid is probably one of the most useful and versatile layouts that Silverlight and WPF offers. However, if you hand craft your XAML, as I do, you will probably start to find the Grid markup for defining rows and columns to be verbose and cumbersome. If we look at the following example, which uses a mixture of Auto, Star and Pixel widths / heights:

Despite this being a simple example, the required row and column definitions are highly verbose:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="User Details" FontSize="20" HorizontalAlignment="Center" Grid.ColumnSpan="2"/> <TextBlock Text="Forename:" Grid.Row="1"/> <TextBox Text="Jeremy" Grid.Column="1" Grid.Row="1"/> <TextBlock Text="Surname:" Grid.Row="2"/> <TextBox Text="James" Grid.Column="1" Grid.Row="2"/> <TextBlock Text="Age:" Grid.Row="3"/> <TextBox Text="24" Grid.Column="1" Grid.Row="3"/> <TextBlock Text="Phone:" Grid.Row="4"/> <TextBox Text="+44 191 555467" Grid.Column="1" Grid.Row="4"/> <TextBlock Text="Notes:" Grid.Row="5"/> <TextBox Text="A multi-line block of text ..." TextWrapping="Wrap" Grid.Row="6" Grid.ColumnSpan="2"/> </Grid> |
To provide a simpler alternative, I have created attached properties, one for the column definitions, and the other for the rows. These attached properties allow you to specify the columns / rows as a comma separated list of heights and widths.
Using this technique, the above example becomes:
<Grid local:GridUtils.ColumnDefinitions="100," local:GridUtils.RowDefinitions="Auto,,,,,,2*"> <TextBlock Text="User Details" FontSize="20" HorizontalAlignment="Center" Grid.ColumnSpan="2"/> <TextBlock Text="Forename:" Grid.Row="1"/> <TextBox Text="Jeremy" Grid.Column="1" Grid.Row="1"/> <TextBlock Text="Surname:" Grid.Row="2"/> <TextBox Text="James" Grid.Column="1" Grid.Row="2"/> <TextBlock Text="Age:" Grid.Row="3"/> <TextBox Text="24" Grid.Column="1" Grid.Row="3"/> <TextBlock Text="Phone:" Grid.Row="4"/> <TextBox Text="+44 191 555467" Grid.Column="1" Grid.Row="4"/> <TextBlock Text="Notes:" Grid.Row="5"/> <TextBox Text="A multi-line block of text ..." TextWrapping="Wrap" Grid.Row="6" Grid.ColumnSpan="2"/> </Grid> |
The comma separated list defines the widths / heights of each column / row. The number of items in this list defined the number of rows / column that are generated. This notation supports pixel, star and auto widths / heights. Also, if you want a row or column with the default size, just leave the value blank, i.e. a row definition of “,,,” will create four rows of default height.
The code to achieve this is pretty simple, involving a couple of attached properties and string parsing in their changed event handlers. The complete code for the GridUtils class is given below, feel free to copy and paste it into your project:
using System.Windows; using System.Windows.Controls; namespace SimplifiedGrid { public class GridUtils { #region RowDefinitions attached property /// <summary> /// Identified the RowDefinitions attached property /// </summary> public static readonly DependencyProperty RowDefinitionsProperty = DependencyProperty.RegisterAttached("RowDefinitions", typeof(string), typeof(GridUtils), new PropertyMetadata("", new PropertyChangedCallback(OnRowDefinitionsPropertyChanged))); /// <summary> /// Gets the value of the RowDefinitions property /// </summary> public static string GetRowDefinitions(DependencyObject d) { return (string)d.GetValue(RowDefinitionsProperty); } /// <summary> /// Sets the value of the RowDefinitions property /// </summary> public static void SetRowDefinitions(DependencyObject d, string value) { d.SetValue(RowDefinitionsProperty, value); } /// <summary> /// Handles property changed event for the RowDefinitions property, constructing /// the required RowDefinitions elements on the grid which this property is attached to. /// </summary> private static void OnRowDefinitionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Grid targetGrid = d as Grid; // construct the required row definitions targetGrid.RowDefinitions.Clear(); string rowDefs = e.NewValue as string; var rowDefArray = rowDefs.Split(','); foreach (string rowDefinition in rowDefArray) { if (rowDefinition.Trim() == "") { targetGrid.RowDefinitions.Add(new RowDefinition()); } else { targetGrid.RowDefinitions.Add(new RowDefinition() { Height = ParseLength(rowDefinition) }); } } } #endregion #region ColumnDefinitions attached property /// <summary> /// Identifies the ColumnDefinitions attached property /// </summary> public static readonly DependencyProperty ColumnDefinitionsProperty = DependencyProperty.RegisterAttached("ColumnDefinitions", typeof(string), typeof(GridUtils), new PropertyMetadata("", new PropertyChangedCallback(OnColumnDefinitionsPropertyChanged))); /// <summary> /// Gets the value of the ColumnDefinitions property /// </summary> public static string GetColumnDefinitions(DependencyObject d) { return (string)d.GetValue(ColumnDefinitionsProperty); } /// <summary> /// Sets the value of the ColumnDefinitions property /// </summary> public static void SetColumnDefinitions(DependencyObject d, string value) { d.SetValue(ColumnDefinitionsProperty, value); } /// <summary> /// Handles property changed event for the ColumnDefinitions property, constructing /// the required ColumnDefinitions elements on the grid which this property is attached to. /// </summary> private static void OnColumnDefinitionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Grid targetGrid = d as Grid; // construct the required column definitions targetGrid.ColumnDefinitions.Clear(); string columnDefs = e.NewValue as string; var columnDefArray = columnDefs.Split(','); foreach (string columnDefinition in columnDefArray) { if (columnDefinition.Trim() == "") { targetGrid.ColumnDefinitions.Add(new ColumnDefinition()); } else { targetGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = ParseLength(columnDefinition) }); } } } #endregion /// <summary> /// Parses a string to create a GridLength /// </summary> private static GridLength ParseLength(string length) { length = length.Trim(); if (length.ToLowerInvariant().Equals("auto")) { return new GridLength(0, GridUnitType.Auto); } else if (length.Contains("*")) { length = length.Replace("*",""); if (string.IsNullOrEmpty(length)) length = "1"; return new GridLength(double.Parse(length), GridUnitType.Star); } return new GridLength(double.Parse(length), GridUnitType.Pixel); } } } |
The code which parses the row and column strings is also executed by the Visual Studio designer, so you can see your new rows / columns immediately. It also works just fine in WPF and Silverlight.
You can download example projects in both technologies: SimplfiedGrid.zip
readers might also be interested in Mike Talbot’s AutoGrid which dynamically adds rows / columns based as children are added to the grid. A very neat idea!
(Thanks to Rob Newsome for coming up with this idea in the first place!)
Regards, Colin E.
Tags: attached behaviour, grid


email: ceberhardt@scottlogic.co.uk
on Google+



Colin
Somebody else has just written a similar implementation on Code Project, and there’s a link to your implementation in there. I hadn’t seen you version before, so my apologies for not commenting on this previously, but great job.
If you’re interested, I knocked together an attached behavior to do the same thing, and posted it as a reply to the other article here: http://www.codeproject.com/KB/WPF/apexgrid.aspx?msg=3977308#xx3977308xx
Pete
Hi Pete,
Wow, their approach really is quite similar! Thanks for the heads-up.
Colin E.
Great stuff thanks. I was part way through implementing my own sub-class of Grid but attach properties is a much cleaner way to do it.
Binned my code and adopted yours which is much better!
That’s great to hear – thanks Chris
Awesome. Just got rid of my final Grid.RowDefinitions. Kudos
[...] Simplified Grid Markup for Silverlight and WPF (Colin Eberhardt) [...]
[...] A Simplified Grid Markup for Silverlight and WPF [...]
This fails if you have something like this, RowDefinitions=”*”.
Need to make this change here
///
/// Parses a string to create a GridLength
///
private static GridLength ParseLength(string length)
{
length = length.Trim();
if (length.ToLowerInvariant().Equals(“auto”))
{
return new GridLength(0, GridUnitType.Auto);
}
if (length.Contains(“*”))
{
length = length.Replace(“*”, “”);
// This is the additional check
if (string.IsNullOrEmpty(length)) length = “1″;
return new GridLength(double.Parse(length), GridUnitType.Star);
}
return new GridLength(double.Parse(length), GridUnitType.Pixel);
}
Thanks Ganesh, I have updated the code with your fix. Well spotted
A more succinct Xaml dialect is desperately needed. Grids are a prime example. Element notation is great, but redundant. Don’t get me started with locally defined DataTemplates.
Gaudy Xaml is hurting adoption IMO.
Strong words my friend
[...] A Simplified Grid Markup for Silverlight and WPF – Colin Eberhardt shares a technique for creating grid based WPF and Silverlight user interfaces quickly and easily using simplified markup and a set of functions which wire the simplified markup up. [...]
Thanks, Jeremy. This has been on my ToDo list for more than a year. Kind of amazed that you’re the first to actually do it. Double thanks.
Blendability is the primary worry. If it doesn’t blend, I wouldn’t be able to recommend it in most apps.
I can live with the risk that using Blend to change the rows/columns would crash it … as long as I could SEE the grid and MANIPULATE objects placed in the grid.
Looking forward to checking that out … or better still, learning what someone else confirmed/discovered.
Sorry, Colin. I heard about it from Jeremy Likness and thought he wrote the post. Thanks to you.
awesome , I’m using for the similair projects the new app from Microsoft Silverlight team VSLightSwitch , it’s a simple tool to make an amazing grid markup for silverlight and WPF
you can download it ;
http://download.microsoft.com/download/E/A/1/EA1D55DE-703B-450F-A119-466EDEE4E256/VSLightSwitchBeta1ENU1.iso
This is neat – those row and column definitions do get tedious. Does it Blend?
Hi Tom,
Glad you like it. Good question … do you Blend? I don’t use it myself, so I would be interested to know if it works for blend users.
Hi Colin. It does Blend in that it shows up correctly in the designer. However the dynamically generated rows and columns are not recognized by Blend and the properties cannot be edited.
Boo hiss … oh well. Thanks for letting me know!
A Simplified Grid Markup for Silverlight and WPF | Colin Eberhardt’s Adventures in WPF…
Thank you for submitting this cool story – Trackback from DotNetShoutout…