views:

559

answers:

3

Hi everyone,

First question here. I hope I won't mess it. Also excuse my English.

I have successfully applied the trick explained here. But I still have one problem.

Quick recap : I display users in a ListView. Users are regrouped by Country, and in the GroupStyle DataTemplate I display the sum of all group related Users.Total, using a Converter. But UI users can change the "Total" property value of Users through a modal window.

When there is only one item in the Group, both the User Total displayed and the sum are properly updated. But when there are multiple items in the group, only the User Total is updated (through binding) but the Converter that's supposed to make the sum (TotalSumConverter) is not even called!

Do you have any idea where it could come from? Should I use some kind of a trigger to make sure the Converter is called when there is a modification in the items?

Any help strongly appreciated.

A: 

The trick you are using databinds the group footer to ListView.Items which will not update your view automatically, like a DependencyObject does for example. Instead force a refresh after each update to Total like this:

CollectionViewSource viewSource = FindResource("ViewSource") as CollectionViewSource;
viewSource.View.Refresh();
Wallstreet Programmer
A: 

The problem with refreshing the view is that it completely refreshes it, and I have expanders for my grouping, that will expand back to their original state, even if the user closed them. So yes it's a possible workaround, but it's not completely satisfying.

Also your DependencyObject explanation is interesting, but then, why is it working when I have only one item in my group?

A: 

The problem is that the value converter that calculates the sum for all the items in a group don't run when an item is changed, since there's no notification for changed items. One solution is to bind to something else that you can control how it does notifications and notify the group header when needed.

Below is a working example. You can change the count for a user in the text box and totals gets recalculated.

XAML:

<Window x:Class="UserTotalTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:userTotalTest="clr-namespace:UserTotalTest"
    Title="Window1" Height="300" Width="300"
    Name="this">

    <Window.Resources>

        <userTotalTest:SumConverter x:Key="SumConverter" />

        <CollectionViewSource Source="{Binding Path=Users}" x:Key="cvs">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Country"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="10" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <ListView 
            Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
            ItemsSource="{Binding Source={StaticResource cvs}}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
                        <GridViewColumn Header="Count" DisplayMemberBinding="{Binding Path=Count}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
            <ListView.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <StackPanel Margin="10">
                                            <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
                                            <ItemsPresenter />
                                            <TextBlock FontWeight="Bold">
                                                <TextBlock.Text>
                                                    <MultiBinding Converter="{StaticResource SumConverter}">
                                                        <MultiBinding.Bindings>
                                                            <Binding Path="DataContext.Users" ElementName="this" />
                                                            <Binding Path="Name" />
                                                        </MultiBinding.Bindings>
                                                    </MultiBinding>
                                                </TextBlock.Text>
                                             </TextBlock>
                                        </StackPanel>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </ListView.GroupStyle>
        </ListView>
        <ComboBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" 
            ItemsSource="{Binding Path=Users}"
            DisplayMemberPath="Name" 
            SelectedItem="{Binding Path=SelectedUser}" />
        <TextBlock Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Path=SelectedUser.Country}" />
        <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding Path=SelectedUser.Count}" />
    </Grid>
</Window>

Code behind:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace UserTotalTest
{
    public partial class Window1 : Window
    {
        public Window1() 
        {
            InitializeComponent();

            DataContext = new UsersVM();
        }
    }

    public class UsersVM : INotifyPropertyChanged
    {
        public UsersVM()
        {
            Users = new List<User>();
            Countries = new string[] { "Sweden", "Norway", "Denmark" };
            Random random = new Random();
            for (int i = 0; i < 25; i++)
            {
                Users.Add(new User(string.Format("User{0}", i), Countries[random.Next(3)], random.Next(1000)));
            }

            foreach (User user in Users)
            {
                user.PropertyChanged += OnUserPropertyChanged;
            }

            SelectedUser = Users.First();
        }

        private void OnUserPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Count")
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Users"));
            }
        }

        public List<User> Users { get; private set; }

        private User _selectedUser;
        public User SelectedUser
        {
            get { return _selectedUser; }
            set 
            {
                _selectedUser = value; if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedUser"));
                }
            }
        }

        public string[] Countries { get; private set; }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }

    public class User : INotifyPropertyChanged
    {
        public User(string name, string country, double total)
        {
            Name = name;
            Country = country;
            Count = total;
        }

        public string Name { get; private set; }
        private string _country;
        public string Country
        {
            get { return _country; }
            set
            {
                _country = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Country"));
                }
            }
        }

        private double _count;
        public double Count
        {
            get { return _count; }
            set
            {
                _count = value; if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Count"));
                }
            }
        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }

    public class SumConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            IEnumerable<User> users = values[0] as IEnumerable<User>;
            string country = values[1] as string;
            double sum = users.Cast<User>().Where(u =>u.Country == country).Sum(u => u.Count);
            return "Count: " + sum;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
Wallstreet Programmer