none
HierarchicalDataTemplate CollectionViewSource Nested Groups

    Question

  • I am after a setup similar to this article http://www.beacosta.com/blog/?p=18#comments by Beatriz Costa but I need to have a nested grouping (i.e. Country -> City) but from a a flat structure is this possible.

    I have tried to use a CollectionViewSource based on another CollectionViewSource but this doesn't work, it doesn't appear like you can add a PropertyGroupDescription as a nest of another PropertyGroupDescription.

    Any help would be greatly appreciated.

    Kind Regards,

    Luke Cloudsdale.
    Tuesday, September 18, 2007 12:48 AM

Answers

  • Hello, you can use another TreeView in the first HierarchicalDataTemplate. Suppose your data source structure is Country->City->Person, each has a Name property. And you have a collection class with a property called PersonList, which has a property called City. And the City class contains a property called Country. Your first CollectionViewSource will look like this:

            <local:People x:Key="People"/>

     

            <CollectionViewSource x:Key="cvs1" Source="{Binding Source={StaticResource People}, Path=PersonList}">

                <CollectionViewSource.GroupDescriptions>

                    <PropertyGroupDescription PropertyName="City.Country.Name"/>

                </CollectionViewSource.GroupDescriptions>

            </CollectionViewSource>

     

            <TreeView ItemsSource="{Binding Source={StaticResource cvs1}, Path=Groups}" ItemTemplate="{StaticResource CountryTemplate}"/>

     

    This will group the people by their countries. Now we’ll need to set the CountryTemplate’s ItemsSource to another CollectionViewSource which source is a list of people who live in that country. Unfortunately, we can’t define the CollectionViewSource in the Window.Resources scope, or it won’t inherit the correct DataContext. One way to solve the problem will be create another TreeView inside the HierarchicalDataTemplate:

            <HierarchicalDataTemplate x:Key="CountryTemplate">

                <TreeView BorderThickness="0">

                    <TreeView.Resources>

                        <CollectionViewSource x:Key="cvs2" Source="{Binding Path=Items}">

                            <CollectionViewSource.GroupDescriptions>

                                <PropertyGroupDescription PropertyName="City.Name"/>

                            </CollectionViewSource.GroupDescriptions>

                        </CollectionViewSource>

                    </TreeView.Resources>

                   <TreeViewItem Header="{Binding Path=Name}" ItemsSource="{Binding Source={StaticResource cvs2}, Path=Groups}" ItemTemplate="{StaticResource CityTemplate}"/>

                </TreeView>

            </HierarchicalDataTemplate>

     

    Thus, cvs2 will inherit the correct DataContext, and group the people in that country further by cities. We can set the TreeView’s BorderThickness to 0, so it won’t draw a Border which we probably don’t want. The remaining code should be easy:

            <DataTemplate x:Key="PersonTemplate">

                <TextBlock Text="{Binding Path=Name}"/>

            </DataTemplate>

       

            <HierarchicalDataTemplate x:Key="CityTemplate" ItemsSource="{Binding Path=Items}">

                <TextBlock Text="{Binding Path=Name}"/>

            </HierarchicalDataTemplate>

     

     

     

    Thursday, September 20, 2007 7:04 AM

