How to associate custom data with a CollectionViewGroup?
Asked Answered
T

3

7

I've got an ItemsControl in XAML where I am displaying an expander for each group so I can expand/collapse the group. I want to persist the state of the IsExpanded property (and potentially other settings relating to the display of the group header). Normally you just have a class with the properties on it and bind to this. However, the data context for the group is CollectionViewGroup. Now this class isn't very helpful as it only gives you the Name property and the items in the group (which is fine if you just want a heading and maybe display some sort of metric based on the number of items in the group or their contents but not if you just want to store custom data about the state of the group header UI). What I'd like to do is to derive from this class and add other properties to my derived class and bind to that instead. But there doesn't seem to be any easy way to do this. All the details of group generation seem to be hidden away in internal classes which is very frustrating. Has anyone gone down the route of implementing ICollectionView themselves (and therefore all the other related classes as well presumably)? It seems like a massive job to replicate everything in ListCollectionView just to be able to create a custom CollectionViewGroup class and bind to that instead! Thanks.

Taskwork answered 5/11, 2012 at 15:23 Comment(0)
F
1

One approach is to use a MultiBinding to find or compute the custom data and bind-time.

I made a DataGrid with Groups that shows in header the sum of items specific value in the group, in order to update this sum when group items changes I made a multivalue binding with a custom multivalue converter, the multivalue binding with ItemCount property permits to get notified when the group items changes and then to update the sum and display the newsum value.

Here is the code for the multivalue converter class :

Public Class UserBalanceConverter
Implements IMultiValueConverter

Private Function GetSubTotal(ByVal obj As CollectionViewGroup) As String

    Dim total As Decimal
    For Each objItem As Object In obj.Items
        If TypeOf objItem Is Account Then
            Dim a As Account = DirectCast(objItem, Account)
            Dim rate As Decimal = 1
            rate = 1 / ExchangeRatesInfo.GetExchangeRate(a.currencyCode.ToString)

            total += a.Balance * rate
        Else
            total += GetSubTotal(objItem)
        End If
    Next

    Return total.ToString("C")
End Function

Public Function Convert(ByVal value() As Object,
                        ByVal targetType As System.Type,
                        ByVal parameter As Object,
                        ByVal culture As System.Globalization.CultureInfo) _
         As Object Implements System.Windows.Data.IMultiValueConverter.Convert

    Dim cvg As CollectionViewGroup = CType(value(1), CollectionViewGroup)

    Return GetSubTotal(cvg)

End Function


Public Function ConvertBack(ByVal value As Object,
                            ByVal targetType() As System.Type,
                            ByVal parameter As Object,
                            ByVal culture As System.Globalization.CultureInfo) _
        As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack

    Throw New NotImplementedException

End Function

End Class

Then in XAML you use the multivalue converter in a style used for GroupItem :

  <Style TargetType ="{x:Type GroupItem}" x:Key="UserGroupHeaderStyle">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type GroupItem}">
                        <Expander x:Name="exp" IsExpanded="False">
                            <Expander.Header>
                                <StackPanel >
                                    <TextBlock Text="{Binding Name}" />
                                    <StackPanel Orientation="Horizontal" >
                                        <TextBlock Text="{Binding ItemCount}">
                                        <TextBlock Text=" "/>
                                        <TextBlock Text="items" />
                                        <TextBlock Text=" "/>
                                        <TextBlock Text="Balance: " />
                                        <TextBlock>
                                            <TextBlock.Text>
                                                <MultiBinding Converter="{StaticResource UserBalanceConverter}">
                                                    <Binding Path="ItemCount"/>
                                                    <Binding />
                                                </MultiBinding>
                                            </TextBlock.Text>
                                        </TextBlock>
                                    </StackPanel>
                                </StackPanel>
                            </Expander.Header>
                            <ItemsPresenter />
                        </Expander>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Finish with applying the style to your DataGrid :

<DataGrid.GroupStyle>
     <GroupStyle ContainerStyle="{StaticResource UserGroupHeaderStyle}">
             <GroupStyle.Panel>
                   <ItemsPanelTemplate>
                           <DataGridRowsPresenter/>
                   </ItemsPanelTemplate>
             </GroupStyle.Panel>
     </GroupStyle>
 </DataGrid.GroupStyle>

Also don't forget to declare your convert class in the resource section of your XAML :

  <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
             <local:UserBalanceConverter x:Key="UserBalanceConverter"/>
        </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>

Et Voilà ! It works like a charm!

HTH

Floc answered 17/1, 2013 at 17:19 Comment(1)
how does this answer the OPs question? he asked how to persist the IsExpanded value and not generate metrics from the items which he excluded in the question.Fleming
D
0

A simple approach would be to just wrap the CollectionViewGroup into another ViewModel class wich provide the additional needed display properties like IsExpanded. A Lesson I learned the hard way was not to bend the xaml / view to match the business data. Instead bend / wrap or convert the business data to match the requirements of the UI.

Disengagement answered 6/11, 2012 at 7:13 Comment(1)
This is bad advice. Often the business logic is already done or out of your control and thus there is no way of 'warping' it. Furthermore, what if you want to use the same logic with another GUI, what do you do then? Rewarp it?Retardation
M
0

I was able to solve exactly this problem (i.e. binding to IsExpanded) using an approach that's somewhat similar to Cédric's suggestion, but in a seemingly more MVVM way:

<ControlTemplate TargetType="GroupItem">
    <TreeViewItem IsExpanded="{Binding Items[0].IsGroupExpanded, Mode=TwoWay}">
        <TreeViewItem.Header>
            <TextBlock Text="{Binding Name}" />
        </TreeViewItem.Header>
        <TreeViewItem.Items>
            <ItemsPresenter />
        </TreeViewItem.Items>
    </TreeViewItem>
</ControlTemplate>

Both ItemViewModel.IsGroupExpanded setter and getter redirect to Group.IsExpanded.

Note that Mode=TwoWay must be specified, since IsExpanded seems to be bound OneWay by default.

Messer answered 7/1, 2016 at 21:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.