RelayCommand 특성

RelayCommand 형식은 주석이 추가된 메서드에 대한 릴레이 명령 속성을 생성할 수 있는 특성입니다. 그 목적은 viewmodel에서 프라이빗 메서드를 래핑하는 명령을 정의하는 데 필요한 상용구가 완전히 제거되는 것입니다.

참고 항목

작동하려면 주석이 추가된 메서드가 partial 클래스있어야 합니다. 형식이 중첩된 경우 선언 구문 트리의 모든 형식도 부분 주석으로 주석을 추가해야 합니다. 이렇게 하지 않으면 생성기가 요청된 명령을 사용하여 해당 형식의 다른 부분 선언을 생성할 수 없으므로 컴파일 오류가 발생합니다.

플랫폼 API:RelayCommand, , IRelayCommandICommand, IRelayCommand<T>, , TaskIAsyncRelayCommandIAsyncRelayCommand<T>CancellationToken

작동 방식

다음과 RelayCommand 같이 부분 형식의 메서드에 주석을 추가하는 데 이 특성을 사용할 수 있습니다.

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

그리고 다음과 같은 명령을 생성합니다.

private RelayCommand? greetUserCommand;

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

참고 항목

생성된 명령의 이름은 메서드 이름을 기반으로 만들어집니다. 생성기는 메서드 이름을 사용하고 끝에 "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<T> 인터페이스를 통해 비동기 메서드 래핑을 IAsyncRelayCommand 지원합니다. 메서드가 형식을 반환 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 단추가 UI에 처음 바인딩될 때(예: 단추에) 호출된 다음 명령에서 호출될 때마다 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() 합니다. UI에는 컨트롤 바인딩이 있습니다Button. 즉, 이벤트가 발생할 때마다 CanExecuteChanged 해당 메서드를 다시 호출합니다CanExecute.GreetUserCommand 이렇게 하면 래핑된 CanGreetUser 메서드가 평가되고, UI에서 속성에 바인딩된 입력 User 인스턴스가 있는지 여부에 따라 단추의 SelectedUser 새 상태가 반환됩니다 null . 즉, GreetUserCommand 변경될 때마다 SelectedUser 이 시나리오에서 원하는 동작인 값이 속성에 있는지 여부에 따라 활성화됩니다.

참고 항목

메서드 또는 속성의 반환 값 CanExecute 이 변경된 경우 명령은 자동으로 인식되지 않습니다. 명령을 무효화하고 연결된 CanExecute 메서드를 다시 평가하도록 요청한 다음 명령에 바인딩된 컨트롤의 시각적 상태를 업데이트하도록 요청하는 IRelayCommand.NotifyCanExecuteChanged 것은 개발자의 달려 있습니다.

동시 실행 처리

명령이 비동기일 때마다 동시 실행을 허용할지 여부를 결정하도록 구성할 수 있습니다. 특성을 사용하는 RelayCommand 경우 속성을 통해 AllowConcurrentExecutions 설정할 수 있습니다. 기본값은 false실행이 보류될 때까지 명령이 해당 상태를 비활성화됨으로 알리는 것을 의미합니다. 대신 이 호출을 true설정하면 여러 동시 호출을 큐에 대기할 수 있습니다.

명령이 취소 토큰을 수락하는 경우 동시 실행이 요청되면 토큰도 취소됩니다. 기본 차이점은 동시 실행이 허용되면 명령이 다시 활성화되고기본 이전 실행이 실제로 완료될 때까지 기다리지 않고 요청된 새 실행을 시작한다는 것입니다.

비동기 예외 처리

비동기 릴레이 명령에서 예외를 처리하는 방법에는 두 가지가 있습니다.

  • Await 및 rethrow(기본값): 명령이 호출 완료를 기다리는 경우 모든 예외는 기본적으로 동일한 동기화 컨텍스트에서 throw됩니다. 이는 일반적으로 throw되는 예외가 앱을 크래시한다는 것을 의미합니다. 이는 동기 명령과 일치하는 동작입니다(예외가 throw되는 경우 앱도 충돌).
  • 작업 스케줄러에 대한 흐름 예외: 명령이 예외를 작업 스케줄러로 흐르도록 구성된 경우 throw되는 예외가 앱에 충돌하지 않고 노출을 IAsyncRelayCommand.ExecutionTask 통해 사용할 수 있을 뿐만 아니라 최대 버블링됩니다 TaskScheduler.UnobservedTaskException. 이렇게 하면 고급 시나리오(예: UI 구성 요소가 작업에 바인딩되고 작업의 결과에 따라 다른 결과가 표시됨)가 가능하지만 올바르게 사용하는 것이 더 복잡합니다.

기본 동작에는 대기 및 다시 throw 예외 명령이 있습니다. 속성을 통해 구성할 수 있습니다.FlowExceptionsToTaskScheduler

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

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

이 경우 try/catch 예외가 더 이상 앱에 충돌하지 않으므로 필요하지 않습니다. 이렇게 하면 관련 없는 다른 예외가 자동으로 다시 throw되지 않으므로 각 개별 시나리오에 접근하고 나머지 코드를 적절하게 구성하는 방법을 신중하게 결정해야 합니다.

비동기 작업에 대한 명령 취소

비동기 명령에 대한 마지막 옵션 중 하나는 생성할 취소 명령을 요청하는 기능입니다. 작업 취소를 ICommand 요청하는 데 사용할 수 있는 비동기 릴레이 명령 래핑입니다. 이 명령은 지정된 시간에 사용할 수 있는지 여부를 반영하도록 상태를 자동으로 알릴 것입니다. 예를 들어 연결된 명령이 실행되지 않는 경우 해당 상태도 실행 가능하지 않다고 보고합니다. 다음과 같이 사용할 수 있습니다.

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

이렇게 하면 DoWorkCancelCommand 속성도 생성됩니다. 그러면 사용자가 보류 중인 비동기 작업을 쉽게 취소할 수 있도록 다른 UI 구성 요소에 바인딩할 수 있습니다.

사용자 지정 특성 추가

관찰 가능한 속성RelayCommand마찬가지로 생성기에는 생성된 속성에 대한 사용자 지정 특성에 대한 지원도 포함됩니다. 이를 활용하려면 주석이 추가된 메서드를 통해 특성 목록의 대상을 사용하면 [property: ] 되며, MVVM 도구 키트는 해당 특성을 생성된 명령 속성에 전달합니다.

예를 들어 다음과 같은 메서드를 고려합니다.

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

이렇게 하면 속성 위에 특성이 있는 속성이 [JsonIgnore] 생성 GreetUserCommand 됩니다. 메서드를 대상으로 하는 특성 목록을 원하는 만큼 사용할 수 있으며 모든 특성이 생성된 속성으로 전달됩니다.

예제

  • 샘플 앱(여러 UI 프레임워크의 경우)을 확인하여 작동 중인 MVVM 도구 키트를 확인합니다.
  • 단위 테스트에서 더 많은 예제를 찾을 수도 있습니다.