MVVM 패턴으로 OK Cancel 구현
이철우
아주 사소하지만, 고객이 어떤 버튼(예로 ‘삭제’)에 대해 한 번 생각할 시간을 달라고 하면, 대개 대화상자를 띄우고 OK/Cancel에 따라 프로그램이 작동한다. 그런데, MVVM 패턴에서는 이것이 자신의 패턴을 깨는 작업이 된다.
그래서, MVVM 패턴으로 OK/Cancel 을 구현하는 사용자 컨트롤(ConfirmButton.xaml)과 그에 대응하는 ViewModel(ConfirmButtonViewModel.cs)을 작성하고 적용하였다.
닷넷 9.0, CommunityToolkit.Mvvm 8.4.0, Visual Studio Community 최신 버전을 사용하여 WPF 앱 프로젝트를 만들자. 프로젝트 속성-일반에서 출력형식을 'Console Application’으로 바꾼다. 그리고 윈도우 설정은 Height=“225” Width=“400” FontSize=“14” 로 하였다. 이 프로젝트는 한 개의 버튼(“출력”)을 가지고 있고 그 버튼을 누르면 "Start."를 출력하고 1초 뒤에 "End."를 출력한다. 이 기능을 구현하는 ViewModel.cs를 프로젝트에 추가하자.
public sealed class ViewModel : ObservableObject
{
public ViewModel()
{
PrintCommand = new AsyncRelayCommand(Print);
}
public IAsyncRelayCommand PrintCommand { get; }
private async Task Print()
{
Console.WriteLine("Start.");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine("End.");
}
}
MainWindow.xaml 윈도우 설정에 아래 줄을 추가한다.
d:DataContext="{d:DesignInstance Type=local:ViewModel, IsDesignTimeCreatable=False}"
그리고 MainWindow.xaml UI를 아래와 같이 한다.
<Grid>
<Button Content="출력" Command="{Binding PrintCommand}"/>
</Grid>
이제 UI와 ViewModel을 MainWindow.xaml.cs에서 연결하자.
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
실행하면 아래와 같이 된다.
이제 본론으로 돌아와 MVVM 패턴으로 OK/Cancel 을 구현하자. 먼저 클래스 ConfirmButtonViewModel.cs를 만들자.
public class ConfirmButtonViewModel : ObservableObject
{
public ConfirmButtonViewModel(IAsyncRelayCommand missionCommand, string missionCommandContent)
{
MissionCommand = missionCommand;
FrontCommandContent = missionCommandContent;
FrontCommand = new RelayCommand(ShowBack);
OKCommand = new AsyncRelayCommand(OK);
CancelCommand = new RelayCommand(Cancel);
}
public IRelayCommand FrontCommand { get; }
public IRelayCommand OKCommand { get; }
public IRelayCommand CancelCommand { get; }
public IAsyncRelayCommand MissionCommand { get; }
private void ShowBack()
{
IsBack = true;
}
private void ShowFront()
{
IsBack = false;
}
private async Task OK()
{
await MissionCommand.ExecuteAsync(null).ConfigureAwait(false);
ShowFront();
}
private void Cancel()
{
ShowFront();
}
public bool IsBack
{
get => _isBack;
private set => SetProperty(ref _isBack, value);
}
private bool _isBack = false;
public string FrontCommandContent
{
get => _frontCommandContent;
set => SetProperty(ref _frontCommandContent, value);
}
private string _frontCommandContent = string.Empty;
}
프로젝트에 사용자 컨트롤 ConfirmButton.xaml을 추가하고 ConfirmButton.xaml.cs에 아래와 같이 코드를 넣자.
public partial class ConfirmButton : UserControl
{
public ConfirmButton()
{
InitializeComponent();
}
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public sealed class TrueToVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public sealed class FalseToVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ConfirmButton.xaml 에 아래처럼 자원을 추가한다.
<UserControl.Resources>
<local:TrueToVisibleConverter x:Key="TrueToVisibleConverter"/>
<local:FalseToVisibleConverter x:Key="FalseToVisibleConverter"/>
</UserControl.Resources>
ConfirmButton.xaml UI를 구성하자.
<Grid>
<Button Content="{Binding FrontCommandContent}" Command="{Binding FrontCommand}" Visibility="{Binding IsBack, Converter={StaticResource FalseToVisibleConverter}}"/>
<UniformGrid Columns="2" Visibility="{Binding IsBack, Converter={StaticResource TrueToVisibleConverter}}">
<Button Content="O.K." Command="{Binding OKCommand}"/>
<Button Content="Cancel" Command="{Binding CancelCommand}"/>
</UniformGrid>
</Grid>
주 ViewModel에서 ConfirmViewModel을 활용하면 ViewModel.cs는 다음과 같다.
public sealed class ViewModel : ObservableObject
{
public ViewModel()
{
PrintCommand = new AsyncRelayCommand(Print);
ConfirmButtonViewModel = new ConfirmButtonViewModel(PrintCommand, "출력");
}
public IAsyncRelayCommand PrintCommand { get; }
public ConfirmButtonViewModel ConfirmButtonViewModel { get; }
private async Task Print()
{
Console.WriteLine("Start.");
await Task.Delay(1000).ConfigureAwait(false);
Console.WriteLine("End.");
}
}
주 UI인 MainWindow.xaml에서 ConfirmButtonViewModel과 ConfirmButton을 활용하자.
<Grid>
<!--<Button Content="출력" Command="{Binding PrintCommand}"/>-->
<local:ConfirmButton DataContext="{Binding ConfirmButtonViewModel}"/>
</Grid>
실행하고 출력 버튼을 누르면 다음과 같다.
주 UI인 MainWindow.xaml에서 ConfirmButton.xaml으로 DataContext가 Tunneling됨을 알 수 있다.
(*ConfirmButton.xaml 설정 mc:Ignorable=“d” 다음에 아래 줄을 넣으면 성가신 것이 사라진다.)
d:DataContext="{d:DesignInstance Type=local:ConfirmButtonViewModel, IsDesignTimeCreatable=False}"