Auto-sizing the Silverlight DataGrid

This post has been updated to work with the RTW version of the Silverlight 2 DataGrid.  The code examples are not guaranteed to work with previous Beta versions of the DataGrid. Read more about the features that the Silverlight 2 DataGrid has to offer...

Auto-sizing the DataGrid

The Silverlight 2 DataGrid is auto-sized by default.  This means that if you put it in a Canvas or a StackPanel and don't specify a Height or Width, or a MaxHeight or MaxWidth, you will get a DataGrid that is sized to its contents. 

Warning: This can be dangerous however if you are working with a large (1000+ item) collection as the ItemsSource.  Since the DataGrid will grow to the size of its contents, and will create objects for each cell that it can show based on its height, it will literally create thousands of objects to display all of the data.  As you can guess this has app performance implications so you should avoid the combination of an auto-sized DataGrid and a large amount of data to display.  This isn't an issue of course if you specify a Height or MaxHeight for the DataGrid, or put it in a container such as a Grid that provides it with a size.

Since the default size for the DataGrid is auto you won't have write any code to get auto-sizing behavior, but if you do give the DataGrid a height or width, and then want to switch it back to auto-size at runtime, simply set that value to double.NaN which is WPF and Silverlight's way of having a FrameworkElement auto-size itself.

C#

 dataGrid1.Height = double.NaN;

VB

 DataGrid1.Height = Double.NaN

Auto-sizing Columns and Rows

In addition to the DataGrid auto-sizing itself, Columns and Rows are also auto-sized by default.  Like the DataGrid, rows are auto-sized in the same manner by setting the value to double.NaN while columns on the other hand are done slightly differetly.  If you have ever worked with the Grid control, you might have noticed that the Width property of its ColumnDefinitions is not of type double, but rather a struct called GridLength.  The reason for this is that unlike a FrameworkElement's Width or Height properties which only have the possible values of Auto or a numeric value, a ColumnDefinition's Width can be set to a numeric value, Auto, or Star.  DataGridColumn.Width follows a similar pattern where instead of being of type double, it is of type DataGridLength.

The possible values for DataGridLenth are:

  • Auto: This auto-sizes the column or row to grow to the size of the largest header or cell.  It is effectively a combination of the other two auto-size modes.  You might notice as you scroll when a new larger cell is encountered that this value increases.  It will however not decrease once that item is scrolled out of view.
  • SizeToHeader: This auto-sizes the column or row the grow to the size of the header.  This will ignore the contents of the cells when determining its size and will not change unless the size of the header changes.
  • SizeToCells: This auto-sizes the column or row to grow to the size of the largest visible cell.  It will ignore the header cell in this calculation.  You might notice as you scroll when a new larger cell is encountered that this value increases.  It will however not decrease once that item is scrolled out of view.
  • Numeric: Unlike the others this is not an enum value, but rather simply a numeric value such as 100.  This mode will behave the same way that Beta 1 did, however setting it in code behind is slightly different.

Setting these in XAML is straight forward other than that unfortunately just like GridLength, there is no Intellisense for the enum values since a number is a valid entry as well.

XAML

 <data:DataGrid x:Name="dataGrid1" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
        <data:DataGridTextColumn Binding="{Binding}" 
            Width ="Auto"  Header="Auto"/>
        <data:DataGridTextColumn Binding="{Binding}" 
            Width ="SizeToHeader"  Header="Size to Header"/>
        <data:DataGridTextColumn Binding="{Binding}" 
            Width ="SizeToCells"  Header="Size to Cells"/>
        <data:DataGridTextColumn Binding="{Binding}" 
           Width ="50"  Header="Numeric"/>
    </data:DataGrid.Columns>
</data:DataGrid>

If you were to run the code above and give it a data source it would look something like this:

 AutoSizedColumns

As you can see, once you know the possible enum values, setting it in XAML is fairly simple, and the Beta 1 code of Width="100" just works.  When you are working in code behind however things get slightly more tricky.

If you wanted to write code to do the same as above:

C#

 DataGrid dataGrid1 = new DataGrid();
dataGrid1.AutoGenerateColumns = false;

DataGridTextColumn col1 = new DataGridTextColumn();
col1.Binding = new Binding();
col1.Width = DataGridLength.Auto; 
col1.Header = "Auto";
dataGrid1.Columns.Add(col1);
DataGridTextColumn col2 = new DataGridTextColumn();
col2.Binding = new Binding();
col2.Width = DataGridLength.SizeToHeader; 
col2.Header = "Size to Header";
dataGrid1.Columns.Add(col2);
DataGridTextColumn col3 = new DataGridTextColumn();
col3.Binding = new Binding();
col3.Width = DataGridLength.SizeToCells; 
col3.Header = "Size to Cells";
dataGrid1.Columns.Add(col3);
DataGridTextColumn col4 = new DataGridTextColumn();
col4.Binding = new Binding();
col4.Width = new DataGridLength(50); 
col4.Header = "Numeric";
dataGrid1.Columns.Add(col4);

dataGrid1.ItemsSource = "a b c d e f g h i j".Split();
LayoutRoot.Children.Add(dataGrid1);

VB

 Dim dataGrid1 As New DataGrid
dataGrid1.AutoGenerateColumns = False

Dim col1 As New DataGridTextColumn
col1.Binding = New Binding
col1.Width = DataGridLength.Auto
col1.Header = "Auto"
dataGrid1.Columns.Add(col1)
Dim col2 As New DataGridTextColumn
col2.Binding = New Binding
col2.Width = DataGridLength.SizeToHeader
col2.Header = "Size to Header"
dataGrid1.Columns.Add(col2)
Dim col3 = New DataGridTextColumn
col3.Binding = New Binding
col3.Width = DataGridLength.SizeToCells
col3.Header = "Size to Cells"
dataGrid1.Columns.Add(col3)
Dim col4 = New DataGridTextColumn
col4.Binding = New Binding
col4.Width = New DataGridLength(50) 
col4.Header = "Numeric"
dataGrid1.Columns.Add(col4)

dataGrid1.ItemsSource = "a b c d e f g h i j".Split()
LayoutRoot.Children.Add(dataGrid1)

The interesting line above is where the Numeric column's width is set to 50.  There are two ways to do this.  If you are working with doubles, you can create a new GridLength that takes a double in its constructor.  This is the most common and the easiest way to do it.  If however you are working with strings such as a value in a TextBox, you can use the ConvertFrom method on DataGridLengthConverter which takes an object and returns a GridLength.