2.1.1 예제 - MVVM 수동 구현
1️⃣ 학습 목표
: 예제의 학습 목표와 동작 시나리오를 파악한 뒤, 소스코드를 직접 확인합니다.
✔️ 1. 학습 목표
- Step 1.
- CommunityToolkit.Mvvm 패키지를 통한 자동완성 기능을 학습하기에 앞서,
전통적인 방식의 C# MVVM 패턴 구현 방법을 되짚어 봅니다. - INotifyPropertyChanged 인터페이스를 상속 구현하여,
프로퍼티가 변경되었을 때, View에 반영되는 로직을 구현하는 방법을 되짚어봅니다.
- CommunityToolkit.Mvvm 패키지를 통한 자동완성 기능을 학습하기에 앞서,
- Step 2.
- 이후에 CommunityToolkit.Mvvm 패키지를 사용해 자동 구현할 때,
어떤 부분이 자동화되어 편리한지 확인합니다.
- 이후에 CommunityToolkit.Mvvm 패키지를 사용해 자동 구현할 때,
- Step 3.
- 그리고 프로젝트의 어느 부분에 적용하면 효과적일지 예상해봅니다.
✔️ 2. 예제 동작 시나리오

- TextBox에 문자열을 입력하면
- TextBlock에 반영되어 동시에 출력됩니다.
2️⃣ INotifyPropertyChanged - 동작 구조
: MVVM 구조에서 View의 동작과 ViewModel Property와 연결을 구현하는 방법입니다.

①ViewModel 네임스페이스를 ②View에 등록합니다. ③DataContext를 지정합니다.

④View에서 입력받은 데이터가 ⑤바인딩 된 ⑥ViewModel의 Name 프로퍼티의 Setter 실행 후
⑦name 필드 값으로 할당됩니다.

⑧ViewModel의 Name 프로퍼티의 Setter 로직 내부의 OnPropertyChanged 메서드가 동작하면,
PropertyChanged 이벤트를 발생시킵니다.
WPF 바인딩 엔진이 PropertyChanged 이벤트를 감지하면
Name 프로퍼티와 바인딩된 View의 컨트롤에 업데이트를 요청합니다.
//이벤트 구독자(View → WPF 바인딩 엔진)가 이 이벤트를 수신
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))
업데이트 요청을 동작하기 위해 ⑨ViewModel의 Name 프로퍼티 Getter를 통해
⑩View 바인딩된 컨트롤에 ⑪반영됩니다.
3️⃣ PropertyChanged?.Invoke(...) - 동작 구조
: 개발자가 별도로 이벤트 핸들러를 등록하지 않아도, WPF 바인딩 엔진이 내부적으로 리스너 역할을 합니다.

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
PropertyChanged는 이벤트(event)입니다.
PropertyChanged?.Invoke(...)는 이벤트에 연결된 모든 이벤트 핸들러(delegate)를 호출합니다.
단계 | 설명 |
① | PropertyChanged 이벤트에 등록된 구독자(delegate) 목록 확인 |
② | 이벤트에 구독자가 있으면? → 각 delegate를 순서대로 호출 |
③ | delegate의 메서드 시그니처는 PropertyChangedEventHandler 형태 |
④ | 각 delegate는 (object sender, PropertyChangedEventArgs e) 파라미터를 전달받음 ➡️ 개발자가 별도로 이벤트 핸들러를 등록하지 않아도, WPF 바인딩 엔진이 내부적으로 리스너 역할을 함 |
⑤ | WPF의 바인딩 엔진(리스너로 등록되어 있음)이 이 이벤트를 수신함 |
⑥ | 바인딩 엔진이 어떤 속성이 변경되었는지(e.PropertyName) 확인 |
⑦ | 해당 속성을 바인딩한 View 요소(UI 컨트롤)를 찾아 값 업데이트 요청 |
⑧ | View(UI)가 최신 값을 반영 (예: TextBlock.Text 갱신) |
4️⃣ Ex01INotifyPropertyChanged
: 간단한 프로젝트를 INotifyPropertyChanged 인터페이스를 직접 상속받아 자동완성 없이, 전통적인 방식으로 단계별로 구현해봅니다.
📁 1. 가장먼저, 아래 링크를 참고해 WPF 프로젝트를 생성 합니다.
WPF 프로젝트 생성하기
01. Visual Studio 실행더보기 02. 새 프로젝트 생성더보기❶ 최근 실행한 프로젝트의 목록을 보여줍니다.❷ GitHub, Azure, DevOps, Bitbucket 등 원격 저장소에서 프로젝트를 가져옵니다.❸ 내 컴퓨터의 로컬
basiclike.tistory.com
📁 2. 생성한 WPF 프로젝트에, 아래 링크를 참고해 CommunityToolkit.Mvvm 패키지를 설치합니다.
1.2 패키지 설치 방법
1️⃣ "NuGet 패키지 관리자"에서, CommunityToolkit.Mvvm 설치하는 방법더보기 WPF 프로젝트 생성하기01. Visual Studio 실행더보기 02. 새 프로젝트 생성더보기❶ 최근 실행한 프로젝트의 목록을 보여줍니다.
basiclike.tistory.com
📁 3. 프로젝트 구조
Ch01_Sec03_Ex01SourceGenerator/
├── ViewModels/
│ ├── MainViewModel.cs ← 메인 뷰모델
├── Views/
│ ├── MainWindow.xaml ← 메인 뷰
│ └── MainWindow.xaml.cs
├── App.xaml
└── App.xaml.cs
📁 4. ViewModel 클래스 만들기

