WPF DataGrid: Dissecting the Visual Layout


I’m going to be dissecting and discussing the DataGrid visuals and how they are all assembled together to form the overall DataGrid. Note: This is really more of a post for people who want to understand how the DataGrid works internally. I’m not going to be going over how to use some of the APIs in this post.

If you haven’t already, get the binaries and source for it here.  For more related material take a look at these posts:

DataGrid Intro 

Stock and Template Columns

Working with DataGridComboBoxColumn (Part1)


Working with DataGridComboBoxColumn (Part2)


Understanding the visual tree isn’t the most important thing to know about basic DataGrid usage, but it is very helpful for such things like customizing its appearance through the different styling templates available, replacing the visual tree altogether, accessing visual properties, understanding routing events, or when you want to learn how the data maps to the visual tree. Here is the big picture view of the DataGrid:

DataGridVisual

You can view the equivalent in Generic.xaml in the source for DataGrid. The DataGrid, like a generic spreadsheet, is made of rows and cells. The rows are generated from the DataGrid’s GetContainerForItemOverride. That should sound familiar as DataGrid ultimately derives from ItemsControl. If you need a brush up on that, please check out Dr. WPF’s awesome series on ItemsControl. The cells are generated from DataGridCellsPresenter’s GetContainerForItemOverride. The important containers are DataGridRowsPresenter and DataGridCellsPanel. DataGridRowsPresenter derives from VSP and is a really small class that does some clean up on the ItemsHost and some scrolling work. If you plan to replace this container, be sure to handle scrolling. The DataGridCellsPanel on the other hand is a very specific implementation to the default DataGrid which handles sizing of each DataGridCell. Replacing this container will need a good understanding of the DataGrid internals which I will not go into here.

In my previous post I did a really brief intro of the DataGrid and just got it up and running. Well, now I want to break down how it actually works. Whether columns are auto-generated or built manually, the DataGrid will use those columns to map the data source to each cell. Notice that the list of DataGridColumns is not in the diagram as it is not really a visual. DataGridColumn is used as the glue between the data and the DataGridCells. So given this column implementation as an example,

<dg:DataGrid AutoGenerateColumns=”False”>

  <dg:DataGrid.Columns>


    <dg:DataGridTextColumn Binding=”{Binding Path=FirstName}” />


    <dg:DataGridTextColumn Binding=”{Binding Path=LastName}” />


    <dg:DataGridCheckBoxColumn Binding=”{Binding Path=LikesCake}”/>


    <dg:DataGridHyperlinkColumn Binding=”{Binding Path=Homepage}” />


  </dg:DataGrid.Columns>


</dg:DataGrid>


here is the sequence that follows in creating the DataGrid when the ItemsSource is set (assuming all the Path values are properties on the data source),

1. When ItemsSource is set on the DataGrid, PrepareContainerForItemOverride is called on each item of the data source collection which prepares a DataGridRow for each.

2. When a DataGridRow is prepared, it passes the item to its DataGridCellsPresenter which internally creates a copied collection of that item and sets its ItemsSource to that collection.

3. When DataGridCellsPresenter.ItemsSource is set, PrepareContainerForItemOverride is called on each copied item which prepares a DataGridCell for each.

4. When a DataGridCell is prepared (which is a ContentControl), it gets its corresponding column from the DataGrid and asks that column to generate the visual tree for DataGridCell’s Content property.

It is in the generation of the visual tree where the binding is hooked up from the data source to the UI. Recall in the column implementation, the Binding property was set to a property on the data source. In the visual tree generation it takes that Binding and applies it to the generated UIElement (which in step 4 is set to DataGridCell’s Content property).  That is basically it. You should also know that different column types will generate different UIElements. DataGridTextColumn will generate a textbox; DataGridCheckBoxColumn will generate a checkbox, etc. So hopefully some of the magic has been revealed on how the DataGrid populated itself.

In addition to understanding the mapping of the data to the UI, you should have a good grasp of the visuals involved from looking at the diagram. This helps when you want to style the DataGrid. Convenience properties for styling have also been added the DataGrid class itself so you can look there for the available styles as well, but let’s stop here for now as I will get more into styling and templating in an upcoming post.

