ObservableCollection不能反映Summary计算的变化
本文关键字:计算 变化 Summary 不能 ObservableCollection | 更新日期: 2023-09-27 18:12:36
我有一个DataGrid
背后的代码手动处理的ObservableCollection<T>
。当我添加一个新项目到备份列表或删除一个时,Count
被更新,但ItemsSource
没有被通知,因此Price
的总和更新。我甚至尝试了BindingList<T>
,但即使是Count
也停止了更新!有什么诀窍吗?
<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;
我认为问题在于您将列表绑定到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
。
完整的解决方案:
- 将所有内容包装在视图模型中,并在每个位置实现
INPC
- 使用
BindingList<T>
而不是ObservableCollection<T>
(如果你使用ObservableCollection<T>
,列表中现有项目的属性更新不会触发UI更新,而BindingList<T>
支持这一点)
<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;
}
)
{
}
}