Freigeben über


RelayCommand-Attribut

Der RelayCommand-Typ ist ein Attribut, das das Generieren von Relaybefehlseigenschaften für annotierte Methoden ermöglicht. Der Zweck besteht darin, die Bausteine vollständig zu beseitigen, die zum Definieren von Befehlen erforderlich sind, die private Methoden in einem Ansichtsmodell umschließen.

Hinweis

Damit sie funktionieren, müssen sich annotierte Methoden in einer partiellen Klasse befinden. Wenn der Typ geschachtelt ist, müssen auch alle Typen in der Deklarationssyntaxstruktur als partiell annotiert werden. Wenn dies nicht erfolgt, führt dies zu Kompilierungsfehlern, da der Generator keine andere partielle Deklaration dieses Typs mit dem angeforderten Befehl generieren kann.

Plattform-APIs:RelayCommand, ICommand, IRelayCommand, IRelayCommand<T>, IAsyncRelayCommand, IAsyncRelayCommand<T>, Task, CancellationToken

Funktionsweise

Das RelayCommand-Attribut kann verwendet werden, um eine Methode in einem partiellen Typ zu annotieren, z. B.:

[RelayCommand]
private void GreetUser()
{
    Console.WriteLine("Hello!");
}

Außerdem wird ein Befehl wie folgt generiert:

private RelayCommand? greetUserCommand;

public IRelayCommand GreetUserCommand => greetUserCommand ??= new RelayCommand(GreetUser);

Hinweis

Der Name des generierten Befehls wird basierend auf dem Methodennamen erstellt. Der Generator verwendet den Methodennamen und fügt „Command“ am Ende an. Das Präfix „On“ wird entfernt, falls vorhanden. Darüber hinaus wird das Suffix „Async“ für asynchrone Methoden ebenfalls entfernt, bevor „Command“ angefügt wird.

Befehlsparameter

Das [RelayCommand]-Attribut unterstützt das Erstellen von Befehlen für Methoden mit einem Parameter. In diesem Fall wird der generierte Befehl automatisch stattdessen in IRelayCommand<T> geändert und nimmt einen Parameter desselben Typs an:

[RelayCommand]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

Dies führt zu folgendem generiertem Code:

private RelayCommand<User>? greetUserCommand;

public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser);

Der resultierende Befehl verwendet automatisch den Typ des Arguments als Typargument.

Asynchrone Befehle

Der [RelayCommand]-Befehl unterstützt auch das Umschließen asynchroner Methoden über die Schnittstellen IAsyncRelayCommand und IAsyncRelayCommand<T>. Dies wird automatisch behandelt, wenn eine Methode einen Task-Typ zurückgibt. Beispiel:

[RelayCommand]
private async Task GreetUserAsync()
{
    User user = await userService.GetCurrentUserAsync();

    Console.WriteLine($"Hello {user.Name}!");
}

Dies führt zu folgendem Code:

private AsyncRelayCommand? greetUserCommand;

public IAsyncRelayCommand GreetUserCommand => greetUserCommand ??= new AsyncRelayCommand(GreetUserAsync);

Wenn die Methode einen Parameter annimmt, ist der resultierende Befehl ebenfalls generisch.

Es gibt einen Sonderfall, wenn die Methode ein CancellationToken hat, da dies an den Befehl weitergegeben wird, um den Abbruch zu aktivieren. Betrachten Sie eine Methode wie diese:

[RelayCommand]
private async Task GreetUserAsync(CancellationToken token)
{
    try
    {
        User user = await userService.GetCurrentUserAsync(token);

        Console.WriteLine($"Hello {user.Name}!");
    }
    catch (OperationCanceledException)
    {
    }
}

Sie führt dazu, dass der generierte Befehl ein Token an die umschlossene Methode übergibt. Auf diese Weise können Consumer einfach IAsyncRelayCommand.Cancel aufrufen, um dieses Token zu signalisieren und damit ausstehende Vorgänge ordnungsgemäß angehalten werden.

Aktivieren und Deaktivieren von Befehlen

