- 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/
SQLite、Content provider 和 Shared Preference 之间的所有已知区别。 但我想知道什么时候需要根据情况使用 SQLite 或 Content Provider 或
警告:我正在使用一个我无法完全控制的后端,所以我正在努力解决 Backbone 中的一些注意事项,这些注意事项可能在其他地方更好地解决......不幸的是,我别无选择,只能在这里处理它们! 所以,我的
我一整天都在挣扎。我的预输入搜索表达式与远程 json 数据完美配合。但是当我尝试使用相同的 json 数据作为预取数据时,建议为空。点击第一个标志后,我收到预定义消息“无法找到任何内容...”,结果
我正在制作一个模拟 NHL 选秀彩票的程序,其中屏幕右侧应该有一个 JTextField,并且在左侧绘制弹跳的选秀球。我创建了一个名为 Ball 的类,它实现了 Runnable,并在我的主 Draf
这个问题已经有答案了: How can I calculate a time span in Java and format the output? (18 个回答) 已关闭 9 年前。 这是我的代码
我有一个 ASP.NET Web API 应用程序在我的本地 IIS 实例上运行。 Web 应用程序配置有 CORS。我调用的 Web API 方法类似于: [POST("/API/{foo}/{ba
我将用户输入的时间和日期作为: DatePicker dp = (DatePicker) findViewById(R.id.datePicker); TimePicker tp = (TimePic
放宽“邻居”的标准是否足够,或者是否有其他标准行动可以采取? 最佳答案 如果所有相邻解决方案都是 Tabu,则听起来您的 Tabu 列表的大小太长或您的释放策略太严格。一个好的 Tabu 列表长度是
我正在阅读来自 cppreference 的代码示例: #include #include #include #include template void print_queue(T& q)
我快疯了,我试图理解工具提示的行为,但没有成功。 1. 第一个问题是当我尝试通过插件(按钮 1)在点击事件中使用它时 -> 如果您转到 Fiddle,您会在“内容”内看到该函数' 每次点击都会调用该属
我在功能组件中有以下代码: const [ folder, setFolder ] = useState([]); const folderData = useContext(FolderContex
我在使用预签名网址和 AFNetworking 3.0 从 S3 获取图像时遇到问题。我可以使用 NSMutableURLRequest 和 NSURLSession 获取图像,但是当我使用 AFHT
我正在使用 Oracle ojdbc 12 和 Java 8 处理 Oracle UCP 管理器的问题。当 UCP 池启动失败时,我希望关闭它创建的连接。 当池初始化期间遇到 ORA-02391:超过
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 9 年前。 Improve
引用这个plunker: https://plnkr.co/edit/GWsbdDWVvBYNMqyxzlLY?p=preview 我在 styles.css 文件和 src/app.ts 文件中指定
为什么我的条形这么细?我尝试将宽度设置为 1,它们变得非常厚。我不知道还能尝试什么。默认厚度为 0.8,这是应该的样子吗? import matplotlib.pyplot as plt import
当我编写时,查询按预期执行: SELECT id, day2.count - day1.count AS diff FROM day1 NATURAL JOIN day2; 但我真正想要的是右连接。当
我有以下时间数据: 0 08/01/16 13:07:46,335437 1 18/02/16 08:40:40,565575 2 14/01/16 22:2
一些背景知识 -我的 NodeJS 服务器在端口 3001 上运行,我的 React 应用程序在端口 3000 上运行。我在 React 应用程序 package.json 中设置了一个代理来代理对端
我面临着一个愚蠢的问题。我试图在我的 Angular 应用程序中延迟加载我的图像,我已经尝试过这个2: 但是他们都设置了 src attr 而不是 data-src,我在这里遗漏了什么吗?保留 d
我是一名优秀的程序员,十分优秀!