1️⃣ AsyncRelayCommand<T> 개요

더보기

📚 Microsoft Docs: AsyncRelayCommand

 

 

AsyncRelayCommand<T> 개요

 

MVVM에서 비동기 작업을 Command로 실행하기 위해 사용하는 구조입니다.

여기서 <T>는 CommandParameter로 전달될 인자의 타입입니다.

 

 

 사용 예시


문제  상황해결 방법
버튼 클릭 시 API 요청, 파일 다운로드 등 시간이 걸리는 작업이 필요 async/await로 비동기 처리
ViewModel에서 Command 방식으로 비동기 로직 호출 AsyncRelayCommand<T> 사용
작업 중 중복 실행 방지 내부적으로 IsRunning 상태 추적 가능
예외 처리를 깔끔하게 하고 싶음 Try/Catch로 비동기 에러 관리 가능

 

 

 RelayCommand에 CanExecute 연동하는 방법

[RelayCommand(CanExecute = nameof(CanGreet))]  // → 이 속성 하나로 CanExecute 메서드 자동 연결됨
private void Greet()
{
    Debug.WriteLine($"안녕하세요, {userName}님!");
}

private bool CanGreet()
{
    return !string.IsNullOrWhiteSpace(userName);
}

 

 

 

 

 

2️⃣ 예제 - AsyncRelayCommand<T> 

더보기

✔️ 1. 동작 시나리오

  • 기능:
    1. 버튼 클릭 시
    2. 지연시간 2초 로딩 과정에서 로딩중 메시지 출력
    3. 비동기 로딩 → 이름 + 인사 메시지 출력
    4. 중복 클릭 시 자동 비활성화 (AsyncRelayCommand)
  • 기술 포인트:
    • [RelayCommand] + async Task (비동기 작업) 구현
    • 명령 실행 중 자동 비활성화
    • UserService를 통한 서비스 추상화
    • MVVM 구조 적용


✔️
 2. Async 구현

 

🔍 2.1 프로젝트 구조

AsyncCommandTestApp/
├── Models/
│   └── User.cs
├── Services/
│   └── IUserService.cs
│   └── UserService.cs
├── ViewModels/
│   └── MainViewModel.cs
├── MainWindow.xaml
└── MainWindow.xaml.cs

 

🔍 2.2 Model 클래스 만들기

namespace WPF_ToolKit04_06.Model
{
    // User 클래스는 MVVM 아키텍처에서 Model 역할을 수행합니다.
    // 이 클래스는 사용자 데이터를 표현하며, ViewModel과 Service에서 데이터를 주고받는 데 사용됩니다.
    public class User
    {
        // 사용자 이름을 저장하는 속성입니다.
        // 기본값은 빈 문자열로 초기화되어 있으며, 비어 있는 상태에서도 안전하게 사용 가능합니다.
        public string Name { get; set; } = string.Empty;
    }
}

 

🔍 2.3 Interface 만들기

using WPF_ToolKit04_06.Model;

namespace WPF_ToolKit04_06.Service
{
    // IUserService 인터페이스는 사용자 정보를 가져오는 서비스를 정의합니다.
    // MVVM 패턴에서 ViewModel은 이 인터페이스에 의존함으로써
    // 구현체(UserService)에 대한 직접적인 의존을 피하고 느슨한 결합(Loosely Coupled)을 유지할 수 있습니다.
    // 테스트 또는 확장 시 다양한 구현체(UserServiceMock 등)를 대체하여 활용할 수 있습니다.
    public interface IUserService
    {
        // 비동기 방식으로 현재 사용자 정보를 가져오는 메서드 정의입니다.
        // 실제 구현은 UserService 클래스에서 담당하며, API 또는 데이터베이스에서 데이터를 가져올 수 있습니다.
        Task<User> GetCurrentUserAsync();
    }
}

 

🔍 2.4 Service 클래스 만들기

public class UserService : IUserService
{
    // GetCurrentUserAsync 메서드는 사용자 정보를 비동기적으로 반환합니다.
    // 실제 프로젝트에서는 이 메서드가 데이터베이스, 웹 API, 파일 등에서 데이터를 로드하도록 구현됩니다.
    // 여기서는 Task.Delay를 통해 2초간 지연시켜 비동기 처리의 흐름을 학습할 수 있도록 구성되어 있습니다.
    public async Task<User> GetCurrentUserAsync()
    {
        await Task.Delay(2000); // 사용자 정보 로딩 시뮬레이션 (예: API 호출 대기)

        // 사용자 정보를 담은 User 객체를 반환합니다.
        // 실습 목적상 정적인 데이터를 반환하지만, 실제 구현에서는 외부 소스에서 가져온 데이터를 기반으로 구성됩니다.
        return new User { Name = "홍길동" };
    }
}

 

🔍 2.5 ViewModel 클래스 만들기

