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๊ฐœ์˜ ์ข‹์•„์š”

์„ค๋ช…์€ ์—ฌ๊ธฐ์— ์ ์œผ์‹œ๊ณ , Github Gist๋กœ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•ด์ฃผ์‹œ๋Š” ๊ฒƒ์€ ์–ด๋–จ๊นŒ์š”?

๋งˆํฌ๋‹ค์šด์œผ๋กœ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ ์œผ๋ ค๋ฉด ์ž‘์€ ๋”ฐ์˜ดํ‘œ 3๊ฐœ๋ฅผ ์—ฐ๋‹ฌ์•„ ๋ถ™์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์ข‹์•„์š”

๏ผป์ž‘์€ ๋”ฐ์˜ดํ‘œ 3๊ฐœ๏ผฝ๋กœ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค๏ผŽ
๏ผง๏ฝ‰๏ฝ” ์€ ์•„์ง ํ™œ์šฉํ•˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค๏ผŽ
๊ณ ๋ง™์Šต๋‹ˆ๋‹ค๏ผŽ

1๊ฐœ์˜ ์ข‹์•„์š”

xaml ๋ง๊ณ ๋„ ๊ทธ ์œ„์˜ C#์ฝ”๋“œ๋„ ๊ฐ™์ด ํ•ด์ฃผ์‹œ๋ฉด ๋ณด๊ธฐ ํ›จ์”ฌ ํŽธํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

3๊ฐœ์˜ ์ข‹์•„์š”

์Œโ€ฆ ๊ทธ๋ƒฅ., ํ˜น์‹œ๋‚˜ ํ•ด์„œ ๋‹ต๋ณ€ ๋‹ฌ์•„ ๋ด…๋‹ˆ๋‹ค.

View์—์„œ ์œ„ ์ฝ”๋“œ ์ฒ˜๋Ÿผ Model์„ ์ง์ ‘ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์œผ๋ฉด MVVM ์œ„๋ฐ˜์— ํ•ด๋‹น ๋ฉ๋‹ˆ๋‹ค. ใ…Žใ…Ž

5๊ฐœ์˜ ์ข‹์•„์š”

๋„ค.

1๊ฐœ์˜ ์ข‹์•„์š”