Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Тип RelayCommand — это атрибут, позволяющий создавать свойства команды ретранслятора для аннотированных методов. Его цель состоит в том, чтобы полностью исключить шаблон, необходимый для определения команд, которые упаковывают частные методы в viewmodel.
Note
Чтобы работать, аннотированные методы должны находиться в частичном классе. Если тип вложен, все типы в дереве синтаксиса объявления также должны быть помечены как частичные. Если этого не сделать, возникнут ошибки компиляции, так как генератор не сможет создать другое частичное объявление этого типа с указанной командой.
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);
Note
Имя созданной команды будет создано на основе имени метода. Генератор будет использовать имя метода, добавит в конец "Command" и удалит префикс "On", если он есть. Кроме того, для асинхронных методов суффикс 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 вызывается, когда кнопка впервые привязывается к пользовательскому интерфейсу (например, к элементу button), а затем снова вызывается каждый раз, когда для команды вызывается 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 будет включаться или отключаться в зависимости от того, задано ли значение для этого свойства, что и является желаемым поведением в этом сценарии.
Note
Команда не будет автоматически отслеживать, когда изменилось возвращаемое значение метода или свойства CanExecute. Разработчик должен вызвать IRelayCommand.NotifyCanExecuteChanged, чтобы пометить команду как недействительную и запросить повторную проверку связанного метода CanExecute, а затем обновить визуальное состояние элемента управления, привязанного к этой команде.
Обработка параллельных выполнений
При асинхронной команде можно настроить решение о том, разрешать ли одновременные выполнения или нет. При использовании атрибута RelayCommand это можно задать с помощью AllowConcurrentExecutions свойства. По умолчанию используется значение false, то есть, пока выполнение не находится в состоянии ожидания, команда будет сигнализировать, что она отключена. Если вместо него задано trueзначение, любое количество одновременных вызовов можно поместить в очередь.
Обратите внимание, что если команда принимает маркер отмены, маркер также будет отменен при запросе параллельного выполнения. Основное различие заключается в том, что если разрешено параллельное выполнение, команда останется включенной, и она запустит новое запрошенное выполнение, не ожидая завершения предыдущего.
Обработка асинхронных исключений
Существует два способа обработки исключений асинхронных команд ретранслятора.
- Ожидание и повторный выброс исключения (по умолчанию): когда команда ожидает завершения вызова, любые исключения будут естественным образом повторно выброшены в том же контексте синхронизации. Обычно это означает, что выбрасываемые исключения будут просто приводить к сбою приложения, что соответствует поведению синхронных команд (где выбрасываемые исключения также приводят к сбою приложения).
-
Передача исключений планировщику задач: если команда настроена на передачу исключений планировщику задач, генерируемые исключения не приведут к сбою приложения, а вместо этого будут доступны через предоставляемый
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 Toolkit передаст эти атрибуты сгенерированным свойствам команд.
Например, рассмотрим такой метод:
[RelayCommand]
[property: JsonIgnore]
private void GreetUser(User user)
{
Console.WriteLine($"Hello {user.Name}!");
}
Будет создано свойство GreetUserCommand с атрибутом [JsonIgnore] над ним. Вы можете использовать столько списков атрибутов, предназначенных для метода, сколько нужно, и все из них будут перенаправляться в созданные свойства.
Примеры
- Ознакомьтесь с примером приложения (для нескольких платформ пользовательского интерфейса), чтобы просмотреть набор средств MVVM в действии.
- Дополнительные примеры можно найти в модульных тестах.
MVVM Toolkit