Thursday, June 20, 2013

Create a Hierarchical DataGrid in WPF (MVVM Capable)

There are Many Occasions when I wanted to have the ability to have a hierarchical DataGrid for simple recursive tables.

Tree Views did not come up to scratch because they are not easy to two way bind to data and fail on being able to send notifications of changes.

With a lot of gritting teeth I managed to do it in XAML Alone (YAY).

I achieved this by creating an HierarchicalDataTemplate  which refers to itself via DynamicResource.   the IDE does give me a warning that a loop is detected but, other than that it works fine.

So what does it look Like?


With time it will be prettied up :-).

<UserControl.Resources>
 <ResourceDictionary>
   <HierarchicalDataTemplate  x:Key="HeiachyTemplate"  
                                       DataType="{x:Type data:InventoryItemGroup}"
                                       ItemsSource="{Binding InventoryItemGroups}" >
    <DataGrid     
ItemsSource="{Binding InventoryItemGroups,Mode=TwoWay,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True,NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}"   
bhv:DataGridEndEditCommandBehaviour.Command="{Binding ElementName=EditInventoryItemGroupUserControl,Path=DataContext.DataGridEndEditCommand}"
CanUserAddRows="True" AutoGenerateColumns="False" AreRowDetailsFrozen="False"
HorizontalAlignment="Stretch" VerticalContentAlignment="Top"
RowDetailsVisibilityMode="Visible" AlternatingRowBackground="WhiteSmoke" 
HeadersVisibility="Row"
RowDetailsTemplate="{DynamicResource HeiachyTemplate}"  >
  <DataGrid.RowHeaderTemplate>
    <DataTemplate>
      <ContentControl VerticalContentAlignment="Top" VerticalAlignment="Top">
        <TextBlock  FontFamily="Wingdings" Text="&#196;" Margin="15,0,0,0" VerticalAlignment="Top" />
      </ContentControl>
    </DataTemplate>
  </DataGrid.RowHeaderTemplate>
  <DataGrid.Columns>
      <DataGridTextColumn Header="Name" Binding="{Binding InventoryItemGroupName, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}" Width="*" MinWidth="400"></DataGridTextColumn>
   </DataGrid.Columns>
  </DataGrid>
          
</HierarchicalDataTemplate>
        </ResourceDictionary>
    </UserControl.Resources>

Now for the data grid

   <DataGrid 
  ItemsSource="{Binding InventoryItemGroupList,Mode=TwoWay,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True,NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}"   
 CanUserAddRows="True" AutoGenerateColumns="False" AreRowDetailsFrozen="False"
 RowDetailsVisibilityMode="Visible" AlternatingRowBackground="LightGray"
 RowDetailsTemplate="{StaticResource HeiachyTemplate}"
 >
 <DataGrid.RowHeaderTemplate>
     <DataTemplate>
        <TextBlock  Text=" " Margin="15,0,0,0" VerticalAlignment="Top" />
     </DataTemplate>
 </DataGrid.RowHeaderTemplate>
 <DataGrid.Columns>
   <DataGridTextColumn Header="Name" Binding="{Binding InventoryItemGroupName, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}" Width="*" ></DataGridTextColumn>
  </DataGrid.Columns>
</DataGrid>


Now That's All she Wrote.... I cannot say if this will work for all situations nor large record sets but it worked for me.. This is my first post, so please if I am missing any information leave a comment. Source Code                  

