2010-01-18 12 views
60

Mi piace MVVM. Non lo amo, ma mi piace. La maggior parte ha senso. Ma continuo a leggere articoli che incoraggiano a scrivere molto codice in modo da poter scrivere XAML e non dover scrivere alcun codice nel code-behind.MVVM Madness: comandi

Lasciatemi fare un esempio.

Recentemente ho voluto collegare un comando nel mio ViewModel a ListView MouseDoubleClickEvent. Non ero abbastanza sicuro di come farlo. Fortunatamente, Google ha risposte per tutto. Ho trovato i seguenti articoli:

Mentre le soluzioni sono stati utili per la mia comprensione di comandi, ci sono stati problemi. Alcune delle suddette soluzioni hanno reso il progettista WPF inutilizzabile a causa di un errore comune di aggiungere "Interno" dopo una proprietà di dipendenza; il progettista WPF non riesce a trovarlo, ma il CLR può. Alcune soluzioni non consentivano più comandi allo stesso controllo. Alcune soluzioni non hanno consentito i parametri.

Dopo aver sperimentato per qualche ora ho deciso di fare questo:

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { 
    ListView lv = sender as ListView; 
    MyViewModel vm = this.DataContext as MyViewModel; 

    vm.DoSomethingCommand.Execute(lv.SelectedItem); 
} 

Così, i puristi MVVM, prego mi dicono cosa c'è di sbagliato in questo? Posso ancora Unit testare il mio comando. Questo sembra molto pratico, ma sembra violare le linee guida di "ZOMG ... hai il codice nel tuo code-behind !!!!" Per favore condividi i tuoi pensieri.

Grazie in anticipo.

+1

L'ho fatto anch'io, non vedo alcun problema ... – Schneider

+7

CODICE ZOMFG DIETRO !!!!!!! – Pierreten

+1

Sono d'accordo con @Schneider: questo ha perfettamente senso. In effetti, il volume del codice idraulico necessario per evitare l'uso del cosiddetto file code-behind mi fa girare la testa. –

risposta

37

Penso che il difetto risieda nel requisito di purezza. I modelli di progettazione, MVVM incluso, sono uno strumento nella casella degli strumenti, non un fine a se stessi. Se ha più senso rompere con la purezza del modello per un caso ben ponderato (e sembra chiaramente che tu abbia considerato questo caso), quindi rompere con il modello.

Se ciò funziona per voi e non credete che sia un onere di manutenzione eccessivo, allora direi che non c'è niente di sbagliato in ciò che avete fatto. Penso che tu abbia chiaramente incontrato l'onere della prova per dimostrare che questa è una soluzione ragionevole al tuo problema, nonostante ciò che potrebbe essere una pura implementazione MVVM.

(Considero questo argomento simile agli argomenti per i linguaggi multiparadigm.Poiché un approccio OO puro può essere applicato, a volte fare le cose in un modo più funzionale è più appropriato.Anche se un approccio Pure Functional può essere applicato, a volte lo scambio off mostra che le tecniche OO valgono più del tempo.)

+9

Concordato, la purezza eccessiva, sia con MVVM o qualsiasi altro modello, diventa controproducente. Ho visto persone dire che non dovresti nemmeno usare convertitori di valore perché il tuo modello di visualizzazione dovrebbe fare tutto il lavoro - una ricetta infallibile per l'accoppiamento stretto e il fallimento dell'architettura. MVVM è un (molto) utile principio guida per * come pensare * in WPF in modo da rendere il tuo codice semplice e disaccoppiato. Non dovrebbe mai diventare un bastone con cui battere il tuo codice in un pasticcio innaturale di complicata complessità. – itowlson

+1

La domanda originale rispecchia le mie frustrazioni provando a fare MVVM puro e mi piace la tua risposta, tuttavia vorrei sottolineare che quando si apprende un nuovo pattern, in particolare uno come othello-like come MVVM (facile da imparare, difficile da padroneggiare), il modo migliore per approcciare quell'apprendimento è fare tutto il possibile per costringerti a diventare puri. Solo dopo aver imparato le dure lezioni puoi acquisire l'esperienza necessaria per sapere quando e come rompere il modello. – Randolpho

