2012-02-05 15 views
6

Sto cercando di implementare il pattern MVVM (Model View ViewModel) per l'applicazione WinForms. Sto usando C# 2005.Implementazione MVVM per l'applicazione WinForm

La mia applicazione ha un MainForm (vista) con 2 caselle di testo multi linea e 3 pulsanti. Lo scopo della prima casella di testo è mostrare un commento in corso su cosa sta facendo l'applicazione, quando si fa clic sul pulsante. Continuo ad aggiungere linee al TextBox per aggiornare l'utente su ciò che sta accadendo. Lo scopo della seconda casella di testo è di aggiornare l'utente su qualsiasi condizione di errore, conflitti, valori duplicati; in breve, tutto ciò che è richiesto dall'utente per la revisione. Classifica ogni messaggio come INFO o AVVISO o ERRORE. Ciascuno dei 3 pulsanti esegue un'azione e continua ad aggiornare le 2 caselle di testo.

Ho creato una classe MainFormViewModel.

1a domanda: Quando l'utente fa clic sul pulsante in MainForm, devo cancellare il contenuto delle 2 caselle di testo e disabilitare il pulsante in modo che non possa più essere cliccato di nuovo fino al termine della prima operazione. Dovrei fare questo aggiornamento di caselle di testo e pulsanti direttamente nel MainForm o dovrei usare MainFormViewModel in qualche modo?

Seconda domanda: Il clic sul pulsante chiama un metodo sulla classe MainFormViewModel. Prima di chiamare il metodo e dopo aver chiamato il metodo, voglio mostrare un messaggio nella prima casella di testo qualcosa come "Operazione A iniziata/conclusa". Lo faccio chiamando una classe Common che ha un metodo Log per registrare i messaggi su un TextBox o su un file o entrambi. Ancora una volta se è giusto farlo direttamente dal MainForm? Io chiamo questo metodo di registrazione all'inizio e alla fine del gestore di eventi.

3a domanda: Come si propagano i messaggi di errore da ViewModel a View? Ho creato una classe di eccezioni personalizzata "TbtException". Quindi devo scrivere 2 blocchi di cattura in ogni pulsante, uno per TbtException e altro per la classe di eccezione genetica?

Grazie.

risposta

3

È necessario eseguire operazioni nella vista solo per quanto riguarda lo stato dell'oggetto ViewModel. Per esempio. non si deve presumere che il modello di vista stia calcolando quando si fa clic su un pulsante, ma è necessario aggiungere uno stato al modello di vista che dice che sta facendo qualcosa di più lungo e quindi riconoscere tale stato nella vista. Non dovresti disabilitare o abilitare i pulsanti nella vista come preferisci, ma solo se c'è uno stato che richiede la modifica di questi pulsanti. Questo può arrivare fino ad avere una proprietà che indica quale elemento in un elenco è attualmente selezionato, quindi l'interfaccia utente non chiama il membro SelectedItem del controllo elenco, ma quello del viewmodel. E quando l'utente fa clic su Rimuovi, il modello di visualizzazione rimuoverà il membro selezionato dal suo elenco e la vista verrà automaticamente aggiornata attraverso le modifiche di stato sotto forma di eventi.

