Wie kann ich verwaltete WPF-Befehle in meinem ViewModel ohne Code-Behind umgehen?

8

Nach meinem Verständnis von MVVM ist es eine gute Vorgehensweise, geroutete Befehle direkt im ViewModel zu behandeln.

Wenn ein weitergeleiteter Befehl in einem ViewModel als RelayCommand (oder DelegateCommand) definiert wird, ist es einfach, direkt an den Befehl zu binden: Command = {Binding MyViewModelDefinedCommand}.

Für routed-Befehle, die außerhalb von ViewModel definiert sind, handle ich diese Befehle im Code hinter dem View und leite Aufrufe an das ViewModel weiter. Aber ich finde es peinlich, dass ich das tun muss. Es widerspricht empfohlenen MVVM Good Practices. Ich denke, dass es einen eleganteren Weg geben sollte, diesen Job zu erreichen.

Wie kann ich einen "System.Windows.Input.ApplicationCommands" oder einen anderen gerouteten Befehl behandeln, der außerhalb des Viewmodels direkt im ViewModel definiert ist? Mit anderen Worten, wie kann ich den CommandBinding-Callback "CommandExecute" und / oder "CommandCanExecute" für den Befehl, der außerhalb des ViewModels definiert ist, direkt mit dem ViewModel umgehen? Ist das möglich oder nicht? Wenn ja wie? Wenn nein, warum?

    
Eric Ouellet 23.03.2016, 16:16
quelle

3 Antworten

19

Ich würde die Frage wie folgt umschreiben:

  

Wie kann ich verwaltete WPF-Befehle in meinem ViewModel ohne Code-Behind umgehen?

Darauf würde ich antworten: Große Frage!

WPF bietet keine integrierte Möglichkeit, dies zu tun, was besonders ärgerlich ist, wenn du WPF zum ersten Mal startest und jeder sagt dir, dass "Code-Behind ist böse" (das ist es wirklich). Also musst du es selbst bauen.

Wir bauen es selbst

Wie können wir also solche Funktionalität selbst erstellen? Nun, zuerst brauchen wir ein Äquivalent von CommandBinding :

%Vor%

Und dann brauchen wir eine Klasse, die den RoutedCommandHandler tatsächlich mit einem bestimmten Element verbindet. Dazu erstellen wir eine Sammlung von RoutedCommandHandler s als angefügte Eigenschaft, wie folgt:

%Vor%

Dann ist es so einfach wie die Klassen auf unserem Element:

%Vor%

Interaktion.Behavior-Implementierung

Wenn Sie das oben genannte wissen, könnten Sie dann fragen:

  

Wow, das ist großartig, aber das ist eine Menge Code. Ich verwende Expression Behaviors bereits, also gibt es eine Möglichkeit, dies ein wenig zu vereinfachen?

Darauf würde ich antworten: Große Frage!

Wenn Sie bereits Interaction.Behaviors verwenden, können Sie stattdessen die folgende Implementierung verwenden:

%Vor%

Mit dem entsprechenden XAML:

%Vor%     
FriendlyGuy 28.03.2016, 01:31
quelle
1

Hier habe ich ein einfaches Beispiel für das Binden eines Befehls an eine Schaltfläche:

MainWindow.xaml

%Vor%

MainWindow.xaml.cs

%Vor%

MainWindowViewModel.cs

%Vor%     
un-lucky 28.03.2016 01:29
quelle
0

Die angenommene Antwort ist sehr nett, aber es scheint, dass der OP nicht ganz verstanden hat, wie RoutedCommands funktioniert und das einige Verwirrung verursachte. Zitat aus der Frage:

  

Wenn ein weitergeleiteter Befehl in einem ViewModel als RelayCommand definiert ist (oder   DelegateCommand), ist es einfach, direkt an den Befehl wie zu binden   this: Befehl = {Binding MyViewModelDefinedCommand}.

Das ist zweideutig, aber so oder so ist es falsch:

  1. Entweder - man kann keinen RoutedCommand als Relay / DelegateCommand definieren, weil RoutedCommand eine andere Implementierung der ICommand-Schnittstelle ist.
  2. Oder - wenn eine VM einen tatsächlichen RoutedCommand verfügbar macht, wird immer noch das gleiche Problem auftreten wie bei den RoutedCommands, die außerhalb der VM definiert sind (aufgrund der Funktionsweise von RoutedCommands).

RoutedCommand ist eine spezifische Implementierung von ICommand

Die Execute / CanExecute-Methoden von RoutedCommand enthalten keine Anwendungslogik (wenn Sie einen RoutedCommand instanziieren, übergeben Sie Execute / CanExecute-Delegaten nicht). Sie heben Routingereignisse auf , die wie andere Routingereignisse den Elementbaum durchqueren. Diese Ereignisse (PreviewCanExecute, CanExecute, PreviewExecuted, Executed) suchen nach einem Element mit CommandBinding für diesen RoutedCommand. Das CommandBinding-Objekt verfügt über Ereignisbehandlungsroutinen für diese Ereignisse, und hier kommt unsere Anwendungslogik zum Tragen (jetzt ist klar, warum das Aussetzen eines RoutedCommands von Ihrer VM das Problem nicht löst).

%Vor%

xaml:

%Vor%

CommandBinding-Objekt

Die CommandBinding-Klasse erbt nicht von DependencyObject (ihre Command-Eigenschaft kann nicht an einen Befehl gebunden sein, der auf der VM verfügbar ist). Sie können Ereignishandler verwenden, die an eine CommandBinding angehängt sind, um den Anruf (in Code-Behind) an die VM weiterzuleiten - dort ist nichts wichtig, keine Logik (nichts zu testen). Wenn Sie keinen Code-Behind wollen, dann hat die angenommene Antwort eine schöne Lösung (macht diese Weiterleitung für Sie).

    
djomlastic 04.11.2017 16:00
quelle

Tags und Links