我有一个datagrid,它绑定到一个实现INotifyPropertyChanged的Item对象。
在ViewModel中,我订阅了外部设备服务的更改,该服务更新了Item对象。 数据网格是可编辑的,因此也可以从视图中更改项目。该值应写入设备,但尚未在视图中更新,因为设备写入可能会失败。如果成功,设备将发出一个我已经订阅的事件。
我的一些担忧是。
从ViewModel或Item对象中,我在哪里调用设备服务上的写入? 如何确保数据网格中显示的值在编辑后“还原”,直到从设备接收到事件?
一些想法
如果它是Item对象,则Item对象不再是DTO,而是我猜的ViewModel。所以我将为同一个视图(用户控件)提供两个视图模型。一个用于用户控件,另一个用于datagrid中的项目。这不符合我对视图模型的理解。但也许这是错的? 然后,Item如何知道值是从视图(由用户)还是视图模型(由设备服务)更新的?
ViewModel在Item对象上订阅PropertyChanged。要检测是否从View更改了值,ViewModel可以取消订阅PropertyChanged或在从服务获取事件时设置标志。它似乎是丛生的,但会起作用。 也许我应该创建两个属性:ViewValue和ServiceValue。 ViewModel应该更新ServiceValue并订阅ViewValue,它可以在阅读后将ViewValue还原为ServiceValue。
视图处理CellEditEnding并通知视图模型
答案 0 :(得分:2)
关于第1点):是的,使用ViewModel绑定UI而不是DTO。 这是帮助将数据与MVVM中的视图分离的主要思想。
关于第2点和第3点)我建议您在ViewModel上实现IEditableObject
接口,可能在公共基类上。
此接口提供方法BeginEdit(), CancelEdit(), and EndEdit()
。使用这些方法可以清晰,可读地控制何时修改对象以及何时提交这些更改,例如:到服务或数据库。
此外,实现这些方法,这为您提供了一种机制来处理那些不同的数据版本,如提到的“ViewValue”和“ServiceValue”。您可以在调用方法BeginEdit()
时创建ServiceValue的本地副本作为后备,并在调用方法CancelEdit()
时调用此本地后备值。如果调用EndEdit()
,则需要将值提交回服务,然后成功将当前值存储在本地临时值中作为新的回退。
编辑以添加第1点的答案
我不确定这是否是“最佳做法”,但至少这是我们的工作。
我们通常以这样的方式组织ViewModel:每个视图包含一个ViewModel,其中包含特定视图的所有信息。我们称之为“主ViewModel”。如果我们需要视图层次结构,我们将实现遵循相同层次结构的ViewModel层次结构。
在像您这样的情况下,我们会将DataGrid放入主视图中包含的另一个UserControl
。主ViewModel将保存另一个ViewModel,它再次保存DataGrid所需的数据。此ViewModel将设置为该DataGrid-UserControl的DataContext。这样,您仍然可以保持干净的一个ViewModel-per-View分离。
然后可以配置DataGrid中数据的其他格式,Templates
。
答案 1 :(得分:1)
1)您的外部数据服务更新您的DTO类将不会进入视图模型。您的DTO是模型或业务对象层的一部分。它的工作是在表示(视图和视图模型)和存储层(数据库)之间传输数据,并执行验证(如果需要)。由外部(非视图)服务更新是在其角色的范围内。您对每个窗口或用户控件的一个VM的评论没有错误 - 基于每个视图而不是每个控件分配VM(尽管可以为多个视图重用相同的VM类)。您应该拥有视图在VM上绑定的DTO实例。这就是VM的工作 - 组织,管理和呈现模型所需的部分。
要让DTO知道更新的来源,正如您在问题中提到的那样,您可以添加一个标志来指示它是来自视图还是服务并根据需要进行设置。或者,您可以通过公共方法使服务更新DTO,并使视图直接绑定到属性。根据您的设计,此选项可能不如设置标志可行。
2)IEditableObject
,如其他答案所述,是一个不错的选择。在DTO上备份服务和视图数据是不必要的,并且有效地使对象的大小增加三倍(假设它是一个薄的包装器,如果它是DTO则应该是它)。如果你 真的 需要能够恢复到任一版本的数据 - 比如说,因为你给用户提供选择在恢复时使用哪个版本的选项 - 保留额外的您的VM上的数据而不是模型上的数据。我想不出你需要一个不涉及某种演示或用户交互决策的选项的原因。数据驱动的恢复应该被设计为始终是确定性的。
3)再次,IEditableObject
。视图不应该通知视图模型;它应该不知道VM(没有任何引用)。这将松开两层之间的耦合,增加应用的灵活性。我通常通过专门用于处理视图的控制器类来实现这一点。当它打开一个视图时,它会创建一个新的VM实例,并将视图指向DataContext
。