Es ist häufig hilfreich, Befehle zu deaktivieren, dann später den Zustand ungültig zu machen und erneut zu überprüfen, ob sie ausgeführt werden können oder nicht. Um dies zu unterstützen, macht das RelayCommand-Attribut die CanExecute-Eigenschaft verfügbar, die verwendet werden kann, um eine Zieleigenschaft oder -methode anzugeben, mit der ausgewertet werden soll, ob ein Befehl ausgeführt werden kann:

[RelayCommand(CanExecute = nameof(CanGreetUser))]
private void GreetUser(User? user)
{
    Console.WriteLine($"Hello {user!.Name}!");
}

private bool CanGreetUser(User? user)
{
    return user is not null;
}

Auf diese Weise wird CanGreetUser aufgerufen, wenn die Schaltfläche zuerst an die Benutzeroberfläche gebunden wird (z. B. an eine Schaltfläche), und der Aufruf erfolgt jedes Mal erneut, wenn IRelayCommand.NotifyCanExecuteChanged für den Befehl aufgerufen wird.

So kann beispielsweise ein Befehl an eine Eigenschaft gebunden werden, um den Zustand zu steuern:

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(GreetUserCommand))]
private User? selectedUser;
<!-- Note: this example uses traditional XAML binding syntax -->
<Button
    Content="Greet user"
    Command="{Binding GreetUserCommand}"
    CommandParameter="{Binding SelectedUser}"/>

In diesem Beispiel ruft die generierte SelectedUser-Eigenschaft die GreetUserCommand.NotifyCanExecuteChanged()-Methode bei jeder Änderung des Werts auf. Die Benutzeroberfläche verfügt über eine Button-Steuerelementbindung an GreetUserCommand, d. h., jedes Mal, wenn das CanExecuteChanged-Ereignis ausgelöst wird, wird die CanExecute-Methode erneut aufgerufen. Dadurch wird die umschlossene CanGreetUser-Methode ausgewertet, wodurch der neue Zustand für die Schaltfläche zurückgegeben wird, je nachdem, ob die User-Eingabeinstanz (die in der Benutzeroberfläche an die SelectedUser-Eigenschaft gebunden ist) null ist oder nicht. Dies bedeutet, dass GreetUserCommand jedes Mal, wenn SelectedUser geändert wird, aktiviert oder nicht aktiviert wird, abhängig davon, ob diese Eigenschaft einen Wert aufweist. Dies ist das gewünschte Verhalten in diesem Szenario.

Hinweis

Der Befehl beachtet nicht automatisch, wann der Rückgabewert für die CanExecute-Methode oder -Eigenschaft geändert wurde. Der Entwickler muss IRelayCommand.NotifyCanExecuteChanged aufrufen, um den Befehl ungültig zu machen und eine erneute Prüfung der verknüpften CanExecute-Methode anzufordern, um dann den visuellen Zustand des an den Befehl gebundenen Steuerelements zu aktualisieren.

Behandeln von gleichzeitigen Ausführungen

Wenn ein Befehl asynchron ist, kann er so konfiguriert werden, dass entschieden wird, ob gleichzeitige Ausführungen zulässig sind oder nicht. Bei Verwendung des RelayCommand-Attributs kann dies über die AllowConcurrentExecutions-Eigenschaft festgelegt werden. Der Standardwert ist false, d. h., bis eine Ausführung aussteht, signalisiert der Befehl den Status als deaktiviert. Wenn der Wert stattdessen auf true festgelegt ist, kann eine beliebige Anzahl gleichzeitiger Aufrufe in die Warteschlange gestellt werden.

Hinweis: Wenn ein Befehl ein Abbruchtoken annimmt, wird ein Token auch abgebrochen, wenn eine gleichzeitige Ausführung angefordert wird. Der Hauptunterschied besteht darin, dass der Befehl bei zulässigen gleichzeitigen Ausführungen aktiviert bleibt und eine neue angeforderte Ausführung gestartet wird, ohne auf den Abschluss des vorherigen Vorgangs zu warten.

Behandeln von asynchronen Ausnahmen

