Сумма GroupStyle не обновляется, когда несколько элементов

Я успешно применил трюк, описанный здесь . Но у меня все еще есть одна проблема.

Краткий обзор: я отображаю пользователей в ListView. Пользователи перегруппированы по странам, и в шаблоне данных GroupStyle я отображаю сумму всех пользователей, связанных с группой, с помощью преобразователя. Но пользователи пользовательского интерфейса могут изменить значение свойства «Всего» пользователей через модальное окно.

Когда в группе есть только один элемент, отображаемое количество пользователей и сумма корректно обновляются. Но когда в группе несколько элементов, обновляется только User Total (через привязку), а Converter, который должен составлять сумму (TotalSumConverter), даже не вызывается!

У вас есть идеи, откуда это могло взяться? Должен ли я использовать какой-то триггер, чтобы убедиться, что конвертер вызывается при изменении элементов?


person Community    schedule 12.05.2009    source источник


Ответы (3)


Проблема заключается в том, что преобразователь значений, вычисляющий сумму для всех элементов в группе, не запускается при изменении элемента, поскольку для измененных элементов нет уведомления. Одним из решений является привязка к чему-то еще, что вы можете контролировать, как оно делает уведомления и уведомляет заголовок группы, когда это необходимо.

Ниже приведен рабочий пример. Вы можете изменить количество для пользователя в текстовом поле, и итоги будут пересчитаны.

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>

Код позади:

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();
        }
    }
}
person Wallstreet Programmer    schedule 11.02.2010

Трюк, который вы используете, привязывает нижний колонтитул группы к ListView.Items, который не будет автоматически обновлять ваше представление, как это делает, например, DependencyObject. Вместо этого принудительно обновляйте Total после каждого обновления следующим образом:

CollectionViewSource viewSource = FindResource("ViewSource") as CollectionViewSource;
viewSource.View.Refresh();
person Wallstreet Programmer    schedule 12.05.2009

Проблема с обновлением представления заключается в том, что оно полностью обновляет его, и у меня есть расширители для моей группы, которые будут расширяться до исходного состояния, даже если пользователь их закрыл. Так что да, это возможный обходной путь, но он не полностью удовлетворяет.

Также интересно ваше объяснение DependencyObject, но тогда почему оно работает, когда у меня есть только один элемент в моей группе?

person Community    schedule 17.05.2009