Поделиться через


Атрибут RelayCommand

Тип RelayCommand — это атрибут, позволяющий создавать свойства команды ретранслятора для аннотированных методов. Его цель состоит в том, чтобы полностью исключить шаблон, необходимый для определения команд, которые упаковывают частные методы в viewmodel.

Примечание.

Чтобы работать, аннотированные методы должны находиться в частичном классе. Если тип вложен, все типы в дереве синтаксиса объявления также должны быть помечены как частичные. Это приведет к ошибкам компиляции, так как генератор не сможет создать другое частичное объявление этого типа с запрошенной командой.

API платформы:RelayCommand, ICommand, IRelayCommand, IRelayCommand<T>IAsyncRelayCommand, IAsyncRelayCommand<T>TaskCancellationToken

Как это работает

Атрибут RelayCommand можно использовать для анотации метода в частичном типе, например:

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

И он создаст команду следующим образом:

private RelayCommand? greetUserCommand;

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

Примечание.

Имя созданной команды будет создано на основе имени метода. Генератор будет использовать имя метода и добавить "Command" в конце, и при наличии префикс "Вкл. Кроме того, для асинхронных методов суффикс Async также удаляется перед добавлением команды.

Параметры команд

Атрибут [RelayCommand] поддерживает создание команд для методов с параметром. В этом случае она автоматически изменит созданную команду, чтобы она была IRelayCommand<T> вместо нее, принимая параметр того же типа:

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

Это приведет к следующему созданному коду:

private RelayCommand<User>? greetUserCommand;

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

Результирующая команда автоматически будет использовать тип аргумента в качестве аргумента типа.

Асинхронные команды

Эта [RelayCommand] команда также поддерживает асинхронные методы упаковки через IAsyncRelayCommand интерфейсы и IAsyncRelayCommand<T> интерфейсы. Это обрабатывается автоматически, когда метод возвращает Task тип. Например:

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

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

Это приведет к следующему коду:

private AsyncRelayCommand? greetUserCommand;

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

Если метод принимает параметр, результирующая команда также будет универсальной.

Существует особый случай, когда метод имеет метод CancellationToken, так как он будет распространяться в команду, чтобы включить отмену. То есть метод, как показано ниже.

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

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

Приведет к получению созданной команды передачи маркера в метод оболочки. Это позволяет потребителям просто вызывать IAsyncRelayCommand.Cancel этот маркер и разрешать отложенные операции останавливаться правильно.

Включение и отключение команд

Часто полезно отключить команды, а затем позже отменить состояние и снова проверка их можно выполнить. Для поддержки этого RelayCommand атрибут предоставляет CanExecute свойство, которое можно использовать для указания целевого свойства или метода для оценки возможности выполнения команды:

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

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

Таким образом, вызывается, когда кнопка сначала привязана к пользовательскому интерфейсу (например, CanGreetUser к кнопке), а затем вызывается снова при IRelayCommand.NotifyCanExecuteChanged каждом вызове команды.

Например, это способ привязки команды к свойству для управления состоянием:

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

В этом примере созданное SelectedUser свойство будет вызывать GreetUserCommand.NotifyCanExecuteChanged() метод при каждом изменении его значения. Пользовательский интерфейс имеет привязку Button элемента управления к GreetUserCommand, что означает каждый раз при возникновении его CanExecuteChanged события, он снова вызовет метод CanExecute . Это приведет к оценке метода CanGreetUser оболочки, который возвращает новое состояние кнопки на основе того, является ли входный User экземпляр (который в пользовательском интерфейсе привязан к SelectedUser свойству) null или нет. Это означает, что каждый раз SelectedUser , когда он изменяется, будет включен или не зависит от того, GreetUserCommand имеет ли это свойство значение, которое является требуемым поведением в этом сценарии.

Примечание.

Команда не будет автоматически знать о том, когда возвращаемое значение метода CanExecute или свойства изменилось. Разработчику нужно вызвать IRelayCommand.NotifyCanExecuteChanged недопустимое выполнение команды и запросить оценку связанного CanExecute метода, чтобы затем обновить визуальное состояние элемента управления, привязанного к команде.

Обработка параллельных выполнений

При асинхронной команде можно настроить решение о том, разрешать ли одновременные выполнения или нет. При использовании атрибута RelayCommand это можно задать с помощью AllowConcurrentExecutions свойства. Значение по умолчанию — это falseозначает, что до ожидания выполнения команда будет сигнализировать о его состоянии как отключенном. Если вместо него задано trueзначение, любое количество одновременных вызовов можно поместить в очередь.

Обратите внимание, что если команда принимает маркер отмены, маркер также будет отменен при запросе параллельного выполнения. Основное различие заключается в том, что если разрешено параллельное выполнение, команда останется включенной, и она запустит новое запрошенное выполнение, не ожидая завершения предыдущего.

Обработка асинхронных исключений

Существует два способа обработки исключений асинхронных команд ретранслятора.

  • Await и rethrow (default): когда команда ожидает завершения вызова, все исключения, естественно, будут возникать в том же контексте синхронизации. Это обычно означает, что исключения, создаваемые, просто завершаются сбоем приложения, что соответствует поведению синхронных команд (при возникновении исключений также произойдет сбой приложения).
  • Исключения потоков для планировщика задач: если команда настроена для потока исключений планировщику задач, исключения, которые создаются, не завершаются сбоем приложения, но вместо этого они будут доступны через предоставленные IAsyncRelayCommand.ExecutionTask , а также пузырьки до приложения TaskScheduler.UnobservedTaskException. Это позволяет более сложным сценариям (например, привязывать компоненты пользовательского интерфейса к задаче и отображать различные результаты в зависимости от результата операции), но более сложно использовать правильно.

Поведение по умолчанию имеет команды ожидания и повтора исключений. Это можно настроить с помощью FlowExceptionsToTaskScheduler свойства:

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

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

В этом случае не требуется, try/catch так как исключения больше не завершатся сбоем приложения. Обратите внимание, что это также приведет к автоматическому повтору других несвязанных исключений, поэтому следует тщательно решить, как подходить к каждому отдельному сценарию и соответствующим образом настраивать остальную часть кода.

Отмена команд для асинхронных операций

Одним из последних вариантов асинхронных команд является возможность запрашивать созданную команду отмены. Это ICommand оболочка асинхронной команды ретранслятора, которую можно использовать для запроса отмены операции. Эта команда автоматически сигнализирует о своем состоянии в соответствии с тем, может ли она использоваться в любое время. Например, если связанная команда не выполняется, она сообщает о своем состоянии, а также не выполняется. Это можно использовать следующим образом:

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

Это приведет DoWorkCancelCommand к созданию свойства. Затем это можно привязать к другому компоненту пользовательского интерфейса, чтобы пользователи могли отменить ожидающие асинхронные операции.

Добавление настраиваемых атрибутов

Как и при наблюдаемых свойствах, RelayCommand генератор также включает поддержку пользовательских атрибутов для созданных свойств. Для этого можно просто использовать целевой [property: ] объект в списках атрибутов по аннотированных методах, а MVVM набор средств перенаправит эти атрибуты в созданные свойства команды.

Например, рассмотрим такой метод:

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

Это приведет к созданию GreetUserCommand свойства с атрибутом [JsonIgnore] над ним. Вы можете использовать столько списков атрибутов, предназначенных для метода, сколько нужно, и все из них будут перенаправляться в созданные свойства.

Примеры

  • Ознакомьтесь с примером приложения (для нескольких платформ пользовательского интерфейса), чтобы просмотреть набор средств MVVM в действии.
  • Дополнительные примеры можно найти в модульных тестах.