Es gibt zwei verschiedene Möglichkeiten, wie asynchrone Relaybefehle Ausnahmen behandeln:

  • Warten und erneut auslösen (Standard): Wenn der Befehl auf den Abschluss eines Aufrufs wartet, werden alle Ausnahmen natürlich im selben Synchronisierungskontext ausgelöst. Das bedeutet in der Regel, dass ausgelöste Ausnahmen nur die App abstürzen lassen würden. Dies ist ein Verhalten, das mit dem von synchronen Befehlen übereinstimmt (wenn Ausnahmen ausgelöst werden, stürzt die App ebenfalls ab).
  • Weiterleiten von Ausnahmen an den Taskplaner: Wenn ein Befehl für das Weiterleiten von Ausnahmen an den Taskplaner konfiguriert ist, stützt die App durch das Auslösen von Ausnahmen nicht ab. Stattdessen sind beide über die bereitgestellte IAsyncRelayCommand.ExecutionTask verfügbar und werden für TaskScheduler.UnobservedTaskException verfügbar. Dies ermöglicht komplexere Szenarien (z. B. das Binden von UI-Komponenten an die Aufgabe und das Anzeigen unterschiedlicher Ergebnisse basierend auf dem Ergebnis des Vorgangs), die richtige Verwendung ist jedoch komplizierter.

Das Standardverhalten besteht darin, dass Befehle warten und Ausnahmen erneut auslösen. Dies kann über die Eigenschaft FlowExceptionsToTaskScheduler konfiguriert werden:

[RelayCommand(FlowExceptionsToTaskScheduler = true)]
private async Task GreetUserAsync(CancellationToken token)
{
    User user = await userService.GetCurrentUserAsync(token);

    Console.WriteLine($"Hello {user.Name}!");
}

In diesem Fall ist try/catch nicht erforderlich, da Ausnahmen die App nicht mehr abstürzen lassen. Beachten Sie, dass dies auch dazu führt, dass andere nicht zusammenhängende Ausnahmen nicht automatisch erneut ausgelöst werden. Daher sollten Sie sorgfältig entscheiden, wie Sie jedes einzelne Szenario angehen und den restlichen Code entsprechend konfigurieren.

Abbrechen von Befehlen für asynchrone Vorgänge

Eine letzte Option für asynchrone Befehle ist die Möglichkeit, das Generieren eines Abbruchbefehls anzufordern. Dabei umschließt ein ICommand einen asynchronen Relaybefehl, der zum Anfordern des Abbruchs eines Vorgangs verwendet werden kann. Dieser Befehl signalisiert automatisch seinen Zustand, um anzugeben, ob er zu einem bestimmten Zeitpunkt verwendet werden kann. Wenn der verknüpfte Befehl beispielsweise nicht ausgeführt wird, meldet er den Status auch als nicht als ausführbar. Dies kann wie folgt verwendet werden:

[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync(CancellationToken token)
{
    // Do some long running work...
}

Dies führt dazu, dass auch eine DoWorkCancelCommand-Eigenschaft generiert wird. Diese kann dann an eine andere UI-Komponente gebunden werden, damit Benutzer ausstehende asynchrone Vorgänge problemlos abbrechen können.

Hinzufügen von benutzerdefinierten Attributen

Genau wie bei beobachtbaren Eigenschaftenumfasst der RelayCommand-Generator auch Unterstützung für benutzerdefinierte Attribute für die generierten Eigenschaften. Um dies zu nutzen, können Sie einfach das [property: ]-Ziel in Attributlisten über kommentierte Methoden verwenden, und das MVVM-Toolkit leitet diese Attribute an die generierten Befehlseigenschaften weiter.

Betrachten Sie beispielsweise eine Methode wie die folgende:

[RelayCommand]
[property: JsonIgnore]
private void GreetUser(User user)
{
    Console.WriteLine($"Hello {user.Name}!");
}

Dadurch wird eine GreetUserCommand-Eigenschaft mit dem [JsonIgnore]-Attribut generiert. Sie können beliebig viele Attributlisten verwenden, die auf die Methode abzielen. Alle werden an die generierten Eigenschaften weitergeleitet.

Beispiele

  • Sehen Sie sich die Beispiel-App (für mehrere UI-Frameworks) an, um das MVVM-Toolkit in Aktion zu sehen.
  • Weitere Beispiele finden Sie auch in den Komponententests.