MVVM 패턴에서는 명령을 구현하는 여러 가지 방식이 존재하며, 각각의 특성과 사용 목적에 따라 선택할 수 있다.
1. 커스텀 명령 클래스: DelegateCommand
다음은 간단한 ICommand 인터페이스를 직접 구현한 예제이다. 이 방식은 유연성과 제어력을 제공하지만, 추가적인 라이브러리 없이도 사용 가능하다.
public class CustomCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public CustomCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) != false;
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChange() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
뷰모델에서는 다음과 같이 활용할 수 있다:
public class MainViewModel
{
public ICommand SaveAction { get; }
public ICommand RemoveItem { get; }
public MainViewModel()
{
SaveAction = new CustomCommand(OnSave, CanSave);
RemoveItem = new CustomCommand<Customer>(OnRemove, CanRemove);
}
private void OnSave(object param) { /* 저장 로직 */ }
private bool CanSave(object param) => true;
private void OnRemove(Customer item) { /* 삭제 로직 */ }
private bool CanRemove(Customer item) => item != null;
}
2. CommunityToolkit.Mvvm 라이브러리 사용
최신 프로젝트에서는 CommunityToolkit.Mvvm을 활용해 간결하고 안정적인 명령 정의가 가능하다.
using CommunityToolkit.Mvvm.Input;
public partial class MainViewModel
{
[RelayCommand]
private void SaveData() => Console.WriteLine("저장됨");
[RelayCommand]
private void RemoveCustomer(Customer customer)
{
// 고객 삭제 로직
}
[RelayCommand]
private async Task FetchDataAsync()
{
await Task.Delay(500);
// 비동기 데이터 로드
}
}
이 방식은 컴파일 타임 코드 생성을 기반으로 하며, 성능과 유지보수성 측면에서 우수하다.
3. Prism 프레임워크 통합
대규모 애플리케이션에서 사용되는 Prism.Commands는 강력한 기능을 제공한다.
using Prism.Commands;
public class MainViewModel
{
public DelegateCommand SaveCommand { get; }
public DelegateCommand<Customer> RemoveCommand { get; }
public MainViewModel()
{
SaveCommand = new DelegateCommand(Save, CanSave);
RemoveCommand = new DelegateCommand<Customer>(Remove, CanRemove);
}
private void Save() { /* 저장 처리 */ }
private bool CanSave() => true;
private void Remove(Customer item) { /* 삭제 처리 */ }
private bool CanRemove(Customer item) => item != null;
}
비교 및 선택 기준
| 방식 | 장점 | 단점 | 추천 대상 |
|---|---|---|---|
커스텀 DelegateCommand |
완전한 제어, 의존성 없음 | 반복 코드, 유지보수 어려움 | 단순 프로젝트 또는 교육용 |
CommunityToolkit.Mvvm |
간결한 문법, 비동기 지원, 속성 변경 알림 | 추가 라이브러리 필요 | 신규 개발 프로젝트 |
Prism의 DelegateCommand |
확장성, 전체 프레임워크 통합 | 무거운 라이브러리, 복잡성 | 대규모 워크플로우 기반 앱 |
매개변수 전달 예시 (XAML 바인딩)
명령에 파라미터를 전달하는 방법은 다음과 같다.
<!-- 고정 값 전달 -->
<Button Content="초기화"
Command="{Binding ResetCommand}"
CommandParameter="default" />
<!-- 현재 항목 바인딩 (ListView/ListBox 내부) -->
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<Button Content="삭제"
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
이처럼, 명령과 데이터 사이의 연결을 효과적으로 관리할 수 있으며, 특히 컬렉션 내 요소별 작업 시 매우 유용하다.