Hallo Community,
in diesem Tutorial möchte ich euch die grundlegenden Konzepte von Commands in WPF näher bringen. Ich werde hier alle Erklärungen ausschließlich im Zusammenhang mit dem MVVM-Pattern (Model-View-ViewModel-Pattern) liefern, das Tutorial setzt daher Kenntnisse über dieses Pattern voraus (dies schließt die nötigen XAML-Kenntnisse mit ein).
Was sind Commands und wofür brauche ich sie?
Ein Großteil der WPF-Anwärter stammt aus dem Bereich der Vorgängertechnologie WindowsForms, welches Eventbasiert funktioniert. Es ist daher nicht verwunderlich, dass häufig die Codes von Anfängern in dem Gebiet WPF nach dem selben Prinzip funktionieren - und man kann es ihnen auch nicht verübeln, denn sie kennen es ja nicht anders.
So findet man dann z.B. dergleichen Codes vor:
Und der Eventhandler befindet sich logischerweise im Codebehind des Windows, ganz wie unter WindowsForms:
Warum genau ist das jetzt schlecht?
Events im Windows-Forms-Sinne sind mit dem MVVM-Pattern, welches in WPF eigentlich Pflicht ist, nicht oder nur schwer vereinbar. Grund ist, dass sich die Logik der Anwendung im ViewModel des Windows befindet, nicht im COdebehind der Window-Klasse. Man kann natürlich tricksen und die Events irgendwie an das ViewModel weiterleiten, aber das erfordert weiteren Code im Codebehind, der dort ebensowenig was zu suchen hat wie die Eventhandler. Sinn von MVVM ist es nämlich, dass im Codebehind nur Code steht, der wirklich nur das View (die Benutzeroberfläche) betrifft, Verknüfungen an das ViewModel sollten ausschließlich über Bindings erfolgen, und die legt man am einfachsten im XAML-Code an.
Aber wie werden Events denn dann gebunden?
Die Antwort ist: gar nicht, genau hier kommen unsere Commands in Spiel, die die Events an dieser Stelle ersetzen werden. Commands in WPF basieren auf dem ICommand-Interface und stellen eine Instanz da, die eine Benutzereingabe zu der entsprechen auzuführenden Aktion delegiert. Wichtig dabei ist, dass das Ziel des Commands vom Command allein verwaltet wird, das Steuerelement, welches das Command ausführt, muss davon nichts wissen. Dadurch können wir uns von den Handlern in der Window-Klasse verabschieden und alles ins ViewModel verlagern, wos hingehört. Zusätzlich können Commands dem View mitteilen, ob sie überhaupt ausgeführt werden können, die Steuerelemente können darauf entsprechend (z.B. mit Ausgrauung) reagieren.
Relay-Commands
Die simpelste Art von Commands sind die Relay-Commands. Sie tun nichts weiter als eine ihnen (über einen Delegaten) zugewiesene Aktion auszuführen.
Leider existiert in der Standard-WPF-Klassenbibliothek keine universelle RelayCommand-Klasse, diese gibts nur mit TeamFoundation. Damit man sich aber sparen kann, für jedes Command eine neue Klasse zu implementieren, kann man sich einfach diese relativ simple Klasse selbst bauen. Die hier präsentierte Version stammt von mir und ist selbstverständlich nicht zwangsläufig das Nonplusultra.
C#
VB.Net
Bemerkenswert an dieser Klasse ist, dass sie selbst erkennt, wann sich der Rückgabewert des canExecuteEvaluators ändert, ihr müsst also nicht erst das Event manuell auslösen. Dahinter steckt der CommandManager, dessen Magie ich selbst nicht erklären kann, aber es funktioniert.
Mit Hilfe des RelayCommand können wir jetzt das schlechte Beispiel von weiter oben korrigieren. Wir statten das ViewModel mit dem Command für den Button als Eigenschaft aus, damit wir daran binden können. Im Konstruktor können wir dann das RelayCommand erstellen und die auzuführende Funktion auswählen sowie optional eine Ausführungsbedingung.
C#
VB.Net
Im XAML können wir dann die ButtonCommand-Eigenschaft an die Command-Eigenschaft des Buttons binden, sofern das ViewModel korrekt als DataContext festgelegt wurde. Die Namensgebung für den Button wird hier auch redundant, ein Zeichen, dass wir auf dem richtigen Weg sind, in WPF und MVVM benötigen die wenigsten Steuerelemente einen Namen.
Routed-Commands
Manchmal möchte man Commands nicht an das ViewModel sondern an bestimmte Controls senden, zum Beispiel um einer Textbox mitzuteilen, sie solle ihren Inhalt in die Zwischenablage kopieren. Im Gegensatz zum RelayCommand existiert in der WPF-Klassenbibliothek bereits eine RoutedCommand-Klasse, die wir verwenden können.
Routed-Commands sind statisch und können von mehreren Steuerelementen empfangen werden (welches Steuerelement dann tatsälich das Command ausführt, wird durch den VisualTree bestimmt, dazu später mehr). Einige Standard-Commands sind in der statischen ApplicationCommands-Klasse vordefiniert, die auch teilweise von den Standard-Stuerelementen bereits behandelt werden (z.B. reagiert die Textbox auf ApplicationCommands.Copy). Im Gegensatz zu den Relay-Commands bestimmt bei den Routed-Commands das Empfängersteuerelement das Verhalten des Commands, dies geschieht über sog. Commandbindings, die direkt im XAML des Steuerelements oder im Codebehind definiert werden können. Wenn also einem bestehenden Steuerelement ein Commandbinding hinzugefügt werden soll, muss eine abgeleitete Klasse implementiert werden, bei Usercontrols sind XAML und Codebehind ohnehin schon vorhanden.
Der Einfachheit halber habe ich hier ein Commandbinding am MainWindow eingerichtet, da wir uns so die Erstellung eines Usercontrols oder eines abgeleiteten Controls sparen können. Die Erstellung erfolgt hier im XAML und gebunden wir das Copy-Command aus den ApplicationCommands.
Die Methoden
Beachtet auch, dass hier die Kurzform für das Copy-Command verwendet wurde, äquivalent dazu wäre diese Schreibweise:
Das Codebehind des MainWindow sieht nun so aus:
Interessant ist der Inhalt der CanCopy-Methode.
Das Copy-Command muss jetzt auch irgendwo ausgelöst werden, damit etwas passieren kann. Ich verwende hier wieder einen normalen Button:
Sobald der Button jetzt gedrückt wird, wandert das Routed-Command den Baum der Steuerelemente nach oben, bis ein Steuerelement gefunden wird, welches das Command per CommandBinding gebunden hat und
Sollte kein Steuerelement gefunden worden sein und das RoutedCommand erreicht das Ende des Baumes, wird das Command ignoriert.
Eigene Routed-Commands erstellen
Ihr könnte selbstverständlich auch eigene Routed-Commands erstellen und auf diese reagieren.
Das RoutedCommand muss öffentlich und statisch vorliegen, an welcher Stelle ist egal, ich verwende für dieses Beispiel eine neue statische Klasse:
Alles, was wir tun müssen, um unser eigenes RoutedCommand statt einem vorgefertigten zu nutzen, ist, es im XAML auszutauschen:
Command-Gruppen
Es kommt vor, dass ein einzelnes Steuerelement mehrere Commands ausführen soll und mit der CommandGroup-Klasse wird dies möglich. Leider existiert auch diese Klasse ausschlißlich unter TeamFoundation, aber auch hier können wir uns mit einer eigenen Implementierung Abhilfe schaffen:
C#
VB.Net
Was hier geschieht mag auf den ersten Blick kompliziert wirken, aber eigentlich werden hier nur mehrere Commands in einer Liste gespeichert, das CanExecuteChanged-Event durchgeleitet und in Execute alle Commands ausgeführt.
Ich habe die Klasse so designt, dass man bei Bedarf auch davon ableiten kann.
Sollen nur RoutedCommands in eine CommandGroup gesteckt werden, können wir diese gleich im XAML definieren:
Sollen auch RelayCommands mit enthalten sein oder soll die Liste im Laufe der Programmausführung geändert werden, kommen wir nicht darum herum, die CommandGroup im ViewModel zu erstellen:
C#
VB.Net
Hier können wir dann wiederum ganz normal binden:
Typische Anwendungen für Commands
Buttons
Buttons werden eigentlich immer mit einem Command verknüpft, ansonsten machen sie ja nicht sonderlich viel Sinn (zumindest aus funktionaler Sicht, aber was weiß ich schon, wofür ihr eure Buttons benutzt. ).
Da oben bei allen Beispielen Buttons verwendet wurden, muss ich hier, glaube ich, nicht mehr viel erklären, die Syntax lautet:
Menüitems
Auch Menüitems sind gute Kandidaten für Commands, gerade New, Open, Save, Copy, Paste, Cut, Undo oder Redo sind häufig in Menüs zu finden und lassen sich gut mit Commands umsetzen.
Die Syntax ist der vom Button nahezu identisch und ebenso simpel:
InputBinings
Wenn Menüitems vorhanden sind möchte man diese üblicherweise auch mit Tastenkombinationen verbinden. Das geht in WPF mit den sog. InputBindings, welche ebenfalls auf Basis von Commands funktionieren, womit es dann kein Problem ist, ihnen einfach den selben Command zuzuordnen wie ihrem entsprechenden Menüitem.
Hier sind z.B. einige Inputbindings für die Menüeinträge Neu, Öffnen und Speichern, wie man sie in Editoren aller Art findet:
Neben Tastatureingaben können auch Mauseingaben mit Hilfe von MouseBindings an Commands gebunden werden.
Ich hoffe ich konnte euch mit diesem Tutorial einige hilfreiche Informationen vermitteln. Anbei findet ihr noch ein Beispielprojekt für C# und VB.Net, in dem ihr euch alles hier besprochene noch einmal ausprogrammiert ansehen könnt.
in diesem Tutorial möchte ich euch die grundlegenden Konzepte von Commands in WPF näher bringen. Ich werde hier alle Erklärungen ausschließlich im Zusammenhang mit dem MVVM-Pattern (Model-View-ViewModel-Pattern) liefern, das Tutorial setzt daher Kenntnisse über dieses Pattern voraus (dies schließt die nötigen XAML-Kenntnisse mit ein).
Was sind Commands und wofür brauche ich sie?
Ein Großteil der WPF-Anwärter stammt aus dem Bereich der Vorgängertechnologie WindowsForms, welches Eventbasiert funktioniert. Es ist daher nicht verwunderlich, dass häufig die Codes von Anfängern in dem Gebiet WPF nach dem selben Prinzip funktionieren - und man kann es ihnen auch nicht verübeln, denn sie kennen es ja nicht anders.
So findet man dann z.B. dergleichen Codes vor:
Und der Eventhandler befindet sich logischerweise im Codebehind des Windows, ganz wie unter WindowsForms:
Warum genau ist das jetzt schlecht?
Events im Windows-Forms-Sinne sind mit dem MVVM-Pattern, welches in WPF eigentlich Pflicht ist, nicht oder nur schwer vereinbar. Grund ist, dass sich die Logik der Anwendung im ViewModel des Windows befindet, nicht im COdebehind der Window-Klasse. Man kann natürlich tricksen und die Events irgendwie an das ViewModel weiterleiten, aber das erfordert weiteren Code im Codebehind, der dort ebensowenig was zu suchen hat wie die Eventhandler. Sinn von MVVM ist es nämlich, dass im Codebehind nur Code steht, der wirklich nur das View (die Benutzeroberfläche) betrifft, Verknüfungen an das ViewModel sollten ausschließlich über Bindings erfolgen, und die legt man am einfachsten im XAML-Code an.
Aber wie werden Events denn dann gebunden?
Die Antwort ist: gar nicht, genau hier kommen unsere Commands in Spiel, die die Events an dieser Stelle ersetzen werden. Commands in WPF basieren auf dem ICommand-Interface und stellen eine Instanz da, die eine Benutzereingabe zu der entsprechen auzuführenden Aktion delegiert. Wichtig dabei ist, dass das Ziel des Commands vom Command allein verwaltet wird, das Steuerelement, welches das Command ausführt, muss davon nichts wissen. Dadurch können wir uns von den Handlern in der Window-Klasse verabschieden und alles ins ViewModel verlagern, wos hingehört. Zusätzlich können Commands dem View mitteilen, ob sie überhaupt ausgeführt werden können, die Steuerelemente können darauf entsprechend (z.B. mit Ausgrauung) reagieren.
Relay-Commands
Die simpelste Art von Commands sind die Relay-Commands. Sie tun nichts weiter als eine ihnen (über einen Delegaten) zugewiesene Aktion auszuführen.
Leider existiert in der Standard-WPF-Klassenbibliothek keine universelle RelayCommand-Klasse, diese gibts nur mit TeamFoundation. Damit man sich aber sparen kann, für jedes Command eine neue Klasse zu implementieren, kann man sich einfach diese relativ simple Klasse selbst bauen. Die hier präsentierte Version stammt von mir und ist selbstverständlich nicht zwangsläufig das Nonplusultra.
C#-Quellcode
- class RelayCommand : ICommand
- {
- readonly Action methodToExecute;
- readonly Func<bool> canExecuteEvaluator;
- public event EventHandler CanExecuteChanged
- {
- add { CommandManager.RequerySuggested += value; }
- remove { CommandManager.RequerySuggested -= value; }
- }
- public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
- {
- this.methodToExecute = methodToExecute;
- this.canExecuteEvaluator = canExecuteEvaluator;
- }
- public RelayCommand(Action methodToExecute)
- : this(methodToExecute, () => true)
- { }
- public bool CanExecute(object parameter)
- {
- return canExecuteEvaluator.Invoke();
- }
- public void Execute(object parameter)
- {
- methodToExecute.Invoke();
- }
- }
VB.NET-Quellcode
- Class RelayCommand : Implements ICommand
- Private ReadOnly _methodToExecute As Action
- Private ReadOnly _canExecuteEvaluator As Func(Of Boolean)
- Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
- AddHandler(value As EventHandler)
- AddHandler CommandManager.RequerySuggested, value
- End AddHandler
- RemoveHandler(value As EventHandler)
- RemoveHandler CommandManager.RequerySuggested, value
- End RemoveHandler
- RaiseEvent(sender As Object, e As EventArgs)
- End RaiseEvent
- End Event
- Public Sub New(methodToExecute As Action, canExecuteEvaluator As Func(Of Boolean))
- _methodToExecute = methodToExecute
- _canExecuteEvaluator = canExecuteEvaluator
- End Sub
- Public Sub New(methodToExecute As Action)
- Me.New(methodToExecute, Function() True)
- End Sub
- Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
- Return _canExecuteEvaluator.Invoke()
- End Function
- Public Sub Execute(parameter As Object) Implements ICommand.Execute
- _methodToExecute.Invoke()
- End Sub
- End Class
Bemerkenswert an dieser Klasse ist, dass sie selbst erkennt, wann sich der Rückgabewert des canExecuteEvaluators ändert, ihr müsst also nicht erst das Event manuell auslösen. Dahinter steckt der CommandManager, dessen Magie ich selbst nicht erklären kann, aber es funktioniert.
Mit Hilfe des RelayCommand können wir jetzt das schlechte Beispiel von weiter oben korrigieren. Wir statten das ViewModel mit dem Command für den Button als Eigenschaft aus, damit wir daran binden können. Im Konstruktor können wir dann das RelayCommand erstellen und die auzuführende Funktion auswählen sowie optional eine Ausführungsbedingung.
C#-Quellcode
- class MainViewModel : NotifyPropertyChangedBase
- {
- static MainViewModel instance;
- public static MainViewModel Instance
- {
- get { return instance ?? (instance = new MainViewModel()); }
- }
- public ICommand ButtonCommand { get; private set; }
- private MainViewModel()
- {
- ButtonCommand = new RelayCommand(ButtonClicked, CanClickButton);
- }
- private bool CanClickButton()
- {
- return true;
- }
- private void ButtonClicked()
- {
- //some code here
- }
- }
VB.NET-Quellcode
- Class MainViewModel : Inherits NotifyPropertyChangedBase
- Private Shared _instance As MainViewModel
- Public Shared ReadOnly Property Instance As MainViewModel
- Get
- If (_instance Is Nothing) Then _instance = New MainViewModel()
- Return _instance
- End Get
- End Property
- Private _buttonCommand As ICommand
- Public Property ButtonCommand As ICommand
- Get
- Return _buttonCommand
- End Get
- Private Set(value As ICommand)
- _buttonCommand = value
- End Set
- End Property
- Private Sub New()
- ButtonCommand = New RelayCommand(AddressOf ButtonClicked, AddressOf CanClickButton)
- End Sub
- Private Function CanClickButton() As Boolean
- Return True
- End Function
- Private Sub ButtonClicked()
- 'some code here
- End Sub
- End Class
Im XAML können wir dann die ButtonCommand-Eigenschaft an die Command-Eigenschaft des Buttons binden, sofern das ViewModel korrekt als DataContext festgelegt wurde. Die Namensgebung für den Button wird hier auch redundant, ein Zeichen, dass wir auf dem richtigen Weg sind, in WPF und MVVM benötigen die wenigsten Steuerelemente einen Namen.
Routed-Commands
Manchmal möchte man Commands nicht an das ViewModel sondern an bestimmte Controls senden, zum Beispiel um einer Textbox mitzuteilen, sie solle ihren Inhalt in die Zwischenablage kopieren. Im Gegensatz zum RelayCommand existiert in der WPF-Klassenbibliothek bereits eine RoutedCommand-Klasse, die wir verwenden können.
Routed-Commands sind statisch und können von mehreren Steuerelementen empfangen werden (welches Steuerelement dann tatsälich das Command ausführt, wird durch den VisualTree bestimmt, dazu später mehr). Einige Standard-Commands sind in der statischen ApplicationCommands-Klasse vordefiniert, die auch teilweise von den Standard-Stuerelementen bereits behandelt werden (z.B. reagiert die Textbox auf ApplicationCommands.Copy). Im Gegensatz zu den Relay-Commands bestimmt bei den Routed-Commands das Empfängersteuerelement das Verhalten des Commands, dies geschieht über sog. Commandbindings, die direkt im XAML des Steuerelements oder im Codebehind definiert werden können. Wenn also einem bestehenden Steuerelement ein Commandbinding hinzugefügt werden soll, muss eine abgeleitete Klasse implementiert werden, bei Usercontrols sind XAML und Codebehind ohnehin schon vorhanden.
Der Einfachheit halber habe ich hier ein Commandbinding am MainWindow eingerichtet, da wir uns so die Erstellung eines Usercontrols oder eines abgeleiteten Controls sparen können. Die Erstellung erfolgt hier im XAML und gebunden wir das Copy-Command aus den ApplicationCommands.
Die Methoden
CanCopy
und ExecuteCopy
befinden sich im Codebehind des MainWindow. Bedenkt, dass damit nur Code dort hin gehört, der das Steuerelement (also in dem Fall das MainWindow) betrifft, solltet ihr das Geühl haben, der Code gehöre ins ViewModel, ist ein RoutedCommand die falsche Wahl und ihr solltet ein RelayCommand verwenden.Beachtet auch, dass hier die Kurzform für das Copy-Command verwendet wurde, äquivalent dazu wäre diese Schreibweise:
Das Codebehind des MainWindow sieht nun so aus:
Interessant ist der Inhalt der CanCopy-Methode.
e.CanExecute
entspricht hier dem Rückgabewert des canExecuteEvaluator aus dem RelayCommand, gibt also an, ob das Command ausgeführt werden kann. e.ContinueRoutung
setze ich hier zur Demonstration auf false
, obwohl dies eigentlich nicht nötig ist (Standardwert ist false). Wird dieser Wert jedoch auf true
gesetzt, wird das CommandBinding für das Steuerelement an das entsprechende Command ignoriert, sodass andere Steuerelemente das Command empfangen können (auch dazu später mehr).Das Copy-Command muss jetzt auch irgendwo ausgelöst werden, damit etwas passieren kann. Ich verwende hier wieder einen normalen Button:
Sobald der Button jetzt gedrückt wird, wandert das Routed-Command den Baum der Steuerelemente nach oben, bis ein Steuerelement gefunden wird, welches das Command per CommandBinding gebunden hat und
e.ContinueRouting
false
ist. Dort wird dann das Command ausgeführt und die Executed-Funktion aufgerufen.Sollte kein Steuerelement gefunden worden sein und das RoutedCommand erreicht das Ende des Baumes, wird das Command ignoriert.
Eigene Routed-Commands erstellen
Ihr könnte selbstverständlich auch eigene Routed-Commands erstellen und auf diese reagieren.
Das RoutedCommand muss öffentlich und statisch vorliegen, an welcher Stelle ist egal, ich verwende für dieses Beispiel eine neue statische Klasse:
Alles, was wir tun müssen, um unser eigenes RoutedCommand statt einem vorgefertigten zu nutzen, ist, es im XAML auszutauschen:
Command-Gruppen
Es kommt vor, dass ein einzelnes Steuerelement mehrere Commands ausführen soll und mit der CommandGroup-Klasse wird dies möglich. Leider existiert auch diese Klasse ausschlißlich unter TeamFoundation, aber auch hier können wir uns mit einer eigenen Implementierung Abhilfe schaffen:
C#-Quellcode
- [ContentProperty("Commands")]
- class CommandGroup : ICommand
- {
- public event EventHandler CanExecuteChanged;
- readonly ObservableCollection<ICommand> commands;
- public ObservableCollection<ICommand> Commands
- {
- get { return commands; }
- }
- public CommandGroup()
- {
- commands = new ObservableCollection<ICommand>();
- commands.CollectionChanged += OnCommandsChanged;
- }
- protected virtual void OnCanExecuteChanged(EventArgs e)
- {
- if (CanExecuteChanged != null)
- CanExecuteChanged(this, e);
- }
- private void OnChildCanExecuteChanged(object sender, EventArgs e)
- {
- OnCanExecuteChanged(e);
- }
- protected virtual void OnCommandsChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.NewItems != null && e.NewItems.Count > 0)
- {
- foreach (ICommand command in e.NewItems)
- command.CanExecuteChanged += OnChildCanExecuteChanged;
- }
- if (e.OldItems != null && e.OldItems.Count > 0)
- {
- foreach (ICommand command in e.OldItems)
- command.CanExecuteChanged -= OnChildCanExecuteChanged;
- }
- OnCanExecuteChanged(EventArgs.Empty);
- }
- public bool CanExecute(object parameter)
- {
- return commands.All(command => command.CanExecute(parameter));
- }
- public void Execute(object parameter)
- {
- foreach (ICommand command in commands)
- command.Execute(parameter);
- }
- }
VB.NET-Quellcode
- <ContentProperty("Commands")>
- Class CommandGroup : Implements ICommand
- Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
- ReadOnly _commands As ObservableCollection(Of ICommand)
- Public ReadOnly Property Commands As ObservableCollection(Of ICommand)
- Get
- Return _commands
- End Get
- End Property
- Public Sub New()
- _commands = New ObservableCollection(Of ICommand)()
- AddHandler _commands.CollectionChanged, AddressOf OnCommandsChanged
- End Sub
- Protected Overridable Sub OnCanExecuteChanged(e As EventArgs)
- RaiseEvent CanExecuteChanged(Me, e)
- End Sub
- Private Sub OnChildCanExecuteChanged(sender As Object, e As EventArgs)
- OnCanExecuteChanged(e)
- End Sub
- Protected Overridable Sub OnCommandsChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
- If e.NewItems IsNot Nothing AndAlso e.NewItems.Count > 0 Then
- For Each command As ICommand In e.NewItems
- AddHandler command.CanExecuteChanged, AddressOf OnChildCanExecuteChanged
- Next
- End If
- If e.OldItems IsNot Nothing AndAlso e.OldItems.Count > 0 Then
- For Each command As ICommand In e.NewItems
- RemoveHandler command.CanExecuteChanged, AddressOf OnChildCanExecuteChanged
- Next
- End If
- OnCanExecuteChanged(EventArgs.Empty)
- End Sub
- Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
- Return _commands.All(Function(command) command.CanExecute(parameter))
- End Function
- Public Sub Execute(parameter As Object) Implements ICommand.Execute
- For Each command As ICommand In _commands
- command.Execute(parameter)
- Next
- End Sub
- End Class
Was hier geschieht mag auf den ersten Blick kompliziert wirken, aber eigentlich werden hier nur mehrere Commands in einer Liste gespeichert, das CanExecuteChanged-Event durchgeleitet und in Execute alle Commands ausgeführt.
Ich habe die Klasse so designt, dass man bei Bedarf auch davon ableiten kann.
Sollen nur RoutedCommands in eine CommandGroup gesteckt werden, können wir diese gleich im XAML definieren:
Sollen auch RelayCommands mit enthalten sein oder soll die Liste im Laufe der Programmausführung geändert werden, kommen wir nicht darum herum, die CommandGroup im ViewModel zu erstellen:
C#-Quellcode
- class MainViewModel : NotifyPropertyChangedBase
- {
- static MainViewModel instance;
- public static MainViewModel Instance
- {
- get { return instance ?? (instance = new MainViewModel()); }
- }
- public ICommand ButtonCommand { get; private set; }
- public ICommand CommandGroup { get; private set; }
- private MainViewModel()
- {
- ButtonCommand = new RelayCommand(ButtonClicked, CanClickButton);
- var commandGroup = new CommandGroup();
- commandGroup.Commands.Add(ButtonCommand);
- commandGroup.Commands.Add(ApplicationCommands.Copy);
- commandGroup.Commands.Add(Commands.CustomRoutedCommand);
- CommandGroup = commandGroup;
- }
- private bool CanClickButton()
- {
- return true;
- }
- private void ButtonClicked()
- {
- Debug.Print("Relay command executed!");
- }
- }
VB.NET-Quellcode
- Class MainViewModel : Inherits NotifyPropertyChangedBase
- Private Shared _instance As MainViewModel
- Public Shared ReadOnly Property Instance As MainViewModel
- Get
- If (_instance Is Nothing) Then _instance = New MainViewModel()
- Return _instance
- End Get
- End Property
- Private _buttonCommand As ICommand
- Private _commandGroup As ICommand
- Public Property ButtonCommand As ICommand
- Get
- Return _buttonCommand
- End Get
- Private Set(value As ICommand)
- _buttonCommand = value
- End Set
- End Property
- Public Property CommandGroup As ICommand
- Get
- Return _commandGroup
- End Get
- Private Set(value As ICommand)
- _commandGroup = value
- End Set
- End Property
- Private Sub New()
- ButtonCommand = New RelayCommand(AddressOf ButtonClicked, AddressOf CanClickButton)
- Dim commandGroup = New CommandGroup()
- commandGroup.Commands.Add(ButtonCommand)
- commandGroup.Commands.Add(ApplicationCommands.Copy)
- commandGroup.Commands.Add(Commands.CustomRoutedCommand)
- Me.CommandGroup = commandGroup
- End Sub
- Private Function CanClickButton() As Boolean
- Return True
- End Function
- Private Sub ButtonClicked()
- Debug.Print("Relay command executed!")
- End Sub
- End Class
Hier können wir dann wiederum ganz normal binden:
Typische Anwendungen für Commands
Buttons
Buttons werden eigentlich immer mit einem Command verknüpft, ansonsten machen sie ja nicht sonderlich viel Sinn (zumindest aus funktionaler Sicht, aber was weiß ich schon, wofür ihr eure Buttons benutzt. ).
Da oben bei allen Beispielen Buttons verwendet wurden, muss ich hier, glaube ich, nicht mehr viel erklären, die Syntax lautet:
Menüitems
Auch Menüitems sind gute Kandidaten für Commands, gerade New, Open, Save, Copy, Paste, Cut, Undo oder Redo sind häufig in Menüs zu finden und lassen sich gut mit Commands umsetzen.
Die Syntax ist der vom Button nahezu identisch und ebenso simpel:
InputBinings
Wenn Menüitems vorhanden sind möchte man diese üblicherweise auch mit Tastenkombinationen verbinden. Das geht in WPF mit den sog. InputBindings, welche ebenfalls auf Basis von Commands funktionieren, womit es dann kein Problem ist, ihnen einfach den selben Command zuzuordnen wie ihrem entsprechenden Menüitem.
Hier sind z.B. einige Inputbindings für die Menüeinträge Neu, Öffnen und Speichern, wie man sie in Editoren aller Art findet:
Neben Tastatureingaben können auch Mauseingaben mit Hilfe von MouseBindings an Commands gebunden werden.
Ich hoffe ich konnte euch mit diesem Tutorial einige hilfreiche Informationen vermitteln. Anbei findet ihr noch ein Beispielprojekt für C# und VB.Net, in dem ihr euch alles hier besprochene noch einmal ausprogrammiert ansehen könnt.
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()