ObservableCollection不反映摘要计算的更改

时间:2014-07-11 15:46:11

标签: c# wpf datagrid observablecollection

我有一个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;

2 个答案:

答案 0 :(得分:0)

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

datagrid.ItemsSource = list;

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

    <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 :(得分:0)

完整的解决方案:

  1. 在视图模型中包装所有内容,并在每个位置实现INPC
  2. 使用BindingList<T>代替ObservableCollection<T>(如果您使用ObservableCollection<T>,则列表中现有项目属性的更新不会在BindingList<T>时触发对UI的更新支持这个)
  3. 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));
            }
        }
    }
    

    修改

    这是一个更具体的视图模型通用实现,让您使用相同的逻辑,使用不同的项目类型来提供自定义的总计数和计数计算器:

    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;
            }
            )
        {
    
        }
    }