- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
问题:在列表中的单个项目中的属性更改值后,更新有界 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/
vue3 快速入门系列 - 基础 前面我们已经用 vue2 和 react 做过开发了。 从 vue2 升级到 vue3 成本较大,特别是较大的项目。所以许多公司对旧项目继续使用vue2,新项目则
C# 基础 C#项目创建 这里注意win10虚拟机需要更新下补丁,不然直接下载visual studio 2022会显示版本不支持 HelloWorld C#的类文件都是以.cs结尾,入口方法为sta
关于 iPhone 内存管理的非常基本的问题: 假设我有一个 viewController,其中有几个 subview 也由 viewController 控制。当我删除顶部 viewControll
我仍在努力适应指针。不是概念——我理解内存位置、匹配可变长度的指针增量等——这是语法。这是一个我认为是我感到困惑/无法直观把握的原因之一: int a = 42; 在一个int大小的内存空间中分配并放
1. 简介 Kafka(Apache Kafka) 是一种分布式流数据平台,最初由LinkedIn开发,并于后来捐赠给Apache软件基金会,成为了一个Apache顶级项目。它被设计用于处理大规
1.想要在命令提示符下操作mysql服务器,添加系统变量。(计算机-系统属性——环境变量——path) 2.查询数据表中的数据; select selection_lis
MySQL表的增删改查(基础) 1. CRUD 注释:在SQL中可以使用“–空格+描述”来表示注释说明 CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Dele
我有一个网页,可以在加载时打开显示模式,在这个模式中,我有一个可以打开第二个模式的链接。当第二个模式关闭时(通过单击关闭按钮或单击模式外部),我想重新打开第一个模式。 对于关闭按钮,我可以通过向具有
使用 Core Data Fetched Properties,我如何执行这个简单的请求: 我希望获取的属性 ( myFetchProp ) 存储 StoreA ,它应该这样做: [myFetchPr
关闭。这个问题是opinion-based .它目前不接受答案。 想改进这个问题?更新问题,以便 editing this post 可以用事实和引用来回答它. 8年前关闭。 Improve this
最近,我得到了一个现有的Drupal项目,并被要求改进前端(HTML,JavaScript,CSS)。我在Django,PHP,Ruby等方面具有大量的前端和后端开发经验,但是我没有任何Drupal经
我试图让我的用户通过使用扫描仪类来决定要做什么,但我有一个问题,代码一旦运行就不会激活,并且它不会让我跳过任何行。我的代码如下所示: Scanner input = new Scanner(S
对模糊的标题表示歉意,因为我想不出这个名字是什么。 基本上创建一个计算学生财务付款的小程序。当我运行它时,它计算对象限额没有问题。然而,无论我尝试什么,对象“助学金”似乎除了 0 之外什么也没有提出。
这是我的代码 - main() { double x; double y = pow(((1/3 + sin(x/2))(pow(x, 3) + 3)), 1/3); prin
如果我的术语在这个问题上有误,我们深表歉意。 采取以下功能: i = 1; v = i * 2; for (j = 0; j < 4; j++ ) { console.log(v);
我的应用程序中有不同的类文件。我有 5 个类,其中 2 个是 Activity ,1 个是运行的服务。其他 2 个只是类。这两个类中变量的生命周期是多少。我知道一个 Activity 可以被操作系统杀
例如,一个方法返回一个 List 类型的对象。 public List bojangles () ... 一些代码调用方法FooBar.bojangles.iterator(); 我是 Java 的新
我遇到了一个奇怪的问题,网格的大小不适合我的屏幕。当我使用 12 列大时,它只占据屏幕的 1/3 的中间,请参见图像。我不确定是什么导致了这个问题。我没有任何会导致这种情况发生的奇怪 CSS。我不会在
我尝试使用头文件和源文件,但遇到了问题。因此,我对我正在尝试做的事情做了一个简化版本,我在 CodeBlocks 中遇到了同样的错误(undefined reference to add(double
我正在为我的网格系统使用基础,但这在任何网格系统中都可能是一个问题。我基本上用一个容器包裹了 3 个单元格,但其中一个单元格应该长到页面边框(留在我的 Sampe-Image 中)但这也可能在右侧)。
我是一名优秀的程序员,十分优秀!