How to cancel an edit to an object using MVVM?

2019-02-02 13:18发布

问题:

How can I implement cancelation of editing an object using MVVM.

For example: I have a list of customers. I choose one customer an click the button "Edit", a dialog window(DataContext is binded to CustomerViewModel) opens and I start editing customer's fields. And then I decide to cancel editing, but the fields of the customer have been already changed, so how can I return a customer to its previous state in MVVM?

回答1:

Check out the IEditableObject interface. Your Customer class should implement that, and your commands can execute BeginEdit / CancelEdit / EndEdit as appropriate.



回答2:

In this article, Raul just reload the object from the DB. I guess it's less trouble than the solution Kent proposes.

    internal void Cancel(CustomerWorkspaceViewModel cvm)
    {
        Mainardi.Model.ObjectMapping.Individual dc = cvm.DataContext 
                                 as Mainardi.Model.ObjectMapping.Individual;

        int index = 0;

        if (dc.ContactID > 0 && dc.CustomerID > 0)
        {
            index = _customerCollectionViewModel.List.IndexOf(dc);
            _customerCollectionViewModel.List[index] = 
                                  _customerBAL.GetCustomerById(dc.CustomerID);
        }

        Collection.Remove(cvm);
    }


回答3:

One super easy way, if your object is already serializable, such as if you are using WCF. You can serialize your original object into an internal field. If, your object isn't serializable, then just use AutoMapper to create a copy of your object with one line of code.

Order backup = Mapper.Map<Order, Order>(order);

When you handle your CancelCommand, just call AutoMapper in reverse. Since your properties already have a change notification everything just works. Its possible you could combine these techniques with IEditableObject, if you need and want to write the extra code.



回答4:

You can use binding with UpdateSourceTrigger=Explicit. Here you can find more information how this can be implemented.



回答5:

I had this problem too. I solved it using "The Memento Pattern Design". With this pattern you could easy save a copy of your original object and, in selectedIndexChange (of a control) or in the Cancel button, you could restore easy the prior version of your object.

An example of use of this pattern is available at How is the Memento Pattern implemented in C#4?

An example of code:

If we have a class User with properties UserName Password and NombrePersona we need to add methods CreateMemento and SetMemento:

 public class Usuario : INotifyPropertyChanged
{
    #region "Implementación InotifyPropertyChanged"

    internal void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    private String _UserName = "Capture su UserName";

    public String UserName
    {
        get { return _UserName; }
        set { _UserName = value; RaisePropertyChanged("UserName"); }
    }

    private String _Password = "Capture su contraseña";

    public String Password
    {
        get { return _Password; }
        set { _Password = value; RaisePropertyChanged("Password"); }
    }

    private String _NombrePersona = "Capture su nombre";

    public String NombrePersona
    {
        get { return _NombrePersona; }
        set { _NombrePersona = value; RaisePropertyChanged("NombrePersona"); }
    }

    // Creates memento 
    public Memento CreateMemento()
    {
        return (new Memento(this));
    }

    // Restores original state
    public void SetMemento(Memento memento)
    {
        this.UserName memento.State.UserName ;
        this.Password = memento.State.Password ;
        this.NombrePersona = memento.State.NombrePersona;
    }

Then, we need a class Memento that will contain the "copy" of our object like this:

 /// <summary>
/// The 'Memento' class
/// </summary>
public class Memento
{
    //private Usuario _UsuarioMemento;
    private Usuario UsuarioMemento { get; set; }

    // Constructor
    public Memento(Usuario state)
    {
        this.UsuarioMemento = new Usuario();

        this.State.UserName = state.UserName ;
        this.State.Password = state.Password ;
        this.State.NombrePersona = state.NombrePersona ;
    }

    // Gets or sets state
    public Usuario State
    {
        get { return UsuarioMemento; }
    }
}

And we need a class that will generate and contains our memento object:

/// <summary>
/// The 'Caretaker' class
/// </summary>
class Caretaker
{
    private Memento _memento;

    // Gets or sets memento
    public Memento Memento
    {
        set { _memento = value; }
        get { return _memento; }
    }

}

Then for implement this pattern we have to create an instance of Caretaker class

Caretaker creadorMemento = new Caretaker();

And create our memento object when a new user was selected for edit, for example in selectedIndexChange after the SelectedUser has been initializing, I use the method for event RaisPropertyChanged like this:

internal void RaisePropertyChanged(string prop)
    {
        if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        if (prop == "RowIndexSelected") // This is my property assigned to SelectedIndex property of my DataGrid
        {
            if ((this.UserSelected != null) && (creadorMemento .Memento != null))
            {
                this.UserSelected.SetMemento(creadorMemento .Memento);
            }
        }
        if (prop == "UserSelected") // Property UserSelected changed and if not is null we create the Memento Object
        {
            if (this.UserSelected != null)
                creadorMemento .Memento = new Memento(this.UserSelected);
        }
    }

An explication for this, when selectedIndexChanged change value we check if UserSelected and our memento object are not null means that our actual item in edit mode has changed then we have to Restore our object with the method SetMemento. And if our UserSelected property change and is not null we "Create our Memento Object" that we will use when the edit was cancel.

For finish, we have use the SetMemento method in every method that we need to cancel the edition, and when the edit has commited like in the SaveCommand we can set null our memento object like this this.creadorMemento = null.



回答6:

You could also, in your ViewModel copy the model's state to internal fields, and then expose these and then only set them on the model, when the user actually commits the change.

Problem could be, that on-the-fly validation will be more troublesome if validation relies on the entity being updated - if this is a requirement you could create a clone of the model to work on and then merging the clone with the actual entity when it is saved.



回答7:

Based on Камен Великов's answer:

You can mark your bindings as to be updated manually by defining

<TextBox Name="yourTextBox" Text="{BindingPath=YourBinding, UpdateSourceTrigger=Explicit}" />

in your view (XAML). Then, you have to write the changes from your UI in ViewModel by calling

yourTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
when Save is clicked.

Please note, if there are updated to the binding source triggered from anything else, they are still shown directly in the UI.



标签: wpf mvvm