2009-07-07 15 views
18

¿Cómo puedo implementar la cancelación de editar un objeto usando MVVM?¿Cómo cancelar una edición de un objeto usando MVVM?

Por ejemplo: Tengo una lista de clientes. Elijo un cliente y hago clic en el botón "Editar", se abre una ventana de diálogo (DataContext está vinculado a CustomerViewModel) y empiezo a editar los campos del cliente. Y luego decido cancelar la edición, pero los campos del cliente ya han cambiado, entonces ¿cómo puedo devolver un cliente a su estado anterior en MVVM?

Respuesta

11

Revisa la interfaz IEditableObject. Su clase Customer debería implementar eso, y sus comandos pueden ejecutar BeginEdit/CancelEdit/EndEdit según corresponda.

+0

IEditableObject crea una gran cantidad de sobrecarga para sus objetos, especialmente si los objetos de su Modelo son una Clase y no una Struct, tendría que volver a escribir sus objetos de modelo para admitirlo. – Agies

+6

@Agies: ¿Por qué el voto a favor? Si IEditableObject es una "gran cantidad de gastos generales" o no, depende completamente de su infraestructura o de cómo quiera implementarla. Es solo una interfaz que WPF entiende. Cómo lo implementa depende de usted. –

+0

+1, sí, quiero implementarlo con IEditableObject, pero tengo un ViewModelBase que expone un Modelo de propiedad de tipo TModel y ato la Vista directamente a las propiedades del modelo expuesto. Ahora, ¿cómo podría seguir usando 'Cancelar edición', también, ahora que conoce mi escenario, diga que mi TModel es una entidad de Dirección. en modo de vista, solo se une a la línea FullAddress y utiliza la plantilla AddressDataTemplate (haciendo un enlace a GMaps), pero quiero que cuando el usuario haga clic en el botón Editar en la AddressView, abra una ChildWindow (SL o cualquier ventana en WPF) para continuar ... – Shimmy

3

En this article, Raul acaba de volver a cargar el objeto de la base de datos. Supongo que es menos problema que la solución que Kent propone.

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); 
    } 
+0

Creo que recargar desde el DB es una forma de cumplir las obligaciones del IEditableObject de la sugerencia del Sr. Boogaarts, no necesariamente una alternativa al mismo. – Guge

0

Se podría también, en su modelo de vista copian el estado del modelo de campos internos, y luego exponer estos y entonces sólo ponerlos en el modelo, cuando el usuario realmente confirma el cambio.

El problema podría ser que la validación sobre la marcha será más problemática si la validación se basa en la entidad que se está actualizando; si es un requisito, puede crear un clon del modelo para trabajar y fusionar el clon con la entidad real cuando se guarda.

4

Una manera muy fácil, si su objeto ya es serializable, como si está usando WCF. Puede serializar su objeto original en un campo interno. Si su objeto no es serializable, simplemente use AutoMapper para crear una copia de su objeto con una línea de código.

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

Cuando maneja su comando Cancelar, simplemente llame a AutoMapper en reversa. Como sus propiedades ya tienen una notificación de cambio, todo funciona. Es posible que pueda combinar estas técnicas con IEditableObject, si necesita y desea escribir el código adicional.

+0

El inconveniente de AutoMapper es que solo captura el estado que se refleja a través de las Propiedades. Así como otras soluciones que suponen que cada estado de cada clase editable se expone a través de propiedades de lectura/escritura, esto debería funcionar bien para escenarios que usan DTO u otros objetos anémicos similares, pero en el caso donde hay objetos de dominio rico puede no ser propiedades en absoluto. La serialización binaria y NetDataContractSerialization no tienen este problema ya que funcionan con campos. Por cierto, es complicado cuando los eventos y los delegados entran en juego. – jpierson

0

Puede usar el enlace con UpdateSourceTrigger = Explicit. Here puede encontrar más información sobre cómo esto puede implementarse.

+2

Una buena respuesta debería ser más específica, mejor si incluye un ejemplo de código. –

+1

Aunque esta respuesta carece de detalles, creo que es la solución más limpia. UpdateSourceTrigger = Explícito está hecho exactamente para lo que terkri está tratando de hacer. Las otras soluciones, aunque pueden funcionar, son en realidad solo hacks. – user2780436

+0

Si su validación se basa en el seguimiento de los cambios de origen (a través de 'IDataErrorInfo', por ejemplo), no obtendrá la validación hasta que actualice la fuente (si es así, no estoy seguro), en cuyo punto aún puede querer estar capaz de deshacer todos los cambios pero su modelo ya habrá sido actualizado. – Dan

0

Basado en Камен Великов's answer:

Puede marcar sus fijaciones para actualizarse manualmente mediante la definición de

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

en su opinión (XAML). Luego, debe escribir los cambios desde su UI en ViewModel llamando al

yourTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
cuando se hace clic en Guardar.

Tenga en cuenta que si se actualizan las fuentes de enlace activadas por cualquier otra cosa, se muestran directamente en la interfaz de usuario.

1

Tuve este problema también. Lo resolví usando "The Memento Pattern Design". Con este patrón, puede guardar fácilmente una copia de su objeto original y, en selectedIndexChange (de un control) o en el botón Cancelar, puede restaurar fácilmente la versión anterior de su objeto.

Un ejemplo de uso de este patrón está disponible en How is the Memento Pattern implemented in C#4?

Un ejemplo de código:

Si tenemos un usuario de clase con propiedades Nombre de usuario Contraseña y NombrePersona tenemos que añadir métodos CreateMemento y 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; 
    } 

Entonces, necesitamos un Memento clase que contendrá la "copia" de nuestro objeto de esta manera:

/// <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; } 
    } 
} 

y necesitamos una clase que va a generar y contiene nuestro objeto memento:

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

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

} 

Entonces, para poner en práctica esta patrón tenemos que crear una instancia de Caretaker clase

Caretaker creadorMemento = new Caretaker(); 

Y crear nuestro objeto de recuerdo cuando se seleccionó un nuevo usuario para editar, por ejemplo en selectedIndexChange después de que el Usuario Seleccionado se haya inicializado, utilizo el método para el evento RaisPropertyChanged de esta manera:

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); 
     } 
    } 

Una explicación para esto, cuando selectedIndexChanged valor de cambio en el que comprobar si UserSelected y our memento object no son nulos significa que nuestro artículo en modo de edición ha cambiado, entonces tenemos que restaurar nuestro objeto con el método SetMemento. Y si nuestra propiedad UserSelected cambia y no es nula, "Crearemos nuestro objeto de recuerdo" que usaremos cuando se canceló la edición.

Para terminar, tenemos que utilizar el método SetMemento en cada método que necesitamos para cancelar la edición, y cuando la edición se ha comprometido como en el SaveCommand podemos establecer nula nuestro objeto recuerdo como esto this.creadorMemento = null.

Cuestiones relacionadas