MVVM Sample for Wpf (고침)

MVVM Sample for Wpf

이철우

MVP 에서 개선한 것.

  • C# WPF의 ICommand 를 이용하여
  • View 의 버튼 Event를 Model의 Method 와 Binding하는 클래스 ViewModel을 만들고
  • 이 ViewModel을 View의 DataContext에 할당하여
  • ViewModel과 View를 분리하였다.
  1. 먼저 Wpf 프로젝트(.Net 7)를 만든다.
    0-1. 프로젝트 속성에서 'Console Application’을 선택한다.
    0-2. 패키지 CommunityToolkit.Mvvm를 프로젝트에 추가한다.
    dotnet add package CommunityToolkit.Mvvm --version 8.2.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;
    }
}
  1. 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; }
    }
}
  1. 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());
        }
    }
}
  1. 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();
        }
    }
}
2 Likes

설명은 여기에 적으시고, Github Gist로 소스코드를 공유해주시는 것은 어떨까요?

마크다운으로 소스 코드를 적으려면 작은 따옴표 3개를 연달아 붙여야합니다.

2 Likes

[작은 따옴표 3개]로 해결했습니다.
Git 은 아직 활용하지 않고 있습니다.
고맙습니다.

1 Like

xaml 말고도 그 위의 C#코드도 같이 해주시면 보기 훨씬 편할 것 같습니다.

3 Likes

음… 그냥., 혹시나 해서 답변 달아 봅니다.

View에서 위 코드 처럼 Model을 직접 참조하고 있으면 MVVM 위반에 해당 됩니다. ㅎㅎ

5 Likes

네.

1 Like