+1

@Randolpho: Tu fai un punto molto buono e convincente, e sono completamente d'accordo. Mi sforzo per la purezza della mia comprensione del modello nella maggior parte dei miei progetti personali, e certamente i più piccoli. Penso che MVVM in particolare sia complicato perché c'è così tanta area grigia rispetto a ciò che appartiene alla vista rispetto al modello di vista. La mia opinione personale è che tutto ciò che non influisce sullo stato dell'applicazione rimane fuori dalla VM e nella vista (forse anche il codebehind!), Ma sto scrivendo qualcosa di simile a un MVP? Boh. Roba molto sottile! –

13

Sono d'accordo con te sul fatto che molte soluzioni MVVM-Command sono troppo complicate. Personalmente, utilizzo un approccio misto e definisco i miei comandi nella vista piuttosto che nel ViewModel, utilizzando metodi e proprietà dal ViewModel.

XAML:

<Window.Resources> 
    <RoutedCommand x:Key="LookupAddressCommand" /> 
</Window.Resources> 
<Window.CommandBindings> 
    <CommandBinding Command="{StaticResource LookupAddressCommand}" x:Name="cmdLookupAddress" /> 
</Window.CommandBindings> 

Codice (Visualizza):

Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute 
    e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2) 
End Sub 

Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed 
    myViewModel.LookupAddress() 
End Sub 

Non è pura MVVM, ma semplice, funziona, non ha bisogno di particolari MVVM-Comando-classi e rende il tuo codice è molto più facile da leggere per gli esperti non MVVM (= i miei colleghi di lavoro).

+0

Questo è intelligente :) – cwap

5

Credo che l'obiettivo di avere "Nessun codice nel code-behind" è esattamente questo, un obiettivo da raggiungere - non qualcosa che dovresti prendere come dogma assoluto. Nella Vista ci sono posti appropriati per il codice, e questo non è necessariamente un cattivo esempio di dove o come il codice possa essere più semplice di un approccio alternativo.

Il vantaggio degli altri approcci elencati, incluse le proprietà associate o gli eventi collegati, è che sono riutilizzabili. Quando colleghi direttamente un evento, poi fai quello che hai fatto, è molto facile finire per duplicare quel codice nell'intera applicazione. Creando una singola proprietà associata o un evento per gestire tale cablaggio, aggiungi del codice aggiuntivo nell'impianto idraulico, ma è un codice riutilizzabile per qualsiasi ListView in cui si desidera la gestione con doppio clic.

Ciò detto, io preferisco usare l'approccio più "purista". Mantenere tutta la gestione degli eventi fuori dalla vista potrebbe non influenzare lo scenario di test (che specificatamente si indirizza), ma influenza la designabilità e la manutenibilità generali. Introducendo il codice nel codice, si limita la vista a utilizzare sempre un controllo ListView con il gestore eventi cablato, che lega la vista al codice e limita la flessibilità di riprogettazione da parte di un designer.

10

Anche se preferisco non scrivere codice in coda quando si utilizza il pattern MVVM, penso che sia OK farlo finché il codice è puramente correlato all'interfaccia utente.

Ma questo non è il caso qui: stai chiamando un comando di modello di visualizzazione dal code-behind, quindi non è puramente relativo all'interfaccia utente, e la relazione tra la vista e il comando del modello di vista non è direttamente apparente in XAML.

Penso che si potrebbe facilmente farlo in XAML, utilizzando attached command behavior. In questo modo è possibile "legare" il MouseDoubleClick evento a un comando della visualizzazione modello:

<ListView ItemSource="{Binding Items}"> 
    <local:CommandBehaviorCollection.Behaviors> 
     <local:BehaviorBinding Event="MouseDoubleClick" Action="{Binding DoSomething}" /> 
    </local:CommandBehaviorCollection.Behaviors> 

    ... 
