Miral
Miral

Reputation: 13030

WPF ListView grouping with expander via header template?

All examples I can find for implementing an Expander as a group for the ListView in WPF do so by defining a new ControlTemplate for the GroupItem in the GroupStyle.ContainerStyle property.

An example of this can be found here, and it works as expected.

However it does have the flaw that if you add additional GroupDescriptions (for multi-level grouping), there is no indentation for the second and subsequent levels like there would be when overriding only the header template and not the container style.

I happened to notice in passing that the GroupItem code does appear to have special support for locating an Expander control in its template, which (if that's a recursive search) suggests that it might be possible to replace the group style above with a simpler one like so:

<GroupStyle>
    <GroupStyle.HeaderTemplate>
        <DataTemplate DataType="GroupItem">
            <Expander IsExpanded="True">
                <Expander.Header>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"
                                   FontWeight="Bold" Foreground="Gray"
                                   FontSize="22" VerticalAlignment="Bottom" />
                        <TextBlock Text="{Binding ItemCount}" FontSize="22"
                                   Foreground="Green" FontWeight="Bold"
                                   FontStyle="Italic" Margin="10,0,0,0"
                                   VerticalAlignment="Bottom" />
                        <TextBlock Text=" item(s)" FontSize="22"
                                   Foreground="Silver" FontStyle="Italic"
                                   VerticalAlignment="Bottom" />
                    </StackPanel>
                </Expander.Header>
                <ItemsPresenter />
            </Expander>
        </DataTemplate>
    </GroupStyle.HeaderTemplate>
</GroupStyle>

ie. the same core Expander but expressed in the HeaderTemplate rather than the ContainerStyle.

This almost works -- visually it appears correct, and even has the expected indentation for multiple levels. But toggling the Expander does not actually show or hide the items in the group, so clearly the visual hierarchy is wrong.

I have a few questions:

  1. Is there a way to make this work like this?
  2. If not, what is the GroupItem actually doing with Expander detection, if not for this?
  3. Sticking with the original ContainerStyle, is there a way to keep the natural indentation?

Upvotes: 2

Views: 5313

Answers (1)

Grx70
Grx70

Reputation: 10349

First of all, if you place Expander in the header template, GroupItem will not recognize it (if you inspect the source code (in particular this helper method) you'll see why - it looks for a descendant Expander, but with TemplatedParent set to the GroupItem in question, whereas if the Expander is part of the header template, its TemplatedParent will be a ContentPresenter1.

Second of all, even if it was recognized, GroupItem does not really handle group items visibility - this is done by the Expander template (when it's expanded its Content is visible, otherwise it is not). GroupItem only handles some edge case when UI virtualization is enabled and it's recycled to represent another item (basically it boils down to invalidating measure on the owner ItemsControl).

Lastly, the correct approach is to put the Expander in the GroupItem template (via GroupStyle.ContainerStyle). If you blindly follow the referred sample then yes, there won't be any indentation. However, all you need to do is to set desired margin on the ItemsPresenter, e.g.:

<GroupStyle>
    <GroupStyle.ContainerStyle>
        <Style TargetType="{x:Type GroupItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type GroupItem}">
                        <Expander Header="{Binding Name}">
                            <ItemsPresenter Margin="20,0,0,0" />
                        </Expander>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </GroupStyle.ContainerStyle>
</GroupStyle>

and voilà! - you've got yourself a nice (and "recursive") indentation.


1 Assuming you do not overwrite the default GroupItem template.

Upvotes: 3

Related Questions