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 구현을 자동으로 대체할 수 있습니다.

 

 

 

 

✔️ 예제 소스코드

 

WpfMvvmToolkit04_RelayCommand.zip
0.24MB

 

 

 

 

 

2. 순수 C#으로 ICommand 구현

더보기

✔️ 1. 동작 시나리오

  1. 사용자가 이름을 입력할 수 있는 TextBox에 이름을 입력한다.
  2. "인사하기" 버튼을 눌렀을 때
  3. Hello, [이름]! 메시지를 TextBlock에 표시한다.
  4. 이름이 비어 있으면 인사하지 않고 "이름을 입력하세요!" 라고 안내한다.

 

✔️ 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. 동작 시나리오

  1. 사용자가 이름을 입력할 수 있는 TextBox에 이름을 입력한다.
  2. "인사하기" 버튼을 눌렀을 때
  3. Hello, [이름]! 메시지를 TextBlock에 표시한다.
  4. 이름이 비어 있으면 인사하지 않고 "이름을 입력하세요!" 라고 안내한다.

 

✔️ 2RelayCommand ]으로 구현


🆕 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 속성은 양방향 바인딩

 

 

 


✔️ 3RelayCommand ]으로 구현 - 상세 주석

 

🔍 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. 동작 시나리오

  1. 버튼 컨트롤에서 전달받은 값을 전달받아
  2. RelayCommand 에 전달
  3. 콘솔 출력

 

✔️ 2RelayCommand ]으로 구현

 

📁 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. 동작 시나리오

  1. 이름 입력
  2. 체크박스 및 라디오 버튼 활용
  3. 버튼 클릭시
  4. 인사 메시지 출력
  5. 그리고 메시지를 지우는 버튼을 ClearCommand로 구현하기
  6. 입력된 이름이 없을 경우 버튼을 비활성화 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. 동작 시나리오

  • 기능:
    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가 됩니다.
  • 트리거에 의해 선택된 항목만 배경색이 변경됩니다.