①ViewModels 폴더에, MainViewModel.cs를 생성하고
②아래 소스코드를 붙여넣으면, ③관련 라이브러리가 자동 참조됩니다.
using System;
using System.ComponentModel;
using System.Collections.Generic;
/// <summary>
/// MainViewModel 클래스는 MVVM 패턴에서 ViewModel 역할을 수행합니다.
/// ViewModel은 UI(View)와 데이터(Model) 사이에서 데이터 바인딩을 중계하는 역할을 합니다.
/// </summary>
public class MainViewModel : INotifyPropertyChanged
{
// 실제 데이터를 저장하는 private 필드
// ViewModel의 속성(Property)은 이 필드를 기반으로 동작합니다.
private string? name;
/// <summary>
/// Name 속성은 View(XAML UI)와 데이터 바인딩됩니다.
/// 사용자가 TextBox에 값을 입력하면, 이 속성의 setter가 호출됩니다.
/// setter 내부에서 값이 실제로 변경되면 OnPropertyChanged를 호출하여 UI에 변경 사실을 알립니다.
/// </summary>
public string Name
{
// name 필드의 값을 반환하되, null일 경우 빈 문자열로 처리
get => name ?? string.Empty;
// 값 설정 시, 기존 값과 다르면 변경 및 변경 알림 호출
set
{
// 값이 이전 값과 다를 경우에만 변경 처리 (불필요한 이벤트 방지)
if (!EqualityComparer<string>.Default.Equals(name, value))
{
name = value;
// UI(View) 측에 "Name" 속성이 변경되었음을 알림
OnPropertyChanged(nameof(Name));
}
}
}
/// <summary>
/// INotifyPropertyChanged 인터페이스의 이벤트입니다.
/// 속성 값이 변경되면 이 이벤트를 발생시켜, 바인딩된 UI가 자동으로 업데이트됩니다.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 속성 변경을 UI에 알리기 위한 메서드입니다.
/// PropertyChanged 이벤트를 통해 바인딩된 컨트롤에 변경 사실을 전달합니다.
/// </summary>
/// <param name="propertyName">변경된 속성의 이름 (예: "Name")</param>
protected virtual void OnPropertyChanged(string propertyName)
{
// 이벤트 구독자가 있을 경우에만 호출하여 성능 최적화
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
📁 5. View 구성하기

④Views 폴더 아래에, MainWindow.xaml을 이동시킵니다.(또는 새로운 MainView를 생성합니다.)
⑤아래 XAML 코드를 붙여넣고, xmlns를 본인 프로젝트의 ViewModel 경로로 지정합니다.
<!-- MainWindow.xaml -->
<Window x:Class="WpfMvvmToolkit03_INotifyPropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- ViewModel 클래스가 위치한 네임스페이스 참조 -->
xmlns:vm="clr-namespace:WpfMvvmToolkit03_INotifyPropertyChanged.ViewModel"
Title="INotifyPropertyChanged Demo" Height="200" Width="400">
<!-- View의 DataContext를 MainViewModel로 설정 -->
<!-- 이렇게 하면 View에서 ViewModel의 속성에 바인딩할 수 있게 됩니다 -->
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<StackPanel Margin="20">
<!-- TextBox: 사용자가 입력하는 UI 요소 -->
<!-- Name 속성과 양방향 바인딩되어, 텍스트를 입력하면 ViewModel.Name이 자동 갱신됩니다 -->
<!-- UpdateSourceTrigger=PropertyChanged: 사용자가 입력할 때마다 즉시 ViewModel에 반영 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
FontSize="14" Margin="0,0,0,10"/>
<!-- TextBlock: 바인딩된 ViewModel.Name 값을 실시간으로 표시 -->
<!-- ViewModel에서 Name 값이 변경되면, TextBlock에도 자동으로 반영됩니다 -->
<TextBlock Text="{Binding Name}" FontSize="20" FontWeight="Bold"/>
</StackPanel>
</Window>
📁6. App 수정

⑤App.Xaml 에서 시작 View 경로를, ⑥ViewModel 폴더로 이동시킨 MainWindow로 변경합니다.