MVVM Sample for Wpf
이철우
MVP 에서 개선한 것.
- C# WPF의 ICommand 를 이용하여
- View 의 버튼 Event를 Model의 Method 와 Binding하는 클래스 ViewModel을 만들고
- 이 ViewModel을 View의 DataContext에 할당하여
- ViewModel과 View를 분리하였다.
-
먼저 Wpf 프로젝트(.Net 7)를 만든다.
0-1. 프로젝트 속성에서 'Console Application’을 선택한다.
0-2. 패키지 CommunityToolkit.Mvvm를 프로젝트에 추가한다.
dotnet add package CommunityToolkit.Mvvm --version 8.2.2 -
Model.cs
using System;
using System.ComponentModel;
namespace MvvmSample.Wpf
{
public class Model : INotifyPropertyChanged
{
public Model() { }
public int Data
{
get => _data;
set
{
if (_data != value)
{
_data = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Data)));
Console.WriteLine(ToString());
}
}
}
private int _data = 0;
public void Reset() => Data = 0;
public void Increase() => Data++;
public override string ToString() => $"Data:{Data}";
public event PropertyChangedEventHandler? PropertyChanged;
}
}
- ViewModel.cs
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MvvmSample.Wpf
{
public class ViewModel : ObservableObject
{
public Model Model { get; init; }
public ViewModel(Model model)
{
Model = model;
ResetCommand = new RelayCommand(Model.Reset);
IncreaseCommand = new RelayCommand(Model.Increase);
}
public ICommand ResetCommand { get; }
public ICommand IncreaseCommand { get; }
}
}
- MainWindow.xaml.cs
using System.Windows;
namespace MvvmSample.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel(new Model());
}
}
}
- MainWindow.xaml
<Window x:Class="MvvmSample.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmSample.Wpf"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:ViewModel, IsDesignTimeCreatable=False}"
Title="MVVM for C# WPF" Height="120" Width="360" FontSize="14">
<DockPanel LastChildFill="False">
<DockPanel DockPanel.Dock="Top" LastChildFill="False">
<Label Content="Data" DockPanel.Dock="Left"/>
<TextBox Text="{Binding Path=Model.Data}" Width="100" TextAlignment="Right"/>
</DockPanel>
<DockPanel DockPanel.Dock="Top" LastChildFill="False">
<Button Content="Reset" Command="{Binding Path=ResetCommand}"/>
<Button Content="Increase" Command="{Binding Path=IncreaseCommand}"/>
</DockPanel>
</DockPanel>
</Window>
위 내용을 조금 고칩니다.
MVVM에서 Model에 대하여 위키피디아를 참고 하니,
상태(real state content), 데이터 접근 층(data access layer) 또는 내용(content)이라고 합니다.
저는 동작(Processing)까지를 포함하는 줄 알았습니다.
그러므로 위 예제에서 Model.cs에서,
두 Method - Increase(), Reset() - 을 ViewModel.cs로 옮기겠습니다.
그리고, Model.Data를 참조하는 프로퍼티 Data를 ViewModel에 추가하고,
두 Method는 추가한 프로퍼티에 대해 동작하도록 합니다.
Model.cs에는 Data만 남습니다.
MainWindow.xaml에서 Binding Path=Model.Data는 Binding Path=Data로 바꿉니다.
이 부분이 View가 Model을 참조한 곳이었습니다.
ViewModel 생성자에서 Model을 생성하고 View의 Datacontext에 할당합니다.
Model을 ViewModel에 주입한 것이 View가 Model을 참조하는 것은 아닙니다.
ViewModel에 Model의 상태, 내용, 데이터를 바꾸는 동작이 있어야 한다는 것을 알게되었습니다.
아래는 코드입니다.
Model.cs
using System;
namespace MvvmSample.Wpf
{
public class Model
{
public Model() { Data = 0; }
public int Data
{
get => _data;
set
{
if (_data != value)
{
_data = value;
Console.WriteLine(this);
}
}
}
public int _data = 0;
public override string ToString() => $"Data:{Data}";
}
}
ViewModel.cs
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MvvmSample.Wpf
{
public class ViewModel : ObservableObject
{
public readonly Model _model;
public ViewModel()
{
_model = new Model();
ResetCommand = new RelayCommand(Reset);
IncreaseCommand = new RelayCommand(Increase);
}
public int Data
{
get => _model.Data;
set => SetProperty(_model.Data, value, _model, (u, n) => u.Data = n);
}
private void Increase()
{
Data++;
}
private void Reset()
{
Data = 0;
}
public ICommand ResetCommand { get; }
public ICommand IncreaseCommand { get; }
}
}
MainWindow.xaml
<Window x:Class="MvvmSample.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmSample.Wpf"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:ViewModel, IsDesignTimeCreatable=False}"
Title="MVVM for C# WPF" Height="120" Width="360" FontSize="14">
<DockPanel LastChildFill="False">
<DockPanel DockPanel.Dock="Top" LastChildFill="False">
<Label Content="Data" DockPanel.Dock="Left"/>
<TextBox Text="{Binding Path=Data}" Width="100" TextAlignment="Right"/>
</DockPanel>
<DockPanel DockPanel.Dock="Top" LastChildFill="False">
<Button Content="Reset" Command="{Binding Path=ResetCommand}"/>
<Button Content="Increase" Command="{Binding Path=IncreaseCommand}"/>
</DockPanel>
</DockPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MvvmSample.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}