How do GroupStyles work?

Ingó Vals picture Ingó Vals · May 24, 2011 · Viewed 8.1k times · Source

I have a ListView Control bound to a ListCollectionView in a ViewModel.

I wanted to try to group these items but having some problems.

I set the Property grouping in the VM to begin with and then added a GroupStyle.

C#:

ListCollectionView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));

XAML:

<ListView.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
                 <TextBlock Text="{Binding Path=Name}"/>
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListView.GroupStyle>

However the list is now just the category names, no way to see the items themselves.

I'm not really understanding completely what is going on here. When I create a Template for the GroupStyle what am I really binding to? Are there other properties besides Name ?

I just added the GroupStyle to a ListView I has already created where I for example included a ItemTemplate. Is that something that is messing with the GroupStyle?

What if the Items in the list belong to another class and I wan't to group based on what instance of class they belong to (it has an ID). I would then have the group name as a property on this parent class. Is that possible?

PARTIAL SOLUTION:

Problem was with the style applied on the ListView. I have no idea what about the style was interefering.

FULL SOLUTION

I wasn't using a ItemsPresenter in my listbox ControlTemplate opting to use a Panel with IsItemsHost set to true. It seems ItemsPresenter must be used for GroupStyling to work correctly.

Answer

user1228 picture user1228 · May 24, 2011

I think the error lies elsewhere in your code.

Usually, you expose a collection of Models on your ViewModel

namespace Derp
{
    public sealed class ViewModel
    {
      public ObservableCollection<Model> Items {get;set;}
      // initialization code not shown
    }

    public sealed class Model
    {
      public string GroupName {get;set;}
      public string ModelName {get;set;}
    }
}

In your View, you bind a CollectionViewSource to this collection:

<Window.DataContext>
    <ViewModel xmlns="clr-namespace:Derp" />
</Window.DataContext>
<Window.Resources>
    <CollectionViewSource
        Source="{Binding Items}"
        x:Key="GroupedItems">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription
                PropertyName="GroupName" />
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>

Next, we bind our list control to this CollectionViewSource (using a combo in this example):

<ComboBox
    ItemsSource="{Binding Source={StaticResource GroupedItems}}"
    DisplayMemberPath="ModelName">
    <ComboBox.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock
                        Text="{Binding Name}" />
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ComboBox.GroupStyle>
</ComboBox>

Where it can get confusing is that, within the GroupStyle, you aren't binding against your Model, you are binding against a collection of Models which is grouped on (in this case) the property "GroupName". The CollectionViewSource groups your Models into collections that extend CollectionViewGroup. These groups have a property called Name, which contains the common value on which your Models are grouped (the value of the GroupName property). So, in the HeaderTemplate, you are binding to CollectionViewGroup.Name.