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?
Check out the IEditableObject
interface. Your Customer
class should implement that, and your commands can execute BeginEdit
/ CancelEdit
/ EndEdit
as appropriate.
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);
}
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.
You can use binding with UpdateSourceTrigger=Explicit. Here you can find more information how this can be implemented.
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
.
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.
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.