All replies

  • Hello, you can use another TreeView in the first HierarchicalDataTemplate. Suppose your data source structure is Country->City->Person, each has a Name property. And you have a collection class with a property called PersonList, which has a property called City. And the City class contains a property called Country. Your first CollectionViewSource will look like this:

            <local:People x:Key="People"/>

     

            <CollectionViewSource x:Key="cvs1" Source="{Binding Source={StaticResource People}, Path=PersonList}">

                <CollectionViewSource.GroupDescriptions>

                    <PropertyGroupDescription PropertyName="City.Country.Name"/>

                </CollectionViewSource.GroupDescriptions>

            </CollectionViewSource>

     

            <TreeView ItemsSource="{Binding Source={StaticResource cvs1}, Path=Groups}" ItemTemplate="{StaticResource CountryTemplate}"/>

     

    This will group the people by their countries. Now we’ll need to set the CountryTemplate’s ItemsSource to another CollectionViewSource which source is a list of people who live in that country. Unfortunately, we can’t define the CollectionViewSource in the Window.Resources scope, or it won’t inherit the correct DataContext. One way to solve the problem will be create another TreeView inside the HierarchicalDataTemplate:

            <HierarchicalDataTemplate x:Key="CountryTemplate">

                <TreeView BorderThickness="0">

                    <TreeView.Resources>

                        <CollectionViewSource x:Key="cvs2" Source="{Binding Path=Items}">

                            <CollectionViewSource.GroupDescriptions>

                                <PropertyGroupDescription PropertyName="City.Name"/>

                            </CollectionViewSource.GroupDescriptions>

                        </CollectionViewSource>

                    </TreeView.Resources>

                   <TreeViewItem Header="{Binding Path=Name}" ItemsSource="{Binding Source={StaticResource cvs2}, Path=Groups}" ItemTemplate="{StaticResource CityTemplate}"/>

                </TreeView>

            </HierarchicalDataTemplate>

     

    Thus, cvs2 will inherit the correct DataContext, and group the people in that country further by cities. We can set the TreeView’s BorderThickness to 0, so it won’t draw a Border which we probably don’t want. The remaining code should be easy:

            <DataTemplate x:Key="PersonTemplate">

                <TextBlock Text="{Binding Path=Name}"/>

            </DataTemplate>

       

            <HierarchicalDataTemplate x:Key="CityTemplate" ItemsSource="{Binding Path=Items}">

                <TextBlock Text="{Binding Path=Name}"/>

            </HierarchicalDataTemplate>

     

     

     

    Thursday, September 20, 2007 7:04 AM
  • this is a good solution!

    my problem is:

    when the mouse on the child(inner) TreeView which has a CollectionViewSource(x:Key="cvs2"), scroll event does not effect the parent(outer) TreeView, how can I scroll the parent TreeView in this time?

     

    Thanks!

    Thursday, December 20, 2007 4:31 PM
  •  

    I had a similar struggle trying to group a flat list for display in a multi level TreeView.  The above solution works, but I have found the result does not behave like a single TreeView, scrolling each City independently or allowing a visible SelectedItem from each City grouping.

     

    My first attempt was simply to reproduce the solution above. Assume peopleList is a flat list of Person objects, and each Person contains three properties (strings); Name, City, Country.

     

    Code Block

    <Window.Resources>

     

    <HierarchicalDataTemplate x:Key="GroupByCityTemplate"

    ItemsSource="{Binding Path=Items}"

    ItemTemplate="{StaticResource PersonTemplate}">

    <TextBlock Text="{Binding Path=Name}" />

    </HierarchicalDataTemplate>

     

    <HierarchicalDataTemplate x:Key="GroupByCountryTemplate">

    <TreeView BorderThickness ="0">

    <TreeView.Resources>

    <CollectionViewSource x:Key="cvs2" Source="{Binding Path=Items}">

    <CollectionViewSource.GroupDescriptions>

    <PropertyGroupDescription PropertyName="City"/>

    </CollectionViewSource.GroupDescriptions>

    </CollectionViewSource>

    </TreeView.Resources>

    <TreeViewItem ItemsSource="{Binding Source={StaticResource cvs2},

    Path=Groups}"

       Header="{Binding Path=Name}"

       ItemTemplate="{StaticResource GroupByCityTemplate}"/>

    </TreeView>

    </HierarchicalDataTemplate>

     

    <CollectionViewSource

    x:Key="cvs1"

    Source="{Binding Source={StaticResource peopleList}}">

    <CollectionViewSource.GroupDescriptions>

    <PropertyGroupDescription PropertyName="Country"/>

    </CollectionViewSource.GroupDescriptions>

    </CollectionViewSource>

    </Window.Resources>

    <Grid>

    <TreeView ItemsSource="{Binding Source={StaticResource cvs1}, Path=Groups}"

                    ItemTemplate="{StaticResource GroupByCountryTemplate}" />

    </Grid>

     

     

     

    My first approch was to remove the extra TreeView within GroupByCountryTemplate and move cvs2 into HierarchicalDataTemplate.Resources. As follows;

    Code Block

     

    <HierarchicalDataTemplate x:Key="GroupByCountryTemplate"

                                            ItemTemplate="{StaticResource GroupByCityTemplate}">

    <HierarchicalDataTemplate.Resources>

    <CollectionViewSource x:Key="cvs2" Source="{Binding Path=Items}">

    <CollectionViewSource.GroupDescriptions>

    <PropertyGroupDescription PropertyName="City"/>

    </CollectionViewSource.GroupDescriptions>

    </CollectionViewSource>

    </HierarchicalDataTemplate.Resources>

    <HierarchicalDataTemplate.ItemsSource>

    <Binding Source="{StaticResource cvs2}" Path="Groups" />

    </HierarchicalDataTemplate.ItemsSource>

    <TextBlock Text="{Binding Path=Name}" />

    </HierarchicalDataTemplate>

     

     

     

    This did not work, cvs2 did not appear to be created, and placing a Converter (with a breakpoint) within the Binding for HirearchicalDataTemplate.ItemsSource did not execute the convert member function when the TreeView was expanded/created.  Perhaps someone can experiment with this and explain why?

     

    The final working solution was to replace GroupByCountryTemplate with the following;

    Code Block

    <HierarchicalDataTemplate x:Key="GroupByCountryTemplate"

    ItemTemplate="{StaticResource GroupByCityTemplate}"

    ItemsSource="{Binding Converter={StaticResource converter}}">

    <TextBlock Text="{Binding Path=Name}" />

    </HierarchicalDataTemplate>

     

     

     

    Where converter  is

    Code Block

    class GroupByCityConverter : IValueConverter

    {

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

    {

    CollectionViewGroup group = value as CollectionViewGroup;

    if (group == null)

    return Binding.DoNothing;

    CollectionViewSource SortByCity = new CollectionViewSource();

    SortByCity.Source = group.Items;

    SortByCity.GroupDescriptions.Add(new PropertyGroupDescription("City"));

    return SortByCity.View.Groups;

    }

    }

     

    Here we use the converter to create a new CollectionViewSource and return the Groups property to the ItemsSource binding.  It seems like a hack but seems to work, placing all grouping levels within a single TreeView, allowing only a single selection (and scrolling correctly).  Hope that helps.

    Tuesday, January 08, 2008 11:23 PM