MVVM - implementing 'IsDirty' functionality to a ModelView in order to save data

前端 未结 7 1446
攒了一身酷
攒了一身酷 2021-02-04 07:13

Being new to WPF & MVVM I struggling with some basic functionality.

Let me first explain what I am after, and then attach some example code...

I have a scree

相关标签:
7条回答
  • 2021-02-04 07:47

    In my experience, if you implement IsDirty in your view model, you probably also want the view model to implement IEditableObject.

    Assuming that your view model is the usual sort, implementing PropertyChanged and a private or protected OnPropertyChanged method that raises it, setting IsDirty is simple enough: you just set IsDirty in OnPropertyChanged if it isn't already true.

    Your IsDirty setter should, if the property was false and is now true, call BeginEdit.

    Your Save command should call EndEdit, which updates the data model and sets IsDirty to false.

    Your Cancel command should call CancelEdit, which refreshes the view model from the data model and sets IsDirty to false.

    The CanSave and CanCancel properties (assuming you're using a RelayCommand for these commands) just return the current value of IsDirty.

    Note that since none of this functionality depends on the specific implementation of the view model, you can put it in an abstract base class. Derived classes don't have to implement any of the command-related properties or the IsDirty property; they just have to override BeginEdit, EndEdit, and CancelEdit.

    0 讨论(0)
  • 2021-02-04 07:48

    I would suggest you to use GalaSoft MVVM Light Toolkit as it is much more easier to implement than DIY approach.

    For dirty reads, you need to keep the snapshot of each fields, and return true or false from UserSaveCanExecute() method, which will enable / disable command button accordingly.

    0 讨论(0)
  • 2021-02-04 07:58

    Since your UserSave command is in the ViewModel, I would do the tracking of the "dirty" state there. I would databind to the selected item in the ListBox, and when it changes, store a snapshot of the current values of the selected user's properties. Then you can compare to this to determine if the command should be enabled/disabled.

    However, since you are binding directly to the model, you need some way to find out if something changed. Either you also implement INotifyPropertyChanged in the model, or wrap the properties in a ViewModel.

    Note that when the CanExecute of the command changes, you may need to fire CommandManager.InvalidateRequerySuggested().

    0 讨论(0)
  • 2021-02-04 08:00

    This is how I have implemented IsDirty. Create a wrapper for every property of User class (inheriting User class with IPropertyChanged and implementing onpropertychanged in User class wont help) in your ViewModal. You need to change your binding from UserName to WrapUserName.

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

    Now have a property

     public bool isPageDirty
        {
            get;
            set;
        }     
    

    Since your viewmodal inherits from baseviewmodal and baseviewmodal implements onPropertyChanged.

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

    In case any of the propertychanges,isPageDirty will be true, So while saving you chan check isPageDirty.

    0 讨论(0)
  • 2021-02-04 08:05

    If you wanted to take a framework approach rather than writing the infrastructure yourself, you could use CSLA (http://www.lhotka.net/cslanet/) - Rocky's framework for developing business objects. Object state is managed for you on property changes, and the code base also includes an example ViewModel type which supports an underlying model, a Save verb, and a CanSave property. You may be able to take inspiration from the code, even you didn't want to use the framework.

    0 讨论(0)
  • 2021-02-04 08:06

    I have come up with a working solution. This may of course not be the best way, but I am sure I can work on it as I learn more...

    When I run the project, if I cange any item, the list box is disabled, and the save button enabled. If I undo my edits, then the list box is enabled again, and the save button disabled.

    I have changed my User Model to implement INotifyPropertyChanged, and I have also created a set of private variables to store the "original values" and some logic to check for "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));
            }
        }
    }
    

    Then, my ViewModel has changed a bit too....

    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<User> _users;
        RelayCommand _userSave;
        private User _selectedUser = new User();
    
        //Properties
        public ObservableCollection<User> User
        {
            get
            {
                if (_users == null)
                {
                    _users = new ObservableCollection<User>();
                    _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()
        {
    
        }
    
    }
    

    Finally, the XAML I changed the bindings on the Username, Surname & Firstname to include UpdateSourceTrigger=PropertyChanged And then I bound the listbox's SelectedItem and IsEnabled

    As I said in the beginning - it may not be the best solution, but it seems to work...

    0 讨论(0)
提交回复
热议问题