ObservableCollection不能反映Summary计算的变化

本文关键字:计算 变化 Summary 不能 ObservableCollection | 更新日期: 2023-09-27 18:12:36

我有一个DataGrid背后的代码手动处理的ObservableCollection<T>。当我添加一个新项目到备份列表或删除一个时,Count被更新,但ItemsSource没有被通知,因此Price的总和更新。我甚至尝试了BindingList<T>,但即使是Count也停止了更新!有什么诀窍吗?

XAML:

<Label>
   <Label.Content>
        <TextBlock>
             <TextBlock.Text>
                <MultiBinding StringFormat="{}Count: {0}, Sum: {1}" NotifyOnTargetUpdated="True" NotifyOnSourceUpdated="True">
                    <Binding ElementName="datagrid" Path="ItemsSource.Count" UpdateSourceTrigger="PropertyChanged"/>
                    <Binding ElementName="datagrid" Path="ItemsSource" Converter="{StaticResource SumConverter}" UpdateSourceTrigger="PropertyChanged" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Label.Content>
</Label>

SumConverter:

[ValueConversion(typeof(ObservableCollection<DocView>), typeof(String))]
public class SumConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null && value is ObservableCollection<DocView>)
        {
            ObservableCollection<DocView> items = (ObservableCollection<DocView>)value;
            return items.Sum(x => x.Price);
        }
        else
            return 0;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

背后的代码:

ObservableCollection<DocView> list = new ObservableCollection<DocView>();
datagrid.DataContext = list;

ObservableCollection不能反映Summary计算的变化

我认为问题在于您将列表绑定到datagrid的方式,并且转换器不会拾取ItemSource中的更改,因为该属性不会更改。集合不会触发PropertyChanged事件。试试这个:

datagrid.ItemsSource = list;

在XAML中使用多绑定上的转换器。到ItemsSource.Count的绑定将在集合更改时触发通知。注意,您不需要在Label中嵌入TextBlock

    <TextBlock Grid.Row="2">
        <TextBlock.Text>
            <MultiBinding Converter="{StaticResource SumConverter}">
                <Binding ElementName="datagrid" Path="ItemsSource" />
                <Binding ElementName="datagrid" Path="ItemsSource.Count"/>
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>

显然你需要相应地修改你的转换器。

 public class SumConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (values != null && values.Length > 0 && values[0] is ObservableCollection<string>)
            {
                ObservableCollection<DocView> items = (ObservableCollection<DocView>)values[0];
                return string.Format("Count: {0}, Sum: {1}", items.Count, items.Sum(x => x.Price));
            }
            else
                return "Count: 0, Sum: 0";
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

这是一个需要最少更改的解决方案。最好是将集合绑定到ViewModel,而不是绑定到datagrid.ItemsSource

完整的解决方案:

  1. 将所有内容包装在视图模型中,并在每个位置实现INPC
  2. 使用BindingList<T>而不是ObservableCollection<T>(如果你使用ObservableCollection<T>,列表中现有项目的属性更新不会触发UI更新,而BindingList<T>支持这一点)
XAML:

<DataGrid x:Name="datagrid" ItemsSource="{Binding List}" />
    <Label>
       <Label.Content>
            <TextBlock>
                 <TextBlock.Text>
                    <MultiBinding StringFormat="{}Count: {0}, Sum: {1}" NotifyOnTargetUpdated="True" NotifyOnSourceUpdated="True">
                         <Binding ElementName="datagrid" Path="DataContext.Count"/>
                         <Binding ElementName="datagrid" Path="DataContext.Total"/>
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </Label.Content>
    </Label>

背后的代码:

DocViewModel list= new DocViewModel();
datagrid.DataContext = list;

DocViewModel :

public class DocViewModel : INotifyPropertyChanged
{
    public DocViewModel()
    {
        this.list = new BindingList<DocView>();
        this.list.ListChanged += list_ListChanged;
    }
    void list_ListChanged(object sender, ListChangedEventArgs e)
    {
        OnPropertyChanged("Total");
        OnPropertyChanged("Count");
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private BindingList<DocView> list;
    public BindingList<DocView> List
    {
        get { return list; }
        set
        {
            list = value;
            OnPropertyChanged("List");
        }
    }
    public decimal Total
    {
        get { return list.Sum(x => x.Price); }
    }
    private void OnPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public int Count
    {
        get { return list.Count; }
    }
}

DocView:

public class DocView : INotifyPropertyChanged
{
    private decimal price;
    public decimal Price
    {
        get { return price; }
        set
        {
            price = value;
            OnPropertyChanged("Price");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

编辑

这里是视图模型的一个更具体的通用实现,它允许你对不同的项目类型使用相同的逻辑来提供自定义Total和Count计算器:

public class GenericListViewModel<T> : INotifyPropertyChanged
    {
        Func<BindingList<T>, decimal> totalCalculator;    
        Func<BindingList<T>, int> countCalculator;
        public GenericListViewModel(Func<BindingList<T>, decimal> _totalCalculator, Func<BindingList<T>, int> _countCalculator)
        {
            this.list = new BindingList<T>();
            this.list.ListChanged += list_ListChanged;
            this.totalCalculator = _totalCalculator;
            this.countCalculator = _countCalculator;
        }
        void list_ListChanged(object sender, ListChangedEventArgs e)
        {
            OnPropertyChanged("Total");
            OnPropertyChanged("Count");
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private BindingList<T> list;
        public BindingList<T> List
        {
            get { return list; }
            set
            {
                list = value;
                OnPropertyChanged("List");
            }
        }
        public decimal Total
        {
            get
            {
                if (totalCalculator != null)
                    return totalCalculator.Invoke(list);
                else
                    throw new NotImplementedException("Total Func must be impelmented");
            }
        }
        private void OnPropertyChanged(String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        public int Count
        {
            get
            {
                if (countCalculator != null)
                    return countCalculator.Invoke(list);
                else
                    throw new NotImplementedException("Count Func must be implemented");
            }
        }
    }

用法:

public class MyDocViewModel: GenericListViewModel<DocView>
{
    public MyDocViewModel()
        : base(
        (list) =>
        {
            return list.Sum(x => x.Price);
        },
        (list) =>
        {
            return list.Count;
        }
        )
    {
    }
}