2009-07-07 10 views
18

Come posso implementare l'annullamento della modifica di un oggetto utilizzando MVVM.Come annullare una modifica a un oggetto usando MVVM?

Ad esempio: ho un elenco di clienti. Scelgo un cliente e clicco sul pulsante "Modifica", si apre una finestra di dialogo (DataContext è vincolato a CustomerViewModel) e inizio a modificare i campi del cliente. E poi decido di annullare la modifica, ma i campi del cliente sono già stati modificati, quindi come posso restituire un cliente al suo stato precedente in MVVM?

risposta

11

Controllare l'interfaccia IEditableObject. La tua classe Customer dovrebbe implementarlo e i tuoi comandi possono eseguire BeginEdit/CancelEdit/EndEdit come appropriato.

+0

IEditableObject crea un sacco di spese generali per gli oggetti, soprattutto se gli oggetti del modello sono una classe e non una stringa, è necessario riscrivere gli oggetti del modello per supportarlo. – Agies

+6

@Agies: perché il downvote? Se IEditableObject è un "sovraccarico" o non dipende interamente dalla tua infrastruttura o da come desideri implementarla. È solo un'interfaccia che WPF comprende. Come lo realizzi dipende da te. –

+0

+1, sì, voglio implementarlo con IEditableObject, ma ho un ViewModelBase che espone una proprietà Modello di tipo TModel e leghiamo la vista direttamente alle proprietà del modello esposto. Ora, come posso ancora usare "Annulla modifica", inoltre, ora che conosci il mio scenario, dì che il mio TModel è un'entità indirizzo. in modalità di visualizzazione si collega solo alla riga di FullAddress e utilizza AddressDataTemplate (creando un collegamento a GMaps), ma voglio che quando l'utente fa clic sul pulsante Modifica su AddressView, dovrebbe aprire ChildWindow (SL, o qualsiasi altra finestra in WPF) continua ... – Shimmy

3

In this article, Raul ricarica nuovamente l'oggetto dal DB. Immagino che sia meno problematico della soluzione che 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

Penso che ricaricare dal DB sia un modo per adempiere agli obblighi di IEditableObject del suggerimento del signor Boogaarts, non necessariamente un'alternativa allo stesso. – Guge

0

Si potrebbe anche, nei vostri ViewModel copiare lo stato del modello di campi interni, e quindi esporre questi e poi impostare solo sul modello, quando l'utente si impegna in realtà il cambiamento.

Il problema potrebbe essere che la convalida al volo sarà più problematica se la convalida si basa sull'entità che si sta aggiornando - se questo è un requisito è possibile creare un clone del modello su cui lavorare e quindi unire il clone con l'entità reale quando viene salvata.

4

Un modo molto semplice, se l'oggetto è già serializzabile, ad esempio se si sta utilizzando WCF. È possibile serializzare l'oggetto originale in un campo interno. Se il tuo oggetto non è serializzabile, usa semplicemente AutoMapper per creare una copia del tuo oggetto con una riga di codice.

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

Quando gestisci il tuo CancelCommand, chiama AutoMapper al contrario. Poiché le tue proprietà hanno già una notifica di modifica, tutto funziona correttamente. È possibile che si possano combinare queste tecniche con IEditableObject, se è necessario e si desidera scrivere il codice aggiuntivo.

+0

Il lato negativo di AutoMapper è che acquisisce solo lo stato riflesso nelle Proprietà. Così come altre soluzioni che presuppongono che ogni pezzo di stato per ogni classe modificabile è esposto attraverso proprietà di lettura/scrittura, questo dovrebbe funzionare bene per gli scenari che usano DTO o altri oggetti anemici simili ma nel caso in cui ci siano oggetti di dominio ricchi non essere affatto proprietà. La serializzazione binaria e NetDataContractSerialization non presentano questo problema poiché funzionano con i campi. A proposito, è complicato quando eventi e delegati entrano in gioco. – jpierson

0

È possibile utilizzare l'associazione con UpdateSourceTrigger = Explicit. Here puoi trovare maggiori informazioni su come questo può essere implementato.

+2

Una buona risposta dovrebbe essere più specifica, meglio se include un esempio di codice. –

+1

Sebbene questa risposta manchi di dettagli, penso che sia la soluzione più pulita. UpdateSourceTrigger = Explicit è fatto esattamente per ciò che Terkri sta cercando di fare. Le altre soluzioni, mentre possono funzionare, sono in realtà solo hack. – user2780436

+0

Se la convalida si basa sulle modifiche all'origine del monitoraggio (tramite 'IDataErrorInfo' per esempio), allora non si otterrà la convalida fino a quando non si aggiornerà l'origine (se non lo sono, non sono sicuro), a quel punto si potrebbe ancora voler essere in grado di ripristinare tutte le modifiche, ma il tuo modello sarà già aggiornato. – Dan

0

Sulla base Камен Великов's answer:

È possibile contrassegnare le associazioni come essere aggiornati manualmente mediante la definizione di

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

a suo parere (XAML). Quindi, si deve scrivere le modifiche dal l'interfaccia utente in ViewModel chiamando

yourTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
quando si fa clic su Salva.

Nota: se vengono aggiornati alla sorgente di associazione attivata da qualcos'altro, vengono comunque visualizzati direttamente nell'interfaccia utente.

1

Ho avuto anche questo problema. L'ho risolto usando "The Memento Pattern Design". Con questo modello è possibile salvare facilmente una copia dell'oggetto originale e, in selectedIndexChange (di un controllo) o nel pulsante Annulla, è possibile ripristinare facilmente la versione precedente del proprio oggetto.

Un esempio di utilizzo di questo modello è disponibile presso How is the Memento Pattern implemented in C#4?

Un esempio di codice:

Se abbiamo un utente classe con proprietà Nome utente Password e NombrePersona abbiamo bisogno di aggiungere metodi CreateMemento e 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; 
    } 

Poi, abbiamo bisogno di una Memento classe che conterrà la "copia" del nostro oggetto come questo:

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

e abbiamo bisogno di una classe che genera e contiene il nostro oggetto ricordo:

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

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

} 

Poi, per attuare questo modello dobbiamo creare un'istanza di Caretaker classe

Caretaker creadorMemento = new Caretaker(); 

e creare il nostro oggetto ricordo quando è stato selezionato un nuovo utente per la modifica, ad esempio in selectedIndexChange dopo l'inizializzazione di SelectedUser, utilizzo il metodo per l'evento RaisPropertyChanged in questo modo:

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 spiegazione per questo, quando selectedIndexChanged valore di variazione controlliamo se UserSelected e our memento object non sono nulli significa che il nostro articolo reale in modalità di modifica è cambiato allora dobbiamo ripristinare il nostro oggetto con il metodo SetMemento. E se la nostra proprietà UserSelected cambia e non è nulla, "Crea il nostro oggetto Memento" che useremo quando la modifica è stata annullata.

Per finire, abbiamo utilizzare il metodo SetMemento in ogni metodo che abbiamo bisogno di cancellare l'edizione, e quando la modifica è impegnata come nel SaveCommand possiamo impostare nullo il nostro oggetto ricordo come questo this.creadorMemento = null.

Problemi correlati