[WPF] MVVM 패턴으로 WPF 시작하기 - 03 ViewModel 구성

WPF 시리즈 포스팅을 진행하고 있습니다. 아래 링크를 클릭하시면 연관된 다른 포스트들을 보실 수 있습니다.

 

목록

  1. MVVM 패턴으로 WPF 시작하기 - 01 프로젝트 만들기
  2. MVVM 패턴으로 WPF 시작하기 - 02 UI 구성 (Grid, ListView, StackPanel)
  3. MVVM 패턴으로 WPF 시작하기 - 04 Textbox와 ComboBox
  4. MVVM 패턴으로 WPF 시작하기 - 05 Converter

1. MVVM 패턴

뷰모델을 만들기 전, 먼저 WPF의 디자인 패턴인 MVVM 패턴을 알아보도록 하겠습니다.

 

프로그램을 만드는데 여러가지 디자인 패턴이 각 프레임워크에 맞게 사용됩니다. 처음에는 MVC 패턴을 사용하다 점차 파생되어 MVP 그리고 오늘 저희가 볼 MVVM 패턴까지 나오게 되었습니다.

 

MVVM 패턴은 Model + View + View Model를 합친 용어입니다. 

  • Model: 어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분입니다.
  • View: 사용자에게 보여지는 UI 부분입니다.
  • ViewModel: View를 위한 모델입니다. View에서 나타내기 위한 Model이며 데이터 처리를 하는 부분입니다. 

 

이것을 그래프로 표현하면 이렇게 됩니다.

사용자가 View에서 데이터를 볼 때, View에서 비춰지는 데이터는 ViewModel입니다. 그리고 그 ViewModel은 어떤 Model에 대한 데이터를 가지고 있습니다.

 

 

 

2. CommunityToolkit.MVVM 다운받기

MVVM을 다루기 위해서 많은 방법들이 존재합니다. 특히 라이브러리를 사용하여 MVVM을 구현하면 더욱 편리한데요. 대표적인 라이브러리로는 

  • Prism Library - Infragistics
  • MVVM Light - Galasoft
  • CommunityToolkit.Mvvm - Microsoft

이렇게 3개가 존재합니다. 

 

저는 Microsoft에서 정식으로 지원하는 CommunityToolkit.Mvvm을 사용하여 MVVM을 구성하도록 하겠습니다.

 

먼저, Visual Studio에서 "도구" -> "Nuget 패키지 관리자" -> "솔루션용 Nuget 패키지 관리" 로 들어갑니다.

 

 

그 다음, 찾아보기를 클릭하시고 검색창에 "CommunityToolkit.Mvvm"를 입력합니다.

 

 

CommunityToolkit.Mvvm 선택하시고 설치를 눌러주세요.

 

 

설치가 잘 되었으면 솔루션 탐색기와 Nuget 패키지 관리창에서 설치됨을 확인하실 수 있습니다.

 

 

3. ViewModel 만들기

그럼 한번 ViewModel을 생성해 봅시다.

 

먼저, 프로젝트 구성을 보다 쉽게 접근하고 보기 위해서 폴더 구조를 다시 만들게요.

 

ViewModels, Models, Enums 라는 폴더를 새로 프로젝트 최상위단에 만드시고 기존 MainWindow.xaml를 View 폴더로 옮겨주시거나, 삭제하시고 View에 새로 만드셔도 됩니다.

 

※주의

MainWindow.xaml을 특정 폴더로 옮기게 되면 네임스페이스에 영향을 줍니다. 코드 비하인드와 xaml 파일에서 네임스페이스를 해당 폴더 경로로 변경 시켜주세요

 

 

그 다음, App.xaml에서 "StartupUri" 속성을 삭제해 주세요.

StartupUri 속성은 앱 실행 시, 처음 켜질 윈도우를 가르키는 속성입니다. 저는 이 방법 말고 다른 방법으로 메인 윈도우를 찾아 연결 시키도록 하겠습니다.

<Application
    x:Class="MyFirstWPFApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyFirstWPFApp">
    <Application.Resources />
</Application>

 

 

ViewModel을 만들기 전, 뼈대가 되는 Model을 먼저 생성하도록 하겠습니다.

Models 폴더에 Person.cs라는 새로운 파일을 생성해주시고 아래와 같이 코딩해 주세요

using CommunityToolkit.Mvvm.ComponentModel;

namespace MyFirstWPFApp.Models
{
    public class Person : ObservableObject
    {
        private int _id;
        public int Id
        {
            get { return _id; }
            set
            {
                SetProperty(ref _id, value);
            }
        }

        private string? _name;
        public string? Name
        {
            get { return _name; }
            set
            {
                SetProperty(ref _name, value);
            }
        }

        private bool _gender;
        public bool Gender
        {
            get { return _gender; }
            set
            {
                SetProperty(ref _gender, value);
            }
        }
    }
}

ObservableObject 라는 추상클래스를 상속했습니다. ObservableObject 클래스는 위에 Nuget 패키지로 설차한 CommunityToolkit.Mvvm 라이브러리에 속한 추상 클래스이며, INotifyPropertyChanged 와 INotifyPropertyChanging이라는 인터페이스를 상속하고 있습니다.

 

즉, 이 ObservableObject 상속을 통해 Model에 선언된 변수들의 변화를 체크할 수 있습니다.

 

 

이제 Model이 준비가 되었으니 ViewModel을 생성합시다.

