gpt4 book ai didi

c# - 当基础模型数据发生变化时,通知 ViewModel 中定义的属性变化

转载 作者:太空狗 更新时间:2023-10-30 01:15:06 27 4
gpt4 key购买 nike

问题:在列表中的单个项目中的属性更改值后,更新有界 UI 元素以显示仅在 ViewModel 中定义的属性值的正确方法是什么。

在将成为列表中的项目的类中实现 INotifyPropertyChanged 时,它只会更新特定数据片段绑定(bind)到的 UI 元素。类似于 ListView 项或 DataGrid 单元格。这很好,这就是我们想要的。但是如果我们需要总计行,就像在 Excel 表格中一样。当然有多种方法可以解决该特定问题,但这里的根本问题是何时根据模型中的数据在 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;

.
.
.

}

并且 View 模型更改为:

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

此解决方案有效,但它似乎只是一种解决方案,应该有更 Eloquent 实现。我的项目在 View 模型中有几个这样的总和属性,就目前而言,有很多属性需要更新,需要编写很多代码,这感觉就像是更多的开销。

我还有更多的研究要做,在我写这个问题的时候出现了几篇有趣的文章。我将使用指向其他解决方案的链接更新这篇文章,因为这个问题似乎比我想象的更常见。

最佳答案

虽然您的项目看起来像是 MVVM,但我认为它实际上不是。是的,你有层,但你的模型和 View 模型是交易责任。在 MVVM 情况下保持纯净的一种方法是永远不要将 INotifyPropertyChanged 放在 View 模型之外的任何东西上。如果您发现自己将其放入模型中,那么您的模型就会被 viewmodel 职责破坏。同上 View (尽管人们不太倾向于将 INotifyPropertyChanged 堆叠到 View 上)。它也有助于打破您认为 View 与单个 View 模型相关联的假设。这感觉像是 MVC 思想的交叉。

所以我要说的是,您有一个从概念上开始的结构性问题。例如,一个 View 模型没有理由不能有一个 subview 模型。事实上,当我有一个强大的对象层次结构时,我经常发现这很有用。所以你会有 Item 和 ItemViewModel。无论您的父对象是什么(比如 Parent)和 ParentViewModel。 ParentViewModel 将具有类型为 ItemViewModel 的可观察集合,并且它将订阅其子级的 OnPropertyChanged 事件(这将为总属性触发 OnPropertyChanged)。这样 ParentViewModel 既可以提醒 UI 属性更改,又可以确定该更改是否也需要反射(reflect)在 Parent 模型中(有时您希望将聚合存储在父数据中,有时则不需要)。计算字段(如总计)通常仅存在于 ViewModel 中。

简而言之,您的 ViewModel 负责协调。您的 ViewModel 是模型数据的主人,对象之间的通信应该发生在 View 模型到 View 模型之间,而不是通过模型。这意味着您的 UI 可以有一个父 View 和一个单独定义的 subview ,并保持这些独立的工作,因为它们通过绑定(bind)的 View 模型进行通信。

这有意义吗?

它看起来像:

public class ParentViewModel : INotifyPropertyChanged
{
private readonly Model _model;

public ParentViewModel(Model model)
{
_model = model;
Items = new ObservableCollection<ItemViewModel>(_model.Items.Select(i => new ItemViewModel(i)));
foreach(var item in Items)
{
item.PropertyChanged += ChildOnPropertyChanged;
}
Items.CollectionChanged += ItemsOnCollectionChanged;
}

private void ItemsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (Item item in e.NewItems)
item.PropertyChanged += ChildOnPropertyChanged;

if (e.OldItems != null)
foreach (Item item in e.OldItems)
item.PropertyChanged -= ChildOnPropertyChanged;

OnPropertyChanged(nameof(ItemsTotal));
}

private void ChildOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (e.PropertyName == "Value")
OnPropertyChanged(nameof(ItemsTotal));
}

public ObservableCollection<ItemViewModel> Items;
public int ItemsTotal => Items.Sum(item => item.Value);


public event PropertyChangedEventHandler PropertyChanged;

[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

这很复杂,但至少所有的复杂性都包含在您的 ViewModel 中并从那里进行协调。

关于c# - 当基础模型数据发生变化时,通知 ViewModel 中定义的属性变化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39883306/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com