Skip to main content

Binding to Hierarchical Data Structure

  • 3 minutes to read

A hierarchical data structure is any set of nested business objects that have a structure where the children of a node are in a children field. Parents and children can be different object types. An example of such a structure is shown below.


public class ProjectObject : BaseObject {
    public ObservableCollection<ProjectStage> Stages { get; set; }
}

public class ProjectStage : BaseObject {
    public ObservableCollection<Task> Tasks { get; set; }
}

public class Task : BaseObject {
    State state;
    // ...
}

When working with hierarchical data structures, the control's DataControlBase.ItemsSource property contains only data items that correspond to root nodes. In the example above, these are ProjectObject items. To display the entire hierarchy, you can use one of the two approaches provided by the TreeListView.

#Using Hierarchical Data Templates

With this approach, you can take advantage of hierarchical data templates.


<UserControl.Resources>
    <ResourceDictionary>
        <local:CustomHierarchicalDataTemplateSelector x:Key="selector">
            <local:CustomHierarchicalDataTemplateSelector.ProjectDataTemplate>
                <sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Stages}" />
            </local:CustomHierarchicalDataTemplateSelector.ProjectDataTemplate>
            <local:CustomHierarchicalDataTemplateSelector.ProjectStageDataTemplate>
                <sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Tasks}" />
            </local:CustomHierarchicalDataTemplateSelector.ProjectStageDataTemplate>
        </local:CustomHierarchicalDataTemplateSelector>
    </ResourceDictionary>
</UserControl.Resources>

Create a template selector that implements System.Windows.Controls.DataTemplateSelector and overrides the SelectTemplate method.


public class CustomHierarchicalDataTemplateSelector : DataTemplateSelector {

    public HierarchicalDataTemplate ProjectDataTemplate { get; set; }
    public HierarchicalDataTemplate ProjectStageDataTemplate { get; set; }

    public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) {       
        if (item is ProjectObject)
            return ProjectDataTemplate;
        if (item is ProjectStage)
            return ProjectStageDataTemplate;
        return null;
    }
}

Assign the template selector to the TreeListView's DataRowTemplateSelector property and switch to the HierarchicalDataTemplate tree derivation mode using the TreeListView.TreeDerivationMode property.


<dxg:TreeListControl.View>
    <dxg:TreeListView DataRowTemplateSelector="{StaticResource selector}"
                        TreeDerivationMode="HierarchicalDataTemplate" />
</dxg:TreeListControl.View>
NOTE

If all business objects have the same children field/property, create a single template and assign it to the DataRowTemplate property.

NOTE

First, the TreeListView verifies its DataRowTemplateSelector property. If the specified node selector returns null or the DataRowTemplateSelector property is not specified, a template specified by the DataRowTemplate property is used. If this property is not specified, an implicit hierarchical data template is used.

Once the required HierarchicalDataTemplate is defined for a node, its ItemTemplate and ItemTemplateSelector properties take priority over the corresponding properties specified by the TreeListView (DataRowTemplate and DataRowTemplateSelector).

Example: How to: Build a Tree via HierarchicalDataTemplate.

#Building a Tree in 'Code Behind'

Using this approach you should manually write code to specify where a data object's child items come from. Create a selector class that implements DevExpress.Xpf.Grid.IChildNodeSelector. Override the SelectChildren method to return the list of child nodes for the specified node.

For the Project-Stage-Task business class structure, a selector class will be as follows.


public class CustomChildrenSelector : IChildNodesSelector {
    public IEnumerable SelectChildren(object item) {
        if (item is ProjectStage)
            return ((ProjectStage)item).Tasks;
        else if (item is ProjectObject)
            return ((ProjectObject)item).Stages;
        return null;
    }
}

Finally, you should assign the Child Nodes Selector to the TreeListView's TreeListView.ChildNodesSelector property and set the TreeListView.TreeDerivationMode property to 'ChildNodesSelector'.


<dxg:TreeListControl x:Name="treeList"
                        ItemsSource="{Binding DataItems}"
                        DataContext="{StaticResource viewModel}">
    <dxg:TreeListControl.Resources>
        <local:CustomChildrenSelector x:Key="childrenSelector"/>
    </dxg:TreeListControl.Resources>
    <dxg:TreeListControl.Columns>
        <dxg:TreeListColumn FieldName="Name" />
        <dxg:TreeListColumn FieldName="Executor" />
        <dxg:TreeListColumn FieldName="State" />
    </dxg:TreeListControl.Columns>
    <dxg:TreeListControl.View>
        <dxg:TreeListView x:Name="view" TreeDerivationMode="ChildNodesSelector"
                            ChildNodesSelector="{StaticResource childrenSelector}"/>
    </dxg:TreeListControl.View>
</dxg:TreeListControl>
NOTE

If all business objects have the same 'children' field, assign its name to the TreeListView's TreeListView.ChildNodesPath property. Otherwise, create a Child Nodes Selector.

#Limitations

When a Hierarchical binding mode is used, it is not possible to sort/group and filter by a field value(s) because data types in different nesting levels may be different. Checking the type for every object greatly decreases the grid's performance. Data sorting, grouping and filtering is performed based on the text displayed within data cells.