ViewModels 폴더에 MainWindowViewModel.cs라는 파일을 생성해 주시고 아래와 같이 코딩합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using MyFirstWPFApp.Models;

namespace MyFirstWPFApp.ViewModels
{
    public class MainWindowViewModel : ViewModelBase
    {
        private IList<Person>? _people;
        public IList<Person>? People
        {
            get => _people;
            set => SetProperty(ref _people, value);
        }

        private Person? _selectedPerson;
        public Person? SelectedPerson
        {
            get => _selectedPerson;
            set => SetProperty(ref _selectedPerson, value);
        }

        public MainWindowViewModel()
        {
            People = new List<Person>
            {
                new Person{Id = 119302, Name = "김정현", Gender = true},
                new Person{Id = 119801, Name = "이현재", Gender = true},
                new Person{Id = 111202, Name = "송정환", Gender = true},
                new Person{Id = 111319, Name = "이보현", Gender = true},
                new Person{Id = 112215, Name = "고진욱", Gender = true},
                new Person{Id = 112221, Name = "황보아", Gender = true},
                new Person{Id = 200111, Name = "김푸름", Gender = true},
                new Person{Id = 112113, Name = "백아인", Gender = true},
                new Person{Id = 112109, Name = "신소현", Gender = false},
                new Person{Id = 112206, Name = "주명길", Gender = true},
            };
        }

    }
}

미리 People이라는 리스트 속성을 가진 변수에 Person 객체를 생성하여 MainWindowViewModel 객체가 생성될 때 넣도록 하였습니다.

 

그리고 코딩 하시면서 에러가 발생하는 것을 볼 수 있을 겁니다. ViewModelBase라는 클래스를 상속하고 있기 때문입니다.

ViewModelBase는 제가 따로 만든 클래스입니다. 모든 뷰 모델의 베이스 뷰모델을 만들어 관리하도록 합니다.

MainWindowViewModel.cs가 있는 폴더에 Bases라는 폴더를 생성하고 그 폴더 안에 생성합니다. 코드는 다음과 같습니다.

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyFirstWPFApp.ViewModels.Bases
{
    public class ViewModelBase : ObservableObject
    {

    }
}

 보시는 바와 같이 ObservableObject만 상속되어 있습니다. 아직 달리 선언할 변수 혹은 메서드 들이 없기에 그대로 두었습니다. 추후 추가적으로 필요한 요소들이 있다면 추가하도록 하겠습니다.

 

그럼 다시 MainWindowViewModel.cs로 돌아와 살펴보게 되면 에러가 사라진 것을 보실 수 있습니다.

 

 

4. Binding 

이전 포스트에서 xaml 파일을 보셨다면 {Binding People}과 같은 표현의 문구를 보셨을 겁니다. 

이 말은 즉슨, 현재 "이 창의 ViewModel에 People이라는 속성을 가진 변수를 가지고 와라" 라는 말과 동일합니다.

 

근데 아직 우리는 MainWindowViewModel을 아직 MainWindow에 연결시키지 않았습니다. 바인딩을 하기위해 연결을 시켜줍시다.

방법은 MainWindow.xaml의 코드 비하인드 생성자에서 DataContext에 MainWindowViewModel을 연결시켜주는 겁니다.

using MyFirstWPFApp.ViewModels;
using System.Windows;

namespace MyFirstWPFApp.Views
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }
}

자! 이로써 연결까지 마쳤습니다.

이제부터 MainWindow.xaml에서는 DataContext로 등록된 MainWindowViewModel을 통하여 각 속성을 건드릴 수 있게 되었습니다. "{Binding People}"이라는 xaml 명령어를 통하여  MainWindowViewModel안에 People을 바인딩할 수 있습니다.

 

 

 

5. MainWindow 실행

ViewModel을 생성하기 앞서 우리는 App.xaml에서 "StartUpUri"라는 속성을 지웠습니다. 그래서 프로젝트를 실행시켜도 어떠한 창도 열리지 않을겁니다. 왜냐하면 처음 열리는 창을 MainWindow.xaml로 가르키고 있지 않기 때문입니다.

 

App.xaml에서 "StartUp"이라는 속성을 Window 태그 안에 넣어주고 "새 이벤트 처리기"를 엔터하면 자동으로 코드 비하인드에 "Application_Startup"라는 이벤트가 생깁니다.

<Application
    x:Class="MyFirstWPFApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyFirstWPFApp"
    Startup="Application_Startup">
    <Application.Resources />
</Application>

 

 

StartUp 속성은 프로그램이 최초 실행이 될때 시작되는 이벤트입니다. 여기에 MainWindow.xaml 객체를 하나 생성하여 띄우게 합시다.

using MyFirstWPFApp.Views;
using System.Windows;

namespace MyFirstWPFApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            MainWindow main = new();
            main.ShowDialog();
        }
    }
}

 

그럼 프로그램을 실행 시키서 확인해 봅니다.

아주 잘 나오네요! 

 

리스트를 하나 클릭하면 StackPanel에 생성되는 것을 볼 수 있습니다.


잘 따라 오셨나요?

 

현재까지 전체 소스코드는 아래 깃허브에서 다운받아 사용하실 수 있습니다.

https://github.com/epqmqhd/BasicWPF_03

 

 

다음 포스트에는 MVVM을 응용하여 직접 UI 상에 수정할 수 있도록 만들어 보겠습니다.

 

감사합니다.