A simpler (and dynamic) Grid control for WPF

6. november 2009

The last days I have been playing with WPF trying to get an understanding of how it works ”under the hood”. So I have written a bunch of tiny applications where I prototype a function. When building these applications I don’t care much about its user interface, but I still like to have my controls lined up neatly. To do this I usually use a grid control with some rows and columns. This usually works well, but the grids syntax gets tiresome to write and rewrite all the time.

Yesterday this annoyed me so much that I decided to fix it! The reason was not only the syntax, but also that I needed a grid that let me set the number of rows and columns dynamically using data binding. The result is the DynamicGrid control. It allows you to replace this code:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
</Grid>

With this code:

<grid:DynamicGrid NumColumns="8" NumRows="8">       
</grid:DynamicGrid>

Nice! And it also allows you to use data binding to setup the number of rows or columns. Like this:

<grid:DynamicGrid NumColumns="{Binding ColumnCount}" NumRows="{Binding RowCount}">       
</grid:DynamicGrid>

So your grid can adapt to your data source. On top of all that, it even plays nice with the old grid-markup. So if you need a header row with a fixed size then you can insert it using a RowDefinition and let the DynamicGrid add the rest of the rows and columns for you automatically.

How does it work?

It turned out that implementing the grid was really easy. All I needed to do was to subclass the Grid class, add two new dependency properties and add the code that added the columns and rows automatically.

I created two new dependency properties named NumColumns and NumRows. They are both identical except for the naming:

public static readonly DependencyProperty NumColumnsProperty =
    DependencyProperty.Register("NumColumns", typeof(Int32), typeof(DynamicGrid));

public Int32 NumColumns
{
    get { return (Int32)GetValue(NumColumnsProperty); }
    set { SetValue(NumColumnsProperty, value); }
}

There is not much to note about them – we default to one column and one row.

I then wrote an override for the OnInitialized method:

protected override void OnInitialized(EventArgs e)
{
    base.OnInitialized(e);

    RecreateGridCells();
}

This will call the RecreateGridCells method that does the actual setup of the dynamic grid. Since it is called after the base grid is initialized we support its functionality.

The RecreateGridCells method does the custom setup:

private void RecreateGridCells()
{
    int numRows = NumRows;
    int currentNumRows = RowDefinitions.Count;

    while (numRows > currentNumRows)
    {
        RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
        currentNumRows++;
    }

    while (numRows < currentNumRows)
    {
        currentNumRows--;
        RowDefinitions.RemoveAt(currentNumRows);
    }

    int numCols = NumColumns;
    int currentNumCols = ColumnDefinitions.Count;

    while (numCols > currentNumCols)
    {
        ColumnDefinitions.Add(new ColumnDefinition{ Width = new GridLength(1, GridUnitType.Star) });
        currentNumCols++;
    }

    while (numCols < currentNumCols)
    {
        currentNumCols--;
        ColumnDefinitions.RemoveAt(currentNumCols);
    }

}

Here we read out the number of rows we want and the number of rows we have. If we need more rows we add them to the RowDefinitions collection and set their heights to *. The * means that they should scale automatically. If we have too many rows we remove them starting with the last one.

After setting up the rows we setup the columns the same way.

That is all there is to it!

So this code:

<Window x:Class="DynamicGridDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:grid="clr-namespace:DynamicGridDemo"
        Title="MainWindow" Height="350" Width="525">
    <grid:DynamicGrid NumColumns="2" NumRows="2">
        <Button Grid.Column="0" Grid.Row="0" Content="At 0,0" />
        <Button Grid.Column="0" Grid.Row="1" Content="At 0,1" />
        <Button Grid.Column="1" Grid.Row="0" Content="At 1,0" />
        <Button Grid.Column="1" Grid.Row="1" Content="At 1,1" />
    </grid:DynamicGrid>
</Window>

Gives you this result:

Let’s add a header row:

<Window x:Class="DynamicGridDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:grid="clr-namespace:DynamicGridDemo"
        Title="MainWindow" Height="350" Width="525">
    <grid:DynamicGrid NumColumns="2" NumRows="3">
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Header label here at 0,0 and 1,0" FontSize="20" Background="Yellow" />
        <Button Grid.Column="0" Grid.Row="1" Content="At 0,1" />
        <Button Grid.Column="0" Grid.Row="2" Content="At 0,2" />
        <Button Grid.Column="1" Grid.Row="1" Content="At 1,1" />
        <Button Grid.Column="1" Grid.Row="2" Content="At 1,2" />
    </grid:DynamicGrid>