</ListView> 

si può anche facilmente accedere alla voce selezionata del ListView senza fare riferimento ad esso direttamente, utilizzando l'interfaccia ICollectionView:

private ICommand _doSomething; 

public ICommand DoSomething 
{ 
    get 
    { 
     if (_doSomething == null) 
     { 
      _doSomething = new DelegateCommand(
       () => 
       { 
        ICollectionView view = CollectionViewSource.GetDefaultView(Items); 
        object selected = view.CurrentItem; 
        DoSomethingWithItem(selected); 
       }); 
     } 
     return _doSomething; 
    } 
} 
+0

intendevo mettere questo link pure. Ma questo è uno di quelli che rende inutilizzabile il designer WPF. Almeno lo ha fatto per me. Forse ho fatto qualcosa di sbagliato? –

+5

+0

Ah sì, si rompe il progettista ... L'avevo dimenticato. Ad ogni modo, come sottolinea con umorismo Greg D, questo designer non funziona quasi mai;). Non lo uso mai, scrivo solo XAML ... –

1

Il comando è per i pezzi. I veri uomini legano il loro intero ui agli eventi in codebehind.

+4

No, * REAL * uomini scrivono i loro algoritmi in XAML. –

+5

Fumetto obbligatorio xkcd sui programmatori reali: http://xkcd.com/378/ – dthrasher

2

Ciò che @JP descrive nella domanda originale e @Heinzi accenna a è la risposta è un approccio pragmatico alla gestione di comandi difficili. L'utilizzo di un piccolo frammento di codice per la gestione degli eventi nel codice sottostante è particolarmente utile quando è necessario eseguire una piccola interfaccia utente prima di richiamare il comando.

Considerare il caso classico di OpenFileDialog. È molto più semplice utilizzare un evento click sul pulsante, visualizzare la finestra di dialogo e quindi inviare i risultati a un comando sul ViewModel piuttosto che adottare una qualsiasi delle complesse routine di messaggistica utilizzate dagli strumenti MVVM.

Nel vostro XAML:

<Button DockPanel.Dock="Left" Click="AttachFilesClicked">Attach files</Button> 

Nel codice dietro:

private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e) 
    { 
     // Configure open file dialog box 
     Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); 
     dlg.FileName = "Document"; // Default file name 
     dlg.DefaultExt = ".txt"; // Default file extension 
     dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension 

     // Show open file dialog box 
     bool? result = dlg.ShowDialog(); 

     // Process open file dialog box results 
     if (result == true) 
     { 
      string filename = dlg.FileName; 

      // Invoke the command. 
      MyViewModel myViewModel = (MyViewModel)DataContext; 
      if (myViewModel .AttachFilesCommand.CanExecute(filename)) 
      { 
       noteViewModel.AttachFilesCommand.Execute(filename); 
      } 
     } 
    } 

Programmazione informatica è inflessibile. Noi programmatori dobbiamo essere flessibili per poterlo affrontare.

2

Il disaccoppiamento è una delle principali funzionalità di MVVM. Se supponiamo di voler cambiare, ad esempio, visualizza o associa il modello. Quanto è facile per la tua applicazione?

Prendi un esempio in cui View1 e View2 condividono lo stesso ViewModel. Ora implementerai il codice dietro il metodo per entrambi.

Inoltre, Supponiamo che se è necessario modificare il ViewModel per una vista sulla fase successiva vostro comando andranno fallire come vista del modello è cambiato e la dichiarazione

MyViewModel vm = this.DataContext as MyViewModel; 

restituirà incidente codice di nulla e di conseguenza. Quindi arriva un ulteriore onere a cambiare anche il codice. Questo tipo di scenari sorgeranno se lo farai in questo modo.

Naturalmente ci sono molti modi per ottenere la stessa cosa nella programmazione, ma quale è il migliore che porterà al miglior approccio.