Comments (28)

  1. Link Listing – August 14, 2008

  2. Rudi Grobler says:

    Great stuff Vincent…

    Keep it up!!!

    Regards,

    Rudi Grobler

  3. Overview The DataGrid uses a set of DataGridColumns to describe how to display its data just like a GridView

  4. Danny says:

    What I am really struggling to figure out is how to do some simple calculations that update one cell based on the input of another:

    eg:

    price     qty   total

    100        2     (calculates to 200)

    I understand that it has to be done in the CommitingChanged event, but how do I go about finding the cell I need to update?

  5. You could leave the calculation to your data object.  Then you won’t have to worry about the logic in the UI.  So for exmaple you could have a class like this,

    class Product

    {

    public double Price {get; set;}

    public double Quantity {get; set;}

    public double Total

    {

     get

     {

       return Price * Quantity;

     }

    }

    }

    If you want to do it in the UI though, you can listen to the CommittingEdit event where you will have access to the row being committed.  With that you have row.Item which is your data item and you can just set the Total there if you wanted and the cell will update.

  6. There have been several questions on the WPF CodePlex discussion list relating to styling rows and columns

  7. Introduction I’m going to talk a little on the editing features of the DataGrid. I will dive deep into

  8. Keoz says:

    Hi where is the DataGridCellsPresenter in release of WPF toolkit? :S

  9. Keoz,

    It is under the namespace, Microsoft.Windows.Controls.Primitives.

  10. Kevin says:

    Hi, First off I have to say fantastic work Vincent.

    Anyway I have run into a little problem. I have styled my DataGridCheckBoxColumn (See Below). This works fine except the dataGrids_BeginningEdit event is not being fired. It seems the Combobox is swallowing the event.  Any ideas what I need to do to get the event to fire?

    Tks

    Kevin

    <tk:DataGridCheckBoxColumn Binding="{Binding Path=Selected}" Header=" Action " CanUserResize="False" ElementStyle="{StaticResource DataCheckBoxGridCellStyle}"  />

    <Style x:Key="DataCheckBoxGridCellStyle" TargetType="{x:Type CheckBox}">

           <Setter Property="Foreground" Value="White"/>

           <Setter Property="HorizontalAlignment" Value="Center"/>

           <Setter Property="Template">

               <Setter.Value>

                   <ControlTemplate TargetType="CheckBox">

                       <BulletDecorator Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Left">

                           <BulletDecorator.Bullet>

                               <Border x:Name="Border"  

                                     Width="13"

                                     Height="13"

                                     CornerRadius="0"

                                     Background="White"

                                     BorderThickness="1"

                                     BorderBrush="Black">

                                   <Path

                                       Width="7" Height="7"

                                       x:Name="CheckMark"

                                       SnapsToDevicePixels="False"

                                       Stroke="Green"

                                       StrokeThickness="2"

                                       Data="M 0 0 L 7 7 M 0 7 L 7 0" />

                               </Border>

                           </BulletDecorator.Bullet>

                       </BulletDecorator>

    <!–Ihave excluded the triggers–>

                   </ControlTemplate>

               </Setter.Value>

           </Setter>

       </Style>

  11. Kevin,

    It’s hard to say from just that code that you provided.  So it does fires for all the other columns?

  12. Kevin says:

    Yes, it fires for all the other columns that are not readonly.

    there is a spot, just below the checkbox (I know i am not over the check box because a trigger changes its border colour on IsMouseOver) where if i click there the BeginningEdit is called, and i assume via my binding, the styled check box is Checked and Unchecked. (Sorry, i noticed i mentioned ComboBox in my above post, Its a checkbox we are dealing with).

    Kev

  13. Kevin,

    Could you send me a repro app.  You can first contact me through the automated email through this blog and I can contact you from there.

    Thanks

  14. Avijit says:

    Great post Vincent.I am facing a problem.I am using the datagrid with a checkbox column. Now I have to select the checkbox on the basis of a recordset column value(i.e. if the value for one row is ‘Y’ then the checkbox should be checked andd if it is ‘N’ then it should remain unchecked). Could you please help me out.

  15. Avijit,

    You could use a converter for the binding in your DataGridCheckBoxColumn.  For example,

    <DataGridCheckBoxColumn Binding="{Binding Path=SomeRecordColumn, Converter={StaticResource RecordColumnConverter}}" />

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

           {

               var strVal = (string)value;

               if (strVal == "Y")

                   return true;

               else

                   return false;

           }

    Of course, this Convert method is a very simplistic version.  Make the necessary robust changes that you need.

  16. coolduds says:

    I need to access textblock controls defined in a column header template for a datagrid.  Any suggestions on how I can do this?

    thanks much.

  17. Brian says:

    Because all of this DataGrid stuff is fairly new, I’m having a hard time figuring out what is possible versus impossible. I have a need to be able to load a bunch of templates at runtime from a file. The problem is that I don’t know the names of the columns beforehand. My interim solution is to have some wildcard string in the file that I replace with the current column name before calling XamlReader.Load() on the read in xaml, so I get a customized template on demand.

    What I want to know is if there is some way to do the binding in the inner controls of the DataTemplate for the DataGridTemplateColumn so that it somehow pulls the current column header (and thus the column name from the dataset) from somewhere. I am setting the header name in code once the template object is constructed. I guess I would just need to bind the inner controls to the header on the template. Any ideas?

  18. Brian,

    If your DataGridTemplateColumn does not requre a very flexible DataTemplate that dynamically changes all the time, you can consider using a solution similar to what I did in the sample on this post, http://blogs.msdn.com/vinsibal/archive/2008/10/22/wpf-datagrid-and-the-wpftoolkit-have-released.aspx.  Take a look at the DataGridCustomTemplateColumn class.  In that class I added a Binding DependencyProperty so you can set the binding directly on the column.  In the GenerateElement and GenerateEditingElement you can see I walk the visual tree and find the inner control that I want to set the binding to.  Hope that can give you some ideas.

  19. sreeraj says:

    Hi Vinsibal,

    Hope you are doing fine.

    Am fed up with an issue in wpf datagrid . I have a data grid which is having check boxes in first column . Also i have a checkbox outside the Datagrid . When am cheking the checkbox outside the datagrid, i want all the checkboxes inside the grid . I tried implementing this in the following way(Code attached along with this) , but it is showing strange behaviour . Some checkboxes in rows randomly getting checked(if some more clearly says first 10 rows are getting checked after that say 5 not checked again next 10 getting checked  and this process continues till the end of records). I have tried solvint this issue using the helper class in the demo application you have published, but still it is showing same behaviour.  I dont know why this happening . Am attaching my code here .

    Please suggest a solution

    Regards

    sreeraj

    ———–XAML For Grid——————

    <dg:DataGrid Grid.Row="1" AutoGenerateColumns="False" AlternatingRowBackground="SkyBlue" RowDetailsVisibilityMode="Collapsed"

                        AllowDrop="False" HeadersVisibility="All"

                        ColumnHeaderHeight="30" FontFamily="Verdana" FontSize="12"    Background="#FFFFFFFF"

                        OpacityMask="#FFF1E3E3" Foreground="#FF000000"

                        BorderThickness="1" SnapsToDevicePixels="False" CanUserDeleteRows="False" CanUserAddRows="False"

                        ClipToBounds="False" RowHeight="25"

                         ScrollViewer.CanContentScroll="True" Padding="0"

                        VerticalContentAlignment="Center" MinWidth="0"  HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Visible" GridLinesVisibility="All" Name="grdWorkOrders" Margin="0,0,0,52" Height="150" VerticalAlignment="Top" EnableColumnVirtualization="False">

                           <dg:DataGrid.Columns>

                               <dg:DataGridTemplateColumn  MinWidth="30">

                                   <dg:DataGridTemplateColumn.CellTemplate>

                                       <DataTemplate>

                                           <CheckBox x:Name="chkMain"></CheckBox>

                                       </DataTemplate>

                                   </dg:DataGridTemplateColumn.CellTemplate>

                               </dg:DataGridTemplateColumn>

                               <!–<dg:DataGridCheckBoxColumn    Header="Access"></dg:DataGridCheckBoxColumn>–>

                               <dg:DataGridTextColumn Binding="{Binding Path=ActivityName}" Header="AIRCRAFT"       FontSize="10" MinWidth="110" />

                               <dg:DataGridTextColumn Binding="{Binding Path=Description}" Header="FLEET"   FontSize="10" MinWidth="40" />

                               <dg:DataGridTextColumn Binding="{Binding Path=Description}" Header="MAINTENANCE FLEET"   FontSize="10" MinWidth="40" />

                               <dg:DataGridCheckBoxColumn  Header="RESPONSABILITY"></dg:DataGridCheckBoxColumn>

                           </dg:DataGrid.Columns>

                       </dg:DataGrid>

    ———–Code Behind ——————–

    for (int nCntr = 0; nCntr < grdWorkOrders.Items.Count; nCntr++)

               {              

                   var cntr = MyDataGrid.ItemContainerGenerator.ContainerFromIndex(nCntr);

                   DataGridRow ObjROw = (DataGridRow)cntr;

                   if (ObjROw == null)

                   {

                       ObjROw = (DataGridRow)MyDataGrid.ItemContainerGenerator.ContainerFromIndex(nCntr);

                   }

                   else

                   {

                       FrameworkElement objElement = MyDataGrid.Columns[0].GetCellContent(ObjROw);

                       if (objElement != null)

                       {

                           if (objElement.GetType().ToString().EndsWith("CheckBox"))

                           {

                               CheckBox objChk = (CheckBox)objElement;

                               objChk.IsChecked = true;

                           }

                       }                  

                   }

               }

  20. Stan says:

    For DataGridCheckBoxColumn, I was hoping you have sth like CheckedValue = "Y", CheckedValue = "*", UncheckedValue = "Whatever", UncheckedValue = "*".

  21. Kerry says:

    Hi Vin,

    thank you for doing all this work. I read your artictle and one thing, that is still missing in the picure to me is the DataGridRowHeader. Since I am developing applications for mathematical use, I am very much interested on being able to build a x/Y axis representation of a grid. Getting to it:

    In the four steps above you descibed how each Column gets its data bound and of course I can imagine how to use templates for that. But where does the rowheader get it’s data from? If I make an Observable Collection, that contains objecs that have a field for the header this field will also be generated as a column. which really is not what I want :-). So how can I distinguish the Itemsource for the coumn from the Source for the rowheader preventing my rowheader also becoming generated as a colum?

    Many regs,

    Kerry

  22. Kerry,

    There are a couple things you can do here.  You can listen to the AutoGeneratingColumn event and cancel the generation of particular columns or just not do auto-generated columns.  Hope that helps.

  23. cheapsaket says:

    I have grouped my data in a datagrid and used a customized expander to hide the toggle button. I have expanded it to the right. The result is shown below.

    <img src"http://i629.photobucket.com/albums/uu13/cheapsaket/datagrid2.png">”>http://i629.photobucket.com/albums/uu13/cheapsaket/datagrid2.png">

    http://i629.photobucket.com/albums/uu13/cheapsaket/datagrid2.png

    My question is how to access/set the value of the column header above group names?

    The layout diagram in the post does not address this area so if you can specify what goes there, it would be helpful.

  24. shailendrasute says:

    Thanks Vinsibal for excellent article series.

    This is what I was looking for.

    I was trying to change grid orientation to verticle using control template/style triggers etc. No success.

    pl. suggest some way to achieve this.

    Thanks again

    Shailendra

  25. Jew says:

    Hi Vin,

    How can I create a multirow column header?

    Something like this:

    | Alphabet | Number|

    |A | B | C | 1 | 2 |

  26. Steve Miller says:

    Vince,

    When I set the DataGrid property for HeadersVisibility to ‘None’ I get the following binding errors during runtime (listed below).  I have a grid that I want no headers shown.  If I set the property to ‘Column’ or ‘Row’ (or anything but ‘None’) the binding errors go away.  Is there a fix or a workaround?  I know the warning is harmless, but I want to fix all errors or warnings that appear in the output window of Visual Studio to be certain the xaml is completely correct.  I think this is a bug.

    Thanks,

    Steve

    ———————–

    System.Windows.Data Error: 39 : BindingExpression path error: ‘IsSelected’ property not found on ‘object’ ”TotalsDataItem’ (HashCode=50423602)’. BindingExpression:Path=IsSelected; DataItem=’TotalsDataItem’ (HashCode=50423602); target element is ‘DataGridRow’ (Name=”); target property is ‘NoTarget’ (type ‘Object’)

    System.Windows.Data Error: 4 : Cannot find source for binding with reference ‘RelativeSource FindAncestor, AncestorType=’Microsoft.Windows.Controls.DataGrid’, AncestorLevel=’1”. BindingExpression:Path=AreRowDetailsFrozen; DataItem=null; target element is ‘DataGridDetailsPresenter’ (Name=”); target property is ‘SelectiveScrollingOrientation’ (type ‘SelectiveScrollingOrientation’)