</Window>

Gives this result:

And it even plays nice together with the old syntax. This is useful if you need a header row with a fixed size for example:

<Window x:Class="DynamicGridDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:grid="clr-namespace:DynamicGridDemo"
        Title="MainWindow" Height="350" Width="525">
    <grid:DynamicGrid NumColumns="2" NumRows="3">
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Content="Header label here at 0,0 and 1,0" FontSize="20" Background="Yellow" />
        <Button Grid.Column="0" Grid.Row="1" Content="At 0,1" />
        <Button Grid.Column="0" Grid.Row="2" Content="At 0,2" />
        <Button Grid.Column="1" Grid.Row="1" Content="At 1,1" />
        <Button Grid.Column="1" Grid.Row="2" Content="At 1,2" />
    </grid:DynamicGrid>
</Window>

This gives the following output (note the size of the first row):

I haven't done much testing of the code, so it may include some nasty bugs, but I think it may be useful. Either if you are prototyping an application or if you need a grid that sizes to your content.

Download the source code here: DynamicGridDemo.zip (77.44 kb) (Visual Studio 2010 b2, WPF4 required)

kick it on DotNetKicks.com

.NET, WPF ,

Comments

06.11.2009 12:10:47 #
A simpler (and dynamic) Grid control for WPF

You've been kicked (a good thing) - Trackback from DotNetKicks.com
28.05.2010 06:40:46 #
Thanks a bunch for this great suggestion. I would make one suggestion, though. You may want to add a PropertyChangedCallback to the dependency properties. That way, if one of the values changes, you can re-render the grid.
I ran into this problem while binding the NumRows property inside of an ItemsControl. Here's my addition:

public static readonly DependencyProperty NumRowsProperty =
  DependencyProperty.Register("NumRows", typeof(int), typeof(FormSectionGrid),
  new PropertyMetadata(0, new PropertyChangedCallback(OnNumRowsOrColumnsChanged)));

public static readonly DependencyProperty NumColumnsProperty =
  DependencyProperty.Register("NumColumns", typeof(int), typeof(FormSectionGrid),
  new PropertyMetadata(0, new PropertyChangedCallback(OnNumRowsOrColumnsChanged)));

static void OnNumRowsOrColumnsChanged(object sender, DependencyPropertyChangedEventArgs e)
{
  ((FormSectionGrid)sender).RecreateGridCells();
}
28.05.2010 23:59:21 #
@Jason. Thanks for your suggestion!
I will update the post with your code for this is really useful. Smile
Scott
Scott
27.09.2010 17:28:05 #
Cool control, thanks. Besides the binding of the control to number of columns and number of rows, how might one go binding your control to an irregular shaped array of data, i.e a 4x7 array?  I am not sure exactly how to bind my source to your control to display correctly. Thanks for your help!
27.09.2010 20:52:01 #
@Scott: Both the NumColumns and the NumRows properties are dependency properties. This means that you can bind them to whatever (integer) value you want.

So you can set up the grid with NumColumns=4" and NumRows="7".

Further, you can also bind the number of columns and rows to values in your viewmodel, but these must also be integers. If you want to bind the call content to an array you need to bind it to a list of objects that have values for the Grid.Row and Grid.Column properties as well as for the content.

I do something like that in this article: blog.rag.no/.../...ng-of-list-elements-in-WPF.aspx
Andreas Karg
Andreas Karg
30.12.2012 20:30:02 #
Hi Rune,

I've got a license-y question. Your dynamic grid appears to be just what I need right now.

I'm working on a little project that is going to be published under the LGPL. I could not find any license terms for your code, so I'm going to ask you here:
Do I have the permission to use your DynamicGrid class and publish it under the LGPL license together with my own code? (I'll put in a reference to your blog in the code).

The people that will (hopefully) use my application take copyright issues rather seriously and 'stealing' code/graphics/whatever is kinda frowned upon.

Thanks in advance!

Kind regards
Andi
30.12.2012 22:22:10 #
Hi.
Use my code for whatever you want! Smile And that goes for everybody else as well.


Rune
Ben
Ben
14.03.2013 15:28:42 #
That's an interesting control to make defining a grid a bit quicker but have you tried dynamically adding items to the rows and columns using data binding?

I'm guessing you'd have to use your grid as the ItemsPanel in a ListBox.

Add comment




  Country flag

The number (5) not with letters. This is to stop spammers.

biuquote
  • Comment
  • Preview
Loading