Ecco come definirei un modello di visualizzazione per la visualizzazione. Espone i messaggi attraverso una raccolta osservabile a cui la vista può legare (ad esempio registra gestori di eventi, poiché l'associazione non è ben supportata in WinForms). La casella di testo in qualsiasi momento restituisce solo il contenuto della raccolta. Ha azioni per cancellare quelle collezioni che la tua vista può chiamare. La vista può anche chiamare azioni del modello sottostante, ma dovrebbe essere aggiornata solo tramite viewmodel! La vista non dovrebbe mai registrare alcun gestore di eventi per gli eventi esposti dal modello sottostante. Se vuoi farlo, devi collegare quell'evento nel modello di visualizzazione ed esporlo alla vista lì. A volte ciò può sembrare "solo un altro livello di riferimento indiretto", motivo per cui potrebbe essere eccessivo per applicazioni molto semplici come la tua.

public class MainFormViewModel : INotifyPropertyChanged { 
    private object syncObject = new object(); 

    private MainFormModel model; 
    public virtual MainFormModel Model { 
    get { return model; } 
    set { 
     bool changed = (model != value); 
     if (changed && model != null) DeregisterModelEvents(); 
     model = value; 
     if (changed) { 
     OnPropertyChanged("Model"); 
     if (model != null) RegisterModelEvents(); 
     } 
    } 
    } 

    private bool isCalculating; 
    public bool IsCalculating { 
    get { return isCalculating; } 
    protected set { 
     bool changed = (isCalculating != value); 
     isCalculating = value; 
     if (changed) OnPropertyChanged("IsCalculating"); 
    } 
    } 

    public ObservableCollection<string> Messages { get; private set; } 
    public ObservableCollection<Exception> Exceptions { get; private set; } 

    protected MainFormViewModel() { 
    this.Messages = new ObservableCollection<string>(); 
    this.Exceptions = new ObservableCollection<string>(); 
    } 

    public MainFormViewModel(MainFormModel model) 
    : this() { 
    Model = model; 
    } 

    protected virtual void RegisterModelEvents() { 
    Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage); 
    Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown); 
    } 

    protected virtual void DeregisterModelEvents() { 
    Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage); 
    Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown); 
    } 

    protected virtual void Model_NewMessage(object sender, SomeEventArg e) { 
    Messages.Add(e.Message); 
    } 

    protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) { 
    Exceptions.Add(e.Exception); 
    } 

    public virtual void ClearMessages() { 
    lock (syncObject) { 
     IsCalculating = true; 
     try { 
     Messages.Clear(); 
     } finally { IsCalculating = false; } 
    } 
    } 

    public virtual void ClearExceptions() { 
    lock (syncObject) { 
     IsCalculating = true; 
     try { 
     Exceptions.Clear(); 
     } finally { IsCalculating = false; } 
    } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropetyChanged(string property) { 
    var handler = PropertyChanged; 
    if (handler != null) handler(this, new PropertyChangedEventArgs(property)); 
    } 
} 

EDIT: Per quanto riguarda la gestione delle eccezioni

avrei preferito prendere eccezioni nel ViewModel che nella vista. Il modello di visualizzazione è più adatto a prepararli per la visualizzazione. Non so come funzioni in WPF. Devo ancora programmare un'applicazione in WPF, stiamo ancora facendo un sacco di WinForms.

Le opinioni possono variare, ma penso che le clausole try/catch generiche non siano in realtà un trattamento delle eccezioni. Penso che dovresti testare molto bene la tua interfaccia utente e includere la gestione delle eccezioni solo quando necessario. Questo è il motivo per cui tu collaudi il tuo modello di vista e l'utente prova la vista. Tuttavia, se si attiene davvero al principio ed evitare la logica nella vista, si può fare molto con i test unitari.

+0

Molto informativo e utile. Grazie !! – AllSolutions

+0

Quindi, per quanto riguarda la prima domanda, stai dicendo che la vista dovrebbe chiamare ViewModel per aggiornare una proprietà di stato, e le 2 caselle di testo dovrebbero collegarsi a questa proprietà e cancellarsi? E per quanto riguarda la seconda domanda, non sono ancora chiaro, come la casella di testo dovrebbe mantenere un commento in esecuzione. Sembra che ViewModel continui a scrivere i commenti in esecuzione in una variabile e TextBox dovrebbe collegarsi a quella variabile e continuare ad aggiornarsi? Scusate ma lo sto implementando per la prima volta, quindi apprezzerò un po 'di più. Si noti che il testo del commento in esecuzione sarà molto lungo. Qualche esempio di forma? – AllSolutions

+0

Per quanto riguarda la gestione degli errori, il modulo non avrà alcun blocco catch try? – AllSolutions