using WPF_ToolKit04_06.Model;
using WPF_ToolKit04_06.Service;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace WPF_ToolKit04_06.ViewModel
{
    // MainViewModel 클래스는 MVVM 패턴에서 ViewModel에 해당하며,
    // View와 Model(Service) 사이의 데이터 바인딩과 명령 처리를 담당합니다.
    // ObservableObject를 상속받아 속성 변경 알림(INotifyPropertyChanged)을 자동으로 처리합니다.
    public partial class MainViewModel : ObservableObject
    {
        // IUserService 인터페이스를 통해 사용자 정보를 제공받는 의존성 주입 필드입니다.
        // ViewModel은 인터페이스에만 의존하고 구체적인 구현은 외부에서 주입되므로 테스트와 유지보수에 유리합니다.
        private readonly IUserService userService;

        // 생성자에서 IUserService 구현체를 주입받아 ViewModel 내부에서 사용할 수 있게 합니다.
        public MainViewModel(IUserService userService)
        {
            this.userService = userService;
        }

        // 사용자 인사 메시지를 저장하는 속성입니다.
        // ObservableProperty 특성을 사용하면 자동으로 Greeting 속성이 생성되고
        // 속성 값 변경 시 View에 자동으로 알림이 전파됩니다.
        [ObservableProperty]
        private string? greeting;

        // GreetUser는 버튼 클릭 시 실행될 비동기 명령 메서드입니다.
        // [RelayCommand] 특성을 사용하면 GreetUserCommand 또는 GreetUserCommandAsync 같은 ICommand 속성이 자동 생성됩니다.
        // async 메서드를 사용하므로 CommunityToolkit은 내부적으로 AsyncRelayCommand를 생성합니다.
        // 실행 중에는 명령이 자동으로 비활성화되어 중복 실행을 방지합니다.
        [RelayCommand]
        private async Task GreetUser()
        {
            Greeting = "사용자 정보를 가져오는 중...";
            Console.WriteLine(Greeting);

            // 서비스에서 사용자 정보를 비동기로 가져옵니다 (예: API 호출, DB 조회 등)
            User user = await userService.GetCurrentUserAsync();

            // 사용자 이름을 포함한 인사 메시지를 설정하고, UI에 자동 반영됩니다.
            Greeting = $"안녕하세요, {user.Name}님!";
            Console.WriteLine(Greeting);
        }
    }
}

 

🔍 2.6 XAML 화면 구성

    Title="GreetApp" Height="200" Width="300">

<StackPanel Margin="20" VerticalAlignment="Center">

    <!--
    버튼 UI 요소.
    Content: 버튼에 표시될 텍스트.
    Command: ViewModel에 정의된 GreetUser 메서드에서 생성된 GreetUserCommand 속성과 바인딩됨.
    
    [RelayCommand] 특성이 붙은 GreetUser 메서드는 MVVM Toolkit이 자동으로 GreetUserCommand라는 ICommand 속성을 생성해 줌.
    사용자가 버튼을 클릭하면 GreetUserCommand가 실행되고, ViewModel에서 사용자 정보를 비동기로 가져와 인사 메시지를 구성함.
    -->
    
    <Button Content="사용자에게 인사하기"
        Command="{Binding GreetUserCommand}"
        Margin="0,0,0,10" />

    <!--
    사용자에게 인사 메시지를 보여주는 TextBlock.
    Text 속성이 ViewModel의 Greeting 속성과 바인딩되어 있음.
    
    ViewModel의 Greeting 속성이 변경되면 INotifyPropertyChanged에 의해 UI가 자동으로 갱신됨.
    FontWeight="Bold": 텍스트 강조
    FontSize="14": 글자 크기
    TextWrapping="Wrap": 텍스트가 길 경우 자동 줄바꿈
    -->
    
    <TextBlock Text="{Binding Greeting}"
           FontWeight="Bold"
           FontSize="14"
           TextWrapping="Wrap"/>
</StackPanel>

 

 

✔️ 결과

 

  • 항목 클릭 시 ViewModel.SelectedItem이 변경되며, 해당 항목의 IsSelected가 true가 됩니다.
  • 트리거에 의해 선택된 항목만 배경색이 변경됩니다.

 

 

 

 

 

3️⃣ 예제 - AsyncRelayCommand<T> 동작 구조

더보기

✔️ 1. 예제의 RelayCommand<T> 살펴보기

 // 사용자 정보 로딩 서비스
public class UserService : IUserService
{
    public async Task<User> GetCurrentUserAsync()
    {
        await Task.Delay(2000); // 사용자 정보 로딩 지연 시뮬레이션
        return new User { Name = "홍길동" };
    }
}
public partial class MainViewModel : ObservableObject
{
    [RelayCommand]
    private async Task GreetUser()
    {
        Greeting = "사용자 정보를 가져오는 중...";
        Console.WriteLine(Greeting);

        // 서비스에서 사용자 정보를 비동기로 가져옵니다 (예: API 호출, DB 조회 등)
        User user = await userService.GetCurrentUserAsync();

        Greeting = $"안녕하세요, {user.Name}님!";
        Console.WriteLine(Greeting);
    }
}

 

 

✔️ 2. 자동 생성되는 코드