Atribut RelayCommand

Typ RelayCommand je atribut, který umožňuje generování vlastností příkazu relay pro anotované metody. Jejím účelem je zcela odstranit opakující se kód potřebný k definování příkazů, které obalují privátní metody ve viewmodelu.

Note

Aby bylo možné pracovat, musí být anotované metody v částečné třídě. Pokud jde o vnořený typ, musí být všechny typy ve stromu syntaxe deklarace také označeny jako partial. Pokud to neuděláte, dojde k chybám kompilace, protože generátor nebude moci vygenerovat jinou částečnou deklaraci tohoto typu pomocí požadovaného příkazu.

Rozhraní API platformy:RelayCommand, ICommand, IRelayCommand, IRelayCommand<T>, IAsyncRelayCommand, IAsyncRelayCommand<T>, , , TaskCancellationToken

Jak to funguje

Atribut RelayCommand lze použít k anotaci metody v částečném typu, například takto:

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

A vygeneruje příkaz podobný tomuto:

private RelayCommand? greetUserCommand;

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

Note

Název vygenerovaného příkazu se vytvoří na základě názvu metody. Generátor použije název metody a na konci připojí "Command" a v případě přítomnosti odstraní předponu "On". Navíc se u asynchronních metod před připojením řetězce „Command“ také odstraní přípona „Async“.

Parametry příkazu

Atribut [RelayCommand] podporuje vytváření příkazů pro metody s parametrem. V takovém případě automaticky změní vygenerovaný příkaz na IRelayCommand<T>, který místo toho přijímá parametr stejného typu:

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

Výsledkem bude následující vygenerovaný kód:

private RelayCommand<User>? greetUserCommand;

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

Výsledný příkaz automaticky použije typ argumentu jako argument typu.

Asynchronní příkazy

Příkaz [RelayCommand] také podporuje zabalení asynchronních metod prostřednictvím IAsyncRelayCommand rozhraní a IAsyncRelayCommand<T> rozhraní. Tato metoda se zpracovává automaticky, kdykoli metoda vrátí Task typ. Například:

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

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

Výsledkem bude následující kód:

private AsyncRelayCommand? greetUserCommand;

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

Pokud metoda vezme parametr, výsledný příkaz bude také obecný.

Existuje zvláštní případ, kdy metoda má CancellationToken, protože se rozšíří do příkazu pro povolení zrušení. To znamená, že metoda podobná této:

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

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

Výsledkem bude vygenerovaný příkaz, který předá token zabalené metodě. To umožňuje volajícím pouze zavolat IAsyncRelayCommand.Cancel, aby signalizovali tento token a umožnili správné zastavení čekajících operací.

Povolení a zakázání příkazů

Často je užitečné příkazy zakázat a později zneplatnit jejich stav a znovu je zkontrolovat, jestli se dají spustit, nebo ne. Aby to bylo možné podporovat, RelayCommand atribut zveřejňuje CanExecute vlastnost, kterou lze použít k označení cílové vlastnosti nebo metody, které se mají použít k vyhodnocení, zda lze provést příkaz:

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

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

Tímto způsobem je CanGreetUser voláno, když je tlačítko poprvé přidruženo k uživatelskému rozhraní (např. k tlačítku), a potom je voláno znovu pokaždé, když je na příkazu voláno IRelayCommand.NotifyCanExecuteChanged.

Například takto může být příkaz svázán s vlastností, aby mohl řídit jeho stav:

[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}"/>

V tomto příkladu vygenerovaná SelectedUser vlastnost vyvolá GreetUserCommand.NotifyCanExecuteChanged() metodu pokaždé, když se změní její hodnota. Uživatelské rozhraní má vazbu ovládacího prvku na GreetUserCommand, což znamená, že pokaždé, když je vyvolána jeho událost CanExecuteChanged, znovu zavolá svou metodu CanExecute. To způsobí vyhodnocení obalené metody CanGreetUser, která vrátí nový stav tlačítka podle toho, zda je vstupní instance User (která je v uživatelském rozhraní navázána na vlastnost SelectedUser) null, či nikoli. To znamená, že kdykoli se změní SelectedUser, GreetUserCommand se povolí, nebo nepovolí podle toho, zda má tato vlastnost hodnotu, což je v tomto scénáři požadované chování.

Note

