MVVM – 为ModelView实现’IsDirty’function以保存数据

作为WPF和MVVM的新手,我在努力学习一些基本function。

让我先解释一下我的意思,然后附上一些示例代码……

我有一个显示用户列表的屏幕,我在右侧显示所选用户的详细信息,其中包含可编辑的文本框。 然后我有一个Save按钮,它是DataBound,但我只想在数据实际发生变化时显示此按钮。 即 – 我需要检查“脏数据”。

我有一个完整的MVVM示例,其中我有一个名为User的模型:

namespace Test.Model { class User { public string UserName { get; set; } public string Surname { get; set; } public string Firstname { get; set; } } } 

然后,ViewModel看起来像这样:

 using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows.Input; using Test.Model; namespace Test.ViewModel { class UserViewModel : ViewModelBase { //Private variables private ObservableCollection _users; RelayCommand _userSave; //Properties public ObservableCollection User { get { if (_users == null) { _users = new ObservableCollection(); //I assume I need this Handler, but I am stuggling to implement it successfully //_users.CollectionChanged += HandleChange; //Populate with users _users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"}); _users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"}); } return _users; } } //Not sure what to do with this?!?! //private void HandleChange(object sender, NotifyCollectionChangedEventArgs e) //{ // if (e.Action == NotifyCollectionChangedAction.Remove) // { // foreach (TestViewModel item in e.NewItems) // { // //Removed items // } // } // else if (e.Action == NotifyCollectionChangedAction.Add) // { // foreach (TestViewModel item in e.NewItems) // { // //Added items // } // } //} //Commands public ICommand UserSave { get { if (_userSave == null) { _userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute); } return _userSave; } } void UserSaveExecute() { //Here I will call my DataAccess to actually save the data } bool UserSaveCanExecute { get { //This is where I would like to know whether the currently selected item has been edited and is thus "dirty" return false; } } //constructor public UserViewModel() { } } } 

“RelayCommand”只是一个简单的包装类,就像“ViewModelBase”一样。 (为了清楚起见,我会附上后者)

 using System; using System.ComponentModel; namespace Test.ViewModel { public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable { protected ViewModelBase() { } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { var e = new PropertyChangedEventArgs(propertyName); handler(this, e); } } public void Dispose() { this.OnDispose(); } protected virtual void OnDispose() { } } } 

最后 – XAML

                

所以基本上,当我编辑姓氏时,应该启用“保存”按钮; 如果我撤消我的编辑 – 那么它应该再次被禁用,因为没有任何改变。

我在很多例子中看到了这一点,但还没有找到如何做到这一点。

任何帮助将非常感激! 布伦丹

根据我的经验,如果在视图模型中实现IsDirty ,您可能还希望视图模型实现IEditableObject

假设您的视图模型是通常的排序,实现PropertyChanged以及引发它的私有或受保护的OnPropertyChanged方法,设置IsDirty非常简单:如果它不是真的,您只需在OnPropertyChanged设置IsDirty

你的IsDirty setter应该,如果属性为false并且现在为true,则调用BeginEdit

您的Save命令应该调用EndEdit ,它会更新数据模型并将IsDirty设置为false。

您的Cancel命令应该调用CancelEdit ,它从数据模型刷新视图模型并将IsDirty设置为false。

CanSaveCanCancel属性(假设您对这些命令使用RelayCommand )只返回IsDirty的当前值。

请注意,由于此function都不依赖于视图模型的特定实现,因此可以将其放在抽象基类中。 派生类不必实现任何与命令相关的属性或IsDirty属性; 他们只需要覆盖BeginEditEndEditCancelEdit

我已经完成了一些关于在我的ViewModel中包装的模型实现IsDirty的工作。

结果真的简化了我的ViewModel:

 public class PersonViewModel : ViewModelBase { private readonly ModelDataStore data; public PersonViewModel() { data = new ModelDataStore(new Person()); } public PersonViewModel(Person person) { data = new ModelDataStore(person); } #region Properties #region Name public string Name { get { return data.Model.Name; } set { data.SetPropertyAndRaisePropertyChanged("Name", value, this); } } #endregion #region Age public int Age { get { return data.Model.Age; } set { data.SetPropertyAndRaisePropertyChanged("Age", value, this); } } #endregion #endregion } 

Code @ http://wpfcontrols.codeplex.com/在Patterns程序集和MVVM文件夹下检查,你会找到一个ModelDataStore类。

PS我还没有对它进行全面测试,只是你会找到测试组件的真正简单的测试。

我建议你使用GalaSoft MVVM Light Toolkit,因为它比DIY方法更容易实现。

对于脏读,您需要保留每个字段的快照,并从UserSaveCanExecute()方法返回true或false,这将相应地启用/禁用命令按钮。

如果您想采用框架方法而不是自己编写基础结构,可以使用CSLA( http://www.lhotka.net/cslanet/ ) – Rocky的框架来开发业务对象。 在属性更改时管理对象状态,代码库还包括支持底层模型的示例ViewModel类型,Save动词和CanSave属性。 您可以从代码中获取灵感,即使您不想使用该框架。

我想出了一个有效的解决方案。 这当然不是最好的方式,但我相信我可以继续努力,因为我了解更多……

当我运行项目时,如果我插入任何项目,则禁用列表框,并启用保存按钮。 如果我撤消编辑,则会再次启用列表框,并禁用保存按钮。

我已经更改了我的用户模型以实现INotifyPropertyChanged,我还创建了一组私有变量来存储“原始值”和一些逻辑来检查“IsDirty”

 using System.ComponentModel; namespace Test.Model { public class User : INotifyPropertyChanged { //Private variables private string _username; private string _surname; private string _firstname; //Private - original holders private string _username_Orig; private string _surname_Orig; private string _firstname_Orig; private bool _isDirty; //Properties public string UserName { get { return _username; } set { if (_username_Orig == null) { _username_Orig = value; } _username = value; SetDirty(); } } public string Surname { get { return _surname; } set { if (_surname_Orig == null) { _surname_Orig = value; } _surname = value; SetDirty(); } } public string Firstname { get { return _firstname; } set { if (_firstname_Orig == null) { _firstname_Orig = value; } _firstname = value; SetDirty(); } } public bool IsDirty { get { return _isDirty; } } public void SetToClean() { _username_Orig = _username; _surname_Orig = _surname; _firstname_Orig = _firstname; _isDirty = false; OnPropertyChanged("IsDirty"); } private void SetDirty() { if (_username == _username_Orig && _surname == _surname_Orig && _firstname == _firstname_Orig) { if (_isDirty) { _isDirty = false; OnPropertyChanged("IsDirty"); } } else { if (!_isDirty) { _isDirty = true; OnPropertyChanged("IsDirty"); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

然后,我的ViewModel也发生了一些变化….

 using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows.Input; using Test.Model; using System.ComponentModel; namespace Test.ViewModel { class UserViewModel : ViewModelBase { //Private variables private ObservableCollection _users; RelayCommand _userSave; private User _selectedUser = new User(); //Properties public ObservableCollection User { get { if (_users == null) { _users = new ObservableCollection(); _users.CollectionChanged += (s, e) => { if (e.Action == NotifyCollectionChangedAction.Add) { // handle property changing foreach (User item in e.NewItems) { ((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) => { OnPropertyChanged("EnableListBox"); }; } } }; //Populate with users _users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"}); _users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"}); } return _users; } } public User SelectedUser { get { return _selectedUser; } set { _selectedUser = value; } } public bool EnableListBox { get { return !_selectedUser.IsDirty; } } //Commands public ICommand UserSave { get { if (_userSave == null) { _userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute); } return _userSave; } } void UserSaveExecute() { //Here I will call my DataAccess to actually save the data //Save code... _selectedUser.SetToClean(); OnPropertyChanged("EnableListBox"); } bool UserSaveCanExecute { get { return _selectedUser.IsDirty; } } //constructor public UserViewModel() { } } 

最后,XAML我更改了Username,Surname和Firstname上的绑定以包含UpdateSourceTrigger=PropertyChanged然后我绑定了列表框的SelectedItem和IsEnabled

正如我在开始时说的那样 – 它可能不是最好的解决方案,但似乎有效…

由于您的UserSave命令在ViewModel中,我会在那里跟踪“脏”状态。 我将数据绑定到ListBox中的选定项目,当它更改时,存储所选用户属性的当前值的快照。 然后您可以与此进行比较以确定是否应启用/禁用该命令。

但是,由于您直接绑定到模型,因此需要某种方法来确定是否有更改。 您还可以在模型中实现INotifyPropertyChanged,或者将属性包装在ViewModel中。

请注意,当命令的CanExecute更改时,您可能需要触发CommandManager.InvalidateRequerySuggested()。

这就是我实现IsDirty的方式。 在ViewModal中为User类的每个属性创建一个包装器(使用IPropertyChangedinheritanceUser类并在User类中实现onpropertychanged不会有帮助)。 您需要将绑定从UserName更改为WrapUserName。

 public string WrapUserName { get { return User.UserName } set { User.UserName = value; OnPropertyChanged("WrapUserName"); } } 

现在有一个属性

  public bool isPageDirty { get; set; } 

由于您的viewmodalinheritance自baseviewmodal,而baseviewmodal实现onPropertyChanged。

 UserViewModel.PropertyChanged += (s, e) => { isPageDirty = true; }; 

如果任何属性改变,isPageDirty将为true,所以在保存你的时候检查isPageDirty。