9 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hello!

    I am very keen to implement your approach of presenting a recursive table in a WPF hierarchical DataGrid.

    I have a simple recursive table which has a child column and a parent column that store countries, counties, cities, streets, etc... I would like to display the entities and their attributes in an editable hierarchical DataGrid. It seems your approach can handle this better than other articles I have read on the web.

    Could you please expand on the following items and how they are constructed in the example you have used? I would really appreciated your assistance. Many thanks.

    I have created CountryItemGroupList as a viewmodel of the table above. Now I need to know how to generate the corresponding components in the following entries of XAML:

    1)

    2) What is the reference entry for bhv? and how are the rest of the components in bhv:DataGridEndEditCommandBehaviour.Command="{Binding ElementName=EditInventoryItemGroupUserControl, Path=DataContext.DataGridEndEditCommand}" are built?

    3) <DataGridTextColumn Header="Name" Width="*" Binding="{Binding InventoryItemGroupName

    Any possibility of a more detailed example?

    Many thanks in advance.

    Abbas

    ReplyDelete
    Replies
    1. Sorry! Somehow Item (1) did not get displayed:

      1) HierarchicalDataTemplate x:Key="HeiachyTemplate" DataType="{x:Type data:InventoryItemGroup}" ItemsSource="{Binding InventoryItemGroups}"

      Delete
  3. Hi Abbas,

    1 ) {x:Type data:InventoryItemGroup.. data is the namespace of my data objects in the Usercontrol (or Window ) definition you need to add xmlns:data ="NameOfYourDataNamespace"

    This defines what type of data the template is to be used for. in your case it may be x:Type = GountryGroup

    2) the bhv is the namespace to a custom behaviour for data grids, in this case it raises an event to notify other MVVM forms that there has been a change in the data, this is optional.

    3) Width="*' makes the width take up the whole grid, the Binding you will use the property that you wish to display e.g. CountryGroupName

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi Traci,

    Many thanks for your prompt response; unfortunately I got side-tracked to other things.

    First of all it was very silly to raise question (3); it was so trivial and I don’t know why I raised it!! (Perhaps I was excited seeing your article after a long search on the web.) I have been developing applications with DataGrid for some time (not that I am an expert though!)

    Regarding the bhv, I would be like to learn more about it as I need a method of communications between MVVM forms. Could you please point me to a right direction to read more about it? Thanks.

    With regard to the main point of interest, I have the following model and would like to create a hierarchical DataGrid, similar to Microsoft Project:

    1) Reference table: tblMaster, with a primary key ItemID:

    ItemID ItemName
    1 Countries
    2 AllCountries
    3 Continents
    5 Africa
    6 America
    7 Oceania
    8 Europe
    31 Australia
    74 France
    85 UnitedKingdom
    167 Switzerland
    179 UAE
    388 London
    396 Paris
    398 New York
    399 Melbourne
    400 Birmingham
    401 Sydney
    402 Geneva

    2) Recursive (tree) table: tblMasterTree, with foreign keys FK_NodeID and FK_ParentID that map to ItemID of tblMaster:

    TreeNodeID FK_NodeID NodeName FK_ParentID ParentName
    1 1 World NULL NULL
    2 3 Continents 1 Countries
    3 6 America 3 Continents
    4 7 Oceania 3 Continents
    5 8 Europe 3 Continents
    6 31 Australia 7 Oceania
    7 31 Australia 2 AllCountries
    8 74 France 8 Europe
    9 74 France 2 AllCountries
    10 85 UnitedKingdom 8 Europe
    11 85 UnitedKingdom 2 AllCountries
    12 167 Switzerland 8 Europe
    13 167 Switzerland 2 AllCountries
    14 187 USA 6 America
    15 187 USA 2 AllCountries
    16 388 London 85 UnitedKingdom
    17 396 Paris 74 France
    18 398 New York 187 USA
    19 399 Melbourne 31 Australia
    20 400 Birmingham 85 UnitedKingdom
    21 401 Sydney 31 Australia
    22 402 Geneva 167 Switzerland

    As you will note a child can have more than one parent.

    I want to create a hierarchical DataGrid base on the tblMasterTree. I have created a MasterTreeViewModel which is also set as the DataContext of the DataGrid. The view model defines EntityCollection as a collection of tblMasterTree rows, which resembles your InventoryItemGroupList in the DataGrid ItemsSource.

    I have a reference to my ViewMode namespace in my xaml (vm) and all my entities are defined in MasterTreeViewModel.

    Now, how do I create CountryGroup from my tables, which resembles your InventoryItemGroup? Will this be grouping in the database as a result of creating a view? In which case how can I make my DataGrid editable? I would like to be able to add/remove rows, add/remove child and modify cells.

    Also, can you please advise on implementing expanding and collapsing rows.

    Please note that I have already created a TreeListView based tblMasterTree; but it is read only.

    I hope I have described my requirement clearly; please let me know if there are any ambiguities or you need more details.

    Again, many thanks for taking time to read and respond to me query.
    Abbas

    ReplyDelete
  6. Great example. I was looking for something like this for a long time. Could you please provide the sources as zip-archive?

    ReplyDelete
  7. I have created a sample and can be accessed from the link in the article

    ReplyDelete
    Replies
    1. Hi,

      Can't get the source code; on Chrome I get a blank page and on IE I get the "HTTP 403 Forbidden" error.

      I have used my Google account to sign in.

      Thanks for your help.

      Delete