Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to associate custom data with a CollectionViewGroup?

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.

like image 991
user1800540 Avatar asked Nov 05 '12 15:11

user1800540


1 Answers

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

like image 141
Cédric Bellec Avatar answered Nov 03 '22 12:11

Cédric Bellec