问题:当列表中单个项目的属性更改值后,更新有界 UI 元素以显示仅在 ViewModel 中定义的属性的正确方法是什么?
当在将作为列表项的类中实现 INotifyPropertyChanged 时,它只会更新绑定到该特定数据片段的 UI 元素。就像 ListView 项或 DataGrid 单元格一样。这很好,这是我们想要的。但是,如果我们需要一个总计行,就像 Excel 表格中一样。当属性基于来自 Model 的数据在 ViewModel 中被定义和计算时,出现了什么问题。例如:
这个解决方案可以使用,但它似乎只是一个权宜之计,应该有更加优雅的实现方式。在我的项目中,视图模型中有很多这样的求和属性,目前而言,这需要更新很多属性和编写大量代码,感觉增加了很多开销。
我还需要做更多的研究,写这篇问题时出现了几篇有趣的文章。如果这个问题比我想象的更常见,我会在此帖子中更新其他解决方案的链接。
当在将作为列表项的类中实现 INotifyPropertyChanged 时,它只会更新绑定到该特定数据片段的 UI 元素。就像 ListView 项或 DataGrid 单元格一样。这很好,这是我们想要的。但是,如果我们需要一个总计行,就像 Excel 表格中一样。当属性基于来自 Model 的数据在 ViewModel 中被定义和计算时,出现了什么问题。例如:
public class ViewModel
{
public double OrderTotal => _model.order.OrderItems.Sum(item => item.Quantity * item.Product.Price);
}
何时以及如何进行通知/更新/调用?
让我们通过一个更完整的示例来尝试。
这是XAML:
<Grid>
<DataGrid x:Name="GrdItems" ... ItemsSource="{Binding Items}"/>
<TextBox x:Name="TxtTotal" ... Text="{Binding ItemsTotal, Mode=OneWay}"/>
</Grid>
这是模型:
public class Item : INotifyPropertyChanged
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Value
{
get { return _value; }
set
{
if (value.Equals(_value)) return;
_value = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new propertyChangedEventArgs(propertyName));
}
}
public class Model
{
public List<Item> Items { get; set; } = new List<Item>();
public Model()
{
Items.Add(new Item() { Name = "Item A", Value = 100 });
Items.Add(new Item() { Name = "Item b", Value = 150 });
Items.Add(new Item() { Name = "Item C", Value = 75 });
}
}
还有ViewModel:
public class ViewModel
{
private readonly Model _model = new Model();
public List<Item> Items => _model.Items;
public int ItemsTotal => _model.Items.Sum(item => item.Value);
}
我知道这段代码看起来过于简化,但它是一个更大、令人沮丧的应用程序的一部分。
我想要做的只是在DataGrid中更改项目的值时,使ItemsTotal属性更新TxtTotal文本框。
到目前为止,我找到的解决方案包括使用ObservableCollection和实现CollectionChanged事件。
模型更改为:
public class Model: INotifyPropertyChanged
{
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
public Model()
{
Items.CollectionChanged += ItemsOnCollectionChanged;
}
.
.
.
private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (Item item in e.NewItems)
item.PropertyChanged += MyType_PropertyChanged;
if (e.OldItems != null)
foreach (Item item in e.OldItems)
item.PropertyChanged -= MyType_PropertyChanged;
}
void MyType_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
OnPropertyChanged(nameof(Items));
}
public event PropertyChangedEventHandler PropertyChanged;
.
.
.
}
而且视图模型更改为:
public class ViewModel : INotifyPropertyChanged
{
private readonly Model _model = new Model();
public ViewModel()
{
_model.PropertyChanged += ModelOnPropertyChanged;
}
private void ModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
OnPropertyChanged(nameof(ItemsTotal));
}
public ObservableCollection<Item> Items => _model.Items;
public int ItemsTotal => _model.Items.Sum(item => item.Value);
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
这个解决方案可以使用,但它似乎只是一个权宜之计,应该有更加优雅的实现方式。在我的项目中,视图模型中有很多这样的求和属性,目前而言,这需要更新很多属性和编写大量代码,感觉增加了很多开销。
我还需要做更多的研究,写这篇问题时出现了几篇有趣的文章。如果这个问题比我想象的更常见,我会在此帖子中更新其他解决方案的链接。