3

I would like to display a multi-level list of objects in WPF. I have a list of grandparents, each of those grandparents contains a list of parent, and each parent contains a list of sons. I would like to display all of this in a DataGrid like that, in MVVM:

Example

I've tried to bind my list of grandparent to a DataGrid, and to set the template of the childrens with an other DataGrid.

Here is my xaml:

<DataGrid Grid.Row="1" ItemsSource="{Binding CollectionGrandParents}"
          AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding label}" />
        <DataGridTemplateColumn CellTemplate="{StaticResource CellParentTemplate}"/>
    </DataGrid.Columns>
</DataGrid>

<!-- Data Templates -->

<DataTemplate x:Key="CellParentTemplate">
    <DataGrid ItemsSource="{Binding .parents}"
     AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding label}" />
            <DataGridTemplateColumn CellTemplate="{DynamicResource CellSonTemplate}"/>
        </DataGrid.Columns>    
    </DataGrid>
</DataTemplate>

<DataTemplate x:Key="CellSonTemplate">
<DataGrid ItemsSource="{Binding .sons}"
 AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False" CanUserDeleteRows="False">        
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding label}"/>
    </DataGrid.Columns>
</DataGrid>

And the result:

enter image description here

My problem is that the parents and sons are not in the same respective column, they are in another DataGrid. I would like to display all of my grandparents, parents and sons in the same DataGrid (or maybe a should use another element than DataGrid, but I don't know which), because I want to adjust correctly the columns width.

Ben
  • 3,972
  • 8
  • 43
  • 82
  • What do you mean? I made ​​a mistake of language? Sorry, english isn't my mother tongue. :/ – Ben Sep 11 '13 at 20:14
  • Is omitting `DataGrid.Columns` a typo? I mean it's grandparent,parent,**child**. – UIlrvnd Sep 11 '13 at 20:14
  • Ho sorry this is not a typo, I actually forgot the DataGrid.Columns. Thank you. This part is solved now, but the other problem is still relevant. – Ben Sep 11 '13 at 20:19
  • "I would like to display all of my grandparents, parents and sons [*sic*] in the same DataGrid..." How would that look like? A datagrid is meant to represent tabular data i.e. rows and columns... Seems you need something with hierarchy. Also, I don't see what adjusting column width has to do with anything, just bind it? – UIlrvnd Sep 11 '13 at 20:21
  • Yes I think I have to use something else than a DataGrid. But I don't know what element can do what I want. – Ben Sep 11 '13 at 20:23
  • Would [`TreeView`](http://msdn.microsoft.com/en-us/library/system.windows.controls.treeview.aspx) suffice? – UIlrvnd Sep 11 '13 at 20:24
  • But how can I use a TreeView horizontally? Can I have exactly the same result as my first screenshot using a TreeView? – Ben Sep 11 '13 at 20:28
  • That would be too long to explain in a comment, [check this out](http://www.codeproject.com/Articles/17025/Custom-TreeView-Layout-in-WPF) to get you started. Yes. – UIlrvnd Sep 11 '13 at 20:35

1 Answers1

3

Really close to what you requested:

enter image description here

Obtained with the following XAML, which contains a slight modification of the TreeViewItem Default Template

<Window x:Class="MiscSamples.HorizontalTreeView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="HorizontalTreeView" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="TreeViewItemFocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Modified TreeViewItem Style -->
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Padding" Value="1,0,0,0"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <!-- Modified TreeViewItem Template -->
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>

                            <Border x:Name="Bd" 
                                    BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" 
                                    Background="{TemplateBinding Background}" 
                                    Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                                <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </Border>
                            <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="2" Grid.Row="0"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="true">
                                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsSelected" Value="true"/>
                                    <Condition Property="IsSelectionActive" Value="false"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <!-- Sample TreeView -->
    <Grid>
        <TreeView ItemsSource="{Binding}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding DisplayName}"
                               Margin="5"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>
</Window>

Code Behind (just boilerplate to generate random data):

public partial class HorizontalTreeView : Window
{
    public HorizontalTreeView()
    {
        InitializeComponent();

        var data = new List<HierarchicalData>();
        var random = new Random();

        for (int i = 0; i < 5; i++)
        {
            var item = new HierarchicalData() {DisplayName = "Root" + i.ToString()};
            data.Add(item);
            var childcount = random.Next(0, 5);
            for (int j = 0; j < childcount ; j++)
            {
                var child = new HierarchicalData() {DisplayName = "Child" + i.ToString() + j.ToString()};
                item.Children.Add(child);

                var grandchildcount = random.Next(0, 5);
                for (int k = 0; k < grandchildcount; k++)
                {
                    var grandchild = new HierarchicalData() { DisplayName = "GrandChild" + i.ToString() + j.ToString() + k.ToString() };
                    child.Children.Add(grandchild);
                }
            }

        }

        DataContext = data;
    }
}

Data Item:

public class HierarchicalData
{
    private List<HierarchicalData> _children;
    public string DisplayName { get; set; }

    public List<HierarchicalData> Children
    {
        get { return _children ?? (_children = new List<HierarchicalData>()); }
        set { _children = value; }
    }
}

WPF Rocks \m/

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • That's exactly what I wanted, thanks! How can I do now to align all the "colums" ? – Ben Sep 11 '13 at 21:11
  • @Ben What do you mean by align all the columns? – Federico Berasategui Sep 11 '13 at 21:12
  • Try to change the name of only one of your child by a longer one in your example. The cell width will be longer than the others. How can we adjust the other cells width to the larger one? – Ben Sep 11 '13 at 21:15
  • @Ben Ok, let me try that. – Federico Berasategui Sep 11 '13 at 21:16
  • Thank you. That's why I talked about something which is like a grid, because I would like to align and adjust all the cells, and I would like to add headers on the top of each "colums". – Ben Sep 11 '13 at 21:30
  • And in a DataGrid we can have a text wrapping depending on the "column" width. I know I'm asking a lot but it would be very nice for me if I could have those requirments. – Ben Sep 11 '13 at 22:23
  • Another question is, is there a way to set a different template for each level? For exemple, I would like to set a background color for Roots elements, another for Childs elements, and a border around the GrandChils elements. – Ben Sep 12 '13 at 08:36
  • Already told you @Ben, just bind width... And yes, you can achieve all of your requirements, just research wpf a bit. Fyi: you can use `Grid` for this but I doubt it's going to be any easier... If you're expecting to get a complete solution, tailored to your exact specifications here, I think you're in the wrong place... – UIlrvnd Sep 12 '13 at 13:40
  • Ok I will make more researches. – Ben Sep 12 '13 at 13:57