04. RelayCommand

Relay(중계자) = 바통을 넘기다, 전달하다 → "동작(명령)을 전달(delegate)하는 역할"
UI Command → Relay → Method
1. RelayCommand
✔️ Microsoft Docs: CommunityToolkit.Mvvm.Input.RelayCommand

✔️ 작동 방식
CommunityToolkit.Mvvm.Input.RelayCommand의 [RelayCommand] 어트리뷰트는
ICommand 인터페이스를 자동으로 구현해주는 소스 생성기(SG) 기반 기능입니다.
[RelayCommand] 어트피뷰트를 메서드에 붙이면
해당 메서드를 실행하는 <메서드이름>Command 속성이 자동으로 생성됩니다.
UI에서 ViewModel의 메서드를 실행하도록 연결하는 복잡한 ICommand 구현을 자동으로 대체할 수 있습니다.
✔️ 예제 소스코드
2. 순수 C#으로 ICommand 구현
✔️ 1. 동작 시나리오

- 사용자가 이름을 입력할 수 있는 TextBox에 이름을 입력한다.
- "인사하기" 버튼을 눌렀을 때
- Hello, [이름]! 메시지를 TextBlock에 표시한다.
- 이름이 비어 있으면 인사하지 않고 "이름을 입력하세요!" 라고 안내한다.
✔️ 2. 순수 C#으로 ICommand 구현
📁 2.1 프로젝트 구조
WpfMvvmToolkit04_ICommand/ ├── Commands/ │ └── RelayCommand.cs ├── ViewModels/ │ └── MainViewModel.cs └── Views/ └── MainWindow.xaml ← 메인 뷰 └─ MainWindow.xaml.cs
📁 2.3 순수 C#으로 ICommand 구현
// ICommand 인터페이스 구현 클래스 public class RelayCommand : ICommand { private readonly Action execute; private readonly Func<bool>? canExecute; public RelayCommand(Action execute, Func<bool>? canExecute = null) { this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); this.canExecute = canExecute; } public bool CanExecute(object? parameter) => canExecute?.Invoke() ?? true; public void Execute(object? parameter) => execute(); public event EventHandler? CanExecuteChanged; public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
📁 2.4 순수 C#으로 ViewModel 구현
public class MainViewModel : INotifyPropertyChanged { private string? name; private string? greetingMessage; public string? Name { get => name; set { if (name != value) { name = value; OnPropertyChanged(nameof(Name)); } } } public string? GreetingMessage { get => greetingMessage; set { if (greetingMessage != value) { greetingMessage = value; OnPropertyChanged(nameof(GreetingMessage)); } } } private ICommand? sayHelloCommand; public ICommand SayHelloCommand { get { return sayHelloCommand ??= new RelayCommand(() => { if (string.IsNullOrWhiteSpace(Name)) GreetingMessage = "이름을 입력하세요!"; else GreetingMessage = $"안녕하세요, {Name}님!"; }); } } public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
📁 2.5 XAML 화면 구성

xmlns:vm="clr-namespace:WPF_ToolKit04_03.ViewModel" Title="RelayCommand Demo" Height="200" Width="300"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <StackPanel Margin="20"> <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10"/> <Button Content="인사하기" Command="{Binding SayHelloCommand}" Margin="0,0,0,10"/> <TextBlock Text="{Binding GreetingMessage}" FontWeight="Bold" /> </StackPanel>
3. [RelayCommand]로 구현(ICommand 구현 비교)
✔️ 1. 동작 시나리오

- 사용자가 이름을 입력할 수 있는 TextBox에 이름을 입력한다.
- "인사하기" 버튼을 눌렀을 때
- Hello, [이름]! 메시지를 TextBlock에 표시한다.
- 이름이 비어 있으면 인사하지 않고 "이름을 입력하세요!" 라고 안내한다.
✔️ 2. [ RelayCommand ]으로 구현
🆕 2.1 프로젝트 구조
WpfMvvmToolkit04_RelayCommand/ ├── ViewModels/ │ └── MainViewModel.cs └── Views/ └── MainWindow.xaml ← 메인 뷰 └─ MainWindow.xaml.cs
🆕 3.2 ViewModel 구현

public partial class MainViewModel : ObservableObject { [ObservableProperty] private string? name; [ObservableProperty] private string? greetingMessage; [RelayCommand] private void SayHello() { if (string.IsNullOrWhiteSpace(Name)) { GreetingMessage = "이름을 입력하세요!"; } else { GreetingMessage = $"안녕하세요, {Name}님!"; } } }
🆕 3.3 XAML 화면 구성 (동일)
xmlns:vm="clr-namespace:WPF_ToolKit04_03.ViewModel" Title="RelayCommand Demo" Height="200" Width="300"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <StackPanel Margin="20"> <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10"/> <Button Content="인사하기" Command="{Binding SayHelloCommand}" Margin="0,0,0,10"/> <TextBlock Text="{Binding GreetingMessage}" FontWeight="Bold" /> </StackPanel>
🆕 동작 구조
- 실행 흐름
- 버튼 클릭 → 명령 실행 → 결과 표시
- ViewModel
- [RelayCommand]를 붙이면 SayHelloCommand가 자동으로 생성됨
- View
- Command="{Binding SayHelloCommand}"를 통해 버튼과 연결됨
- 입력 필드와 Name 속성은 양방향 바인딩
✔️ 3. [ RelayCommand ]으로 구현 - 상세 주석
🔍 ViewModel 클래스 만들기 (MainViewModel.cs)
// MainViewModel 클래스는 MVVM 패턴에서 View와 Model 사이의 중간 역할을 담당합니다. // ObservableObject를 상속받음으로써 INotifyPropertyChanged를 자동으로 구현합니다. public partial class MainViewModel : ObservableObject { // 사용자로부터 입력받은 이름을 저장하는 속성입니다. // [ObservableProperty] 특성을 사용하면 name 필드에 대한 Name 속성이 자동 생성되고, // 속성이 변경될 때 UI에 자동으로 통지됩니다 (PropertyChanged 이벤트 발생). [ObservableProperty] private string? name; // 인사 메시지를 화면에 표시하기 위한 속성입니다. // GreetingMessage 속성은 TextBlock에 바인딩되어 UI에 출력됩니다. [ObservableProperty] private string? greetingMessage; // [RelayCommand] 특성을 메서드에 붙이면 내부적으로 SayHelloCommand라는 ICommand 속성이 자동으로 생성됩니다. // 이 명령은 XAML에서 Button의 Command 속성과 연결되어 버튼 클릭 시 이 메서드가 실행되도록 합니다. // 예: <Button Command="{Binding SayHelloCommand}" /> [RelayCommand] private void SayHello() { // Name 속성이 비어 있거나 공백일 경우 안내 메시지를 출력하고 // 값이 있을 경우 인사 메시지를 GreetingMessage 속성에 저장합니다. if (string.IsNullOrWhiteSpace(Name)) { GreetingMessage = "이름을 입력하세요!"; } else { GreetingMessage = $"안녕하세요, {Name}님!"; } // GreetingMessage 속성 값이 변경되면 PropertyChanged 이벤트가 발생하며, // 이 속성을 바인딩한 TextBlock의 UI 내용도 자동으로 업데이트됩니다. } }
🔍 XAML 화면 구성
<!-- 이 XAML 파일은 MVVM 패턴을 따르는 WPF 애플리케이션의 MainWindow입니다. ViewModel(MainViewModel)과의 바인딩을 통해 사용자 입력을 처리하고 결과를 화면에 표시합니다. --> <Window x:Class="StudentRelayCommandApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:StudentRelayCommandApp.ViewModels" Title="RelayCommand 실습" Height="200" Width="300"> <!-- ViewModel(MainViewModel)을 이 View의 DataContext로 설정합니다. 이렇게 하면 ViewModel의 속성과 명령을 이 XAML에서 직접 바인딩할 수 있습니다. --> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <!-- StackPanel은 요소를 수직으로 정렬하는 레이아웃 컨트롤입니다. --> <StackPanel Margin="20"> <!-- TextBox는 사용자로부터 이름을 입력받는 UI 요소입니다. Text 속성을 ViewModel의 Name 속성과 바인딩합니다. UpdateSourceTrigger=PropertyChanged 설정을 통해 텍스트가 변경될 때마다 ViewModel에 즉시 반영됩니다. --> <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" PlaceholderText="이름 입력" Margin="0,0,0,10"/> <!-- Button은 ViewModel의 SayHelloCommand 명령에 바인딩되어 있습니다. [RelayCommand] 특성이 적용된 SayHello() 메서드에 의해 SayHelloCommand 속성이 자동으로 생성됩니다. 버튼을 클릭하면 SayHello() 메서드가 실행되고, 그 결과가 GreetingMessage에 저장됩니다. --> <Button Content="인사하기" Command="{Binding SayHelloCommand}" Margin="0,0,0,10"/> <!-- TextBlock은 ViewModel의 GreetingMessage 속성을 바인딩하여 인사 메시지를 화면에 출력합니다. Name 속성 입력 → 버튼 클릭 → 인사 메시지 출력의 흐름을 확인할 수 있습니다. --> <TextBlock Text="{Binding GreetingMessage}" FontWeight="Bold" /> </StackPanel> </Window>
5. 예제2 - RelayCommand (CommandParameter)
✔️ 1. 동작 시나리오

- 버튼 컨트롤에서 전달받은 값을 전달받아
- RelayCommand 에 전달
- 콘솔 출력
✔️ 2. [ RelayCommand ]으로 구현
📁 2.1 프로젝트 구조
WpfMvvmToolkit04_RelayCommand02_Prams/ ├── Models/ │ └── User.cs ├── ViewModels/ │ └── MainViewModel.cs └── Views/ └── MainWindow.xaml ← 메인 뷰 └─ MainWindow.xaml.cs
📁 2.2 Model 클래스 만들기
// User 클래스는 MVVM 아키텍처에서 Model 역할을 수행합니다. // 이 클래스는 사용자 데이터를 표현하며, ViewModel과 Service에서 데이터를 주고받는 데 사용됩니다. public class User { // 사용자 이름을 저장하는 속성입니다. // 기본값은 빈 문자열로 초기화되어 있으며, 비어 있는 상태에서도 안전하게 사용 가능합니다. public string Name { get; set; } = string.Empty; }
📁 2.3 ViewModel 클래스 만들기 (MainViewModel.cs)
// MainViewModel 클래스는 MVVM 패턴에서 View와 Model 사이의 중개자 역할을 수행합니다. // ObservableObject를 상속받아 INotifyPropertyChanged를 자동으로 구현하며, // UI와 데이터 간의 바인딩이 실시간으로 동작하도록 지원합니다. public partial class MainViewModel : ObservableObject { // Users는 사용자 목록을 보관하는 ObservableCollection입니다. // ObservableCollection은 UI와 자동 동기화되며, ItemsControl 등과 바인딩할 때 자주 사용됩니다. public ObservableCollection<User> Users { get; } = new() { new User { Name = "홍길동" }, new User { Name = "김철수" }, new User { Name = "이영희" } }; // GreetUser 명령은 XAML에서 Button의 Command와 바인딩됩니다. // 이 명령은 CommandParameter로 User 객체를 전달받아 처리할 수 있습니다. // CommunityToolkit.MVVM의 [RelayCommand] 특성을 사용하면 GreetUserCommand 속성이 자동 생성됩니다. [RelayCommand] private void GreetUser(User user) { // 전달받은 User 객체의 이름을 출력합니다. // 실습 환경에서는 콘솔 출력으로 확인하며, 실제 앱에서는 메시지 박스, 로그, 뷰에 바인딩된 속성 등으로 확장할 수 있습니다. Console.WriteLine($"Hello {user.Name}!"); } }
📁 2.4 XAML 화면 구성
Title="Greet Users" Height="300" Width="300"> <!-- MainViewModel을 현재 View의 데이터 컨텍스트로 설정하여 ViewModel의 속성과 명령을 바인딩할 수 있게 합니다. --> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <StackPanel Margin="10"> <!-- Users 컬렉션을 ItemsSource로 설정하여 사용자 목록을 동적으로 생성합니다. --> <ItemsControl ItemsSource="{Binding Users}"> <ItemsControl.ItemTemplate> <DataTemplate> <!-- 각 User 항목을 수평으로 정렬하는 StackPanel로 구성합니다. --> <StackPanel Orientation="Horizontal" Margin="5"> <!-- 사용자 이름을 출력하는 TextBlock입니다. --> <TextBlock Text="{Binding Name}" Width="100"/> <!-- 사용자별 인사 버튼입니다. Command: MainViewModel의 GreetUserCommand에 바인딩합니다. RelativeSource를 통해 상위(Window)의 DataContext에서 명령을 찾습니다. CommandParameter: 현재 항목(User 객체 전체)을 매개변수로 전달합니다. 결과적으로 버튼 클릭 시 해당 User 객체가 GreetUser(User user) 메서드에 전달됩니다. --> <Button Content="인사하기" Command="{Binding DataContext.GreetUserCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" /> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel>
📁 2.5 결과
- 버튼을 클릭하면 해당 이름이 콘솔에 출력
✔️ 3. 추가 과제
- 인사 메시지를 TextBlock으로 화면에 출력
- 선택된 사용자만 강조 표시
- 사용자 추가 기능 (TextBox + Command)
6. 예제3 - CanExecute (enabling-and-disabling-commands)
✔️ 1. 동작 시나리오

- 이름 입력
- 체크박스 및 라디오 버튼 활용
- 버튼 클릭시
- 인사 메시지 출력
- 그리고 메시지를 지우는 버튼을 ClearCommand로 구현하기
- 입력된 이름이 없을 경우 버튼을 비활성화 MVVM 패턴에서 UI 제어 흐름과 조건부 명령 실행(CanExecute) 개념을 실습
개념 | 설명 |
CanExecute | 명령 실행 가능 여부를 제어하는 메서드 |
NotifyCanExecuteChanged() | 바인딩된 버튼의 활성/비활성 상태를 실시간 갱신 |
자동 생성 명령 이름 | SayHelloCommand (메서드 이름 + Command) |
✔️ 2. CanExecute 구현
📁 2.1 프로젝트 구조
WpfMvvmToolkit04_RelayCommand03_CanExcute/ ├── ViewModels/ │ └── MainViewModel.cs ├── Views/ │ └── MainWindow.xaml ← 메인 뷰 │ └─ MainWindow.xaml.cs ├── App.xaml/ │ └── App.xaml.cs └── EqualsConverter.cs
🔍 2.2 ViewModel 클래스 만들기 (MainViewModel.cs)
// MainViewModel 클래스는 MVVM 패턴에서 View와 데이터를 연결하는 역할을 합니다. // CommunityToolkit의 ObservableObject를 상속하여 속성 변경 알림(INotifyPropertyChanged)을 자동 구현합니다. public partial class MainViewModel : ObservableObject { // 이름 입력을 저장하는 속성입니다. // [ObservableProperty]를 사용하면 name 필드에 대해 Name 속성이 자동 생성되며, // UI에서 바인딩 시 TextBox 등의 입력 변경이 자동 반영되고 변경 통지가 발생합니다. [ObservableProperty] private string? name; // 인사 메시지를 저장하는 속성입니다. // GreetingMessage 속성은 TextBlock에 바인딩되어 메시지를 화면에 출력합니다. [ObservableProperty] private string? greetingMessage; // 체크박스와 바인딩되어 정중한 인사를 할지 여부를 제어하는 속성입니다. [ObservableProperty] private bool isPolite; // 라디오 버튼과 바인딩되는 성별 선택 속성입니다. [ObservableProperty] private string? selectedGender; // 버튼 클릭 시 실행되는 명령 메서드입니다. // [RelayCommand] 특성을 붙이면 자동으로 SayHelloCommand 속성이 생성되어 XAML에서 바인딩 가능해집니다. // 또한 CanExecute 조건으로 CanSayHello 메서드를 연결하여, 이름이 입력되지 않으면 버튼이 비활성화됩니다. [RelayCommand(CanExecute = nameof(CanSayHello))] private void SayHello() { // 체크박스 값에 따라 인사말을 다르게 설정 string greeting = isPolite ? "안녕하세요" : "하이"; // 선택된 성별에 따라 호칭을 다르게 설정 string? title = selectedGender switch { "남성" => "형님", "여성" => "누님", _ => "친구" }; // 최종 인사 메시지를 구성하여 GreetingMessage 속성에 저장 GreetingMessage = $"{greeting}, {Name} {title}!"; } // SayHelloCommand의 실행 가능 여부를 판단하는 메서드입니다. // 이름이 입력되어 있어야 true 반환 → 버튼 활성화됨 private bool CanSayHello() { return !string.IsNullOrWhiteSpace(Name); } // 이름이 변경될 때마다 SayHelloCommand의 CanExecute 상태를 업데이트하여 // 버튼 활성화 여부를 즉시 반영합니다. partial void OnNameChanged(string? value) { SayHelloCommand.NotifyCanExecuteChanged(); } // 메시지를 초기화하는 명령입니다. // 버튼과 ClearCommand로 바인딩되어 클릭 시 모든 속성을 기본값으로 되돌립니다. [RelayCommand] private void Clear() { GreetingMessage = string.Empty; Name = string.Empty; SelectedGender = null; IsPolite = false; } }
🔍 2.3 XAML 화면 구성
<!-- MainViewModel을 현재 View의 데이터 컨텍스트로 설정합니다. 이를 통해 XAML 내에서 ViewModel의 속성과 명령에 직접 접근할 수 있습니다. --> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <StackPanel Margin="20" VerticalAlignment="Center"> <!-- 사용자로부터 이름을 입력받는 TextBox입니다. Name 속성과 양방향 바인딩되며, 텍스트가 입력될 때마다 ViewModel의 속성이 자동 갱신됩니다. --> <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" /> <!-- 성별 선택을 위한 라디오 버튼 그룹입니다. EqualsConverter를 사용해 SelectedGender 속성과 양방향으로 바인딩되며, 남성/여성 중 하나를 선택할 수 있습니다. --> <TextBlock Text="성별 선택:" /> <StackPanel Orientation="Horizontal"> <RadioButton Content="남성" GroupName="Gender" IsChecked="{Binding SelectedGender, Converter={StaticResource EqualsConverter}, ConverterParameter=남성}" /> <RadioButton Content="여성" GroupName="Gender" IsChecked="{Binding SelectedGender, Converter={StaticResource EqualsConverter}, ConverterParameter=여성}" /> </StackPanel> <!-- 체크박스를 통해 정중한 인사를 사용할지 여부를 설정합니다. IsPolite 속성과 바인딩되어 값이 ViewModel에 실시간 반영됩니다. --> <CheckBox Content="정중한 인사 사용" IsChecked="{Binding IsPolite}"/> <!-- SayHelloCommand에 바인딩된 버튼입니다. 이름이 입력되어 있을 경우에만 활성화되며, 클릭 시 인사 메시지를 생성합니다. --> <Button Content="인사하기" Command="{Binding SayHelloCommand}"/> <!-- ClearCommand에 바인딩된 버튼입니다. 클릭 시 모든 입력 값과 인사 메시지를 초기화합니다. --> <Button Content="메시지 초기화" Command="{Binding ClearCommand}" /> <!-- ViewModel의 GreetingMessage 속성과 바인딩되어 생성된 인사 메시지를 표시합니다. --> <TextBlock Text="{Binding GreetingMessage}" FontSize="16" FontWeight="Bold" TextWrapping="Wrap"/> </StackPanel>
🔍 2.4 추가 클래스: EqualsConverter.cs (라디오버튼 바인딩을 위해 필요)

// EqualsConverter는 주로 WPF의 라디오 버튼과 ViewModel 속성 간 바인딩에 사용됩니다. // MVVM 패턴에서 문자열 값을 비교하거나 특정 값과 매칭될 때 체크 상태를 결정할 수 있도록 도와주는 변환기입니다. public class EqualsConverter : IValueConverter { // Convert 메서드는 ViewModel의 속성 값(value)과 ConverterParameter를 비교하여, // 두 값이 같으면 true, 다르면 false를 반환합니다. // 이 값은 IsChecked 속성에 바인딩되어 라디오 버튼이 선택 상태인지 여부를 결정합니다. public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value?.ToString() == parameter?.ToString(); } // ConvertBack 메서드는 라디오 버튼이 선택되었는지를 나타내는 bool 값을 받아서, // true일 경우 해당 ConverterParameter 값을 ViewModel에 전달하고, // false일 경우 바인딩을 하지 않습니다(Binding.DoNothing). public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return (bool)value ? parameter?.ToString() : Binding.DoNothing; } }
🔍 2.5 XAML 화면 구성 App.xaml 어플리케이션 전역 리소스 등록

<Application.Resources> <!-- EqualsConverter는 라디오 버튼의 선택 상태(IsChecked)와 ViewModel의 문자열 속성 간의 바인딩을 지원합니다. 예: SelectedGender 속성이 "남성"일 때만 특정 라디오 버튼이 선택되도록 제어할 수 있습니다. 이 리소스 등록은 View (XAML) 전역에서 EqualsConverter를 재사용할 수 있도록 메모리에 상주시키며, x:Key="EqualsConverter"를 통해 바인딩 시 참조할 수 있습니다. 사용 예: <RadioButton Content="남성" IsChecked="{Binding SelectedGender, Converter={StaticResource EqualsConverter}, ConverterParameter=남성}" /> 위 바인딩은 SelectedGender == "남성" 일 때 해당 라디오 버튼이 선택됨(IsChecked = true). 반대로 해당 버튼을 선택하면 SelectedGender 속성 값이 "남성"으로 설정됩니다. --> <local:EqualsConverter x:Key="EqualsConverter"/> </Application.Resources>
7. 예제3 - RelayCommand (async)
✔️ 1. 동작 시나리오

- 기능:
- 버튼 클릭 시
- 지연시간 2초 로딩 과정에서 로딩중 메시지 출력
- 비동기 로딩 → 이름 + 인사 메시지 출력
- 중복 클릭 시 자동 비활성화 (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가 됩니다.
- 트리거에 의해 선택된 항목만 배경색이 변경됩니다.
댓글을 사용할 수 없습니다.