Příkaz nebude automaticky vědět o tom, kdy se změnila návratová hodnota pro metodu CanExecute nebo vlastnost. Je na vývojáři, aby zavolal IRelayCommand.NotifyCanExecuteChanged, čímž zneplatní příkaz a vyžádá opětovné vyhodnocení přidružené metody CanExecute, aby se následně aktualizoval vizuální stav ovládacího prvku vázaného na příkaz.

Správa paralelních spuštění

Kdykoli je příkaz asynchronní, můžete ho nakonfigurovat tak, aby se rozhodl, jestli chcete povolit souběžná spuštění, nebo ne. Při použití atributu RelayCommand to lze nastavit prostřednictvím AllowConcurrentExecutions vlastnosti. Výchozí hodnota je false, což znamená, že dokud nečeká na provedení, příkaz bude signalizovat, že je zakázán. Pokud je místo toho nastavená hodnota true, může být zařazen do fronty libovolný počet souběžných vyvolání.

Všimněte si, že pokud příkaz přijme token zrušení, token se zruší také v případě, že se požaduje souběžné spuštění. Hlavní rozdíl spočívá v tom, že pokud jsou povoleny souběžné spuštění, příkaz zůstane povolený a spustí nové požadované spuštění bez čekání na dokončení předchozího spuštění.

Zpracování asynchronních výjimek

Asynchronní příkazy přenosu zpracovávají výjimky dvěma způsoby:

  • Čekání a opětovné vyvolání (výchozí): když příkaz čeká na dokončení volání, všechny výjimky budou přirozeně znovu vyvolány ve stejném kontextu synchronizace. To obvykle znamená, že vyvolání výjimek způsobí pouze chybové ukončení aplikace, což je chování konzistentní s synchronními příkazy (kde dojde také k chybovému ukončení aplikace).
  • Předávání výjimek plánovači úloh: Pokud je příkaz nakonfigurován tak, aby předával výjimky plánovači úloh, vyvolané výjimky nezpůsobí pád aplikace, ale místo toho budou dostupné prostřednictvím zpřístupněného IAsyncRelayCommand.ExecutionTask a zároveň se budou propagovat až do TaskScheduler.UnobservedTaskException. To umožňuje pokročilejší scénáře (například vytvoření vazby komponent uživatelského rozhraní k úloze a zobrazení různých výsledků na základě výsledku operace), ale správné použití je složitější.

Výchozí chování spočívá v tom, že příkazy čekají pomocí await a znovu vyvolají výjimky. Tuto vlastnost lze nakonfigurovat prostřednictvím FlowExceptionsToTaskScheduler vlastnosti:

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

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

V takovém případě není try/catch potřeba, protože výjimky už nezpůsobí pád aplikace. Mějte na paměti, že to také způsobí, že jiné nesouvisející výjimky nebudou automaticky znovu vyvolány, takže byste měli pečlivě zvážit, jak přistupovat ke každé jednotlivé situaci, a podle toho správně nakonfigurovat zbytek kódu.

Zrušení příkazů pro asynchronní operace

Jednou z posledních možností pro asynchronní příkazy je možnost požadovat, aby se vygeneroval příkaz zrušit. Jedná se o zabalení ICommand asynchronního příkazu relé, který lze použít k vyžádání zrušení operace. Tento příkaz bude automaticky indikovat svůj stav tak, aby odrážel, zda jej lze v daném okamžiku použít. Například pokud se propojený příkaz nespouští, oznámí, že jeho stav je také nespustitelný. Můžete ho použít takto:

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

To způsobí, že se také vygeneruje vlastnost DoWorkCancelCommand. To pak může být vázáno na některé jiné součásti uživatelského rozhraní, aby uživatelé mohli snadno zrušit čekající asynchronní operace.

Přidání vlastních atributů

Stejně jako u pozorovatelných vlastnostíRelayCommand generátor také zahrnuje podporu vlastních atributů pro vygenerované vlastnosti. K tomu můžete jednoduše použít [property: ] cíl v seznamech atributů nad anotovanými metodami a sada MVVM Toolkit tyto atributy předá do vygenerovaných vlastností příkazu.

Představte si například takovou metodu:

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

Tím se vygeneruje GreetUserCommand vlastnost s atributem [JsonIgnore] nad ním. Můžete použít libovolný počet seznamů atributů, které cílí na metodu, a všechny z nich budou předány do vygenerovaných vlastností.

Příklady

  • Podívejte se na ukázkovou aplikaci (pro více architektur uživatelského rozhraní) a podívejte se na sadu nástrojů MVVM v akci.
  • Další příklady najdete také v testech jednotek.