03. ObservableProperty
Observable = "감지 가능한" 으로 해석하면, 좀 더 이해하기 쉽습니다.
1️⃣ ObservableProperty 개요
📚 Microsoft Docs: Microsoft CommunityToolkit MVVM - ObservableProperty

✅ ObservableProperty 개요
MVVM Toolkit의 [ObservableProperty] 어트리뷰트가 사용되면,
Source Generator가 C# MVVM 구현에 사용된 INotifyPropertyChanged(프로퍼티 변경 알림) 기반의 프로퍼티 선언 + PropertyChanged 이벤트 발생 코드를 자동 생성합니다.
✅ ObservableProperty 사용법
[ObservableProperty]
private string name;
사용방법은, 필드를 선언하고 해당 필드 위에 [ObservableProperty] 어트리뷰트를 붙이면 됩니다.
그러면, 자동으로 View에서 바인딩 가능한 프로퍼티 및 기능이 생성됩니다.
✅ 예제 소스코드
2️⃣ 비교( 순수C#MVVM vs CommunityToolkit )
*예제 파일의 프로젝트명: WpfMvvmToolkit03_INotifyPropertyChanged
✔️ 1. 동작 시나리오

- TextBox에 문자열을 입력하면
- TextBlock에 반영되어 출력됩니다.
✔️ 2. 순수 C#으로 INotifyPropertyChanged 구현
📁 2.1 프로젝트 구조
WpfMvvmToolkit03_INotifyPropertyChanged/
├── ViewModels/
│ ├── MainViewModel.cs
├── Views/
│ ├── MainWindow.xaml ← 메인 뷰
│ ├── MainWindow.xaml.cs
└── App.xaml
└── App.xaml.cs
📁 2.2 ViewModel 클래스 만들기
// 이 코드는 Name 프로퍼티을 포함, ViewModel 클래스 내에 위치할 수 있으며,
// OnPropertyChanged 메서드를 직접 구현하여 속성 변경 알림을 처리합니다.
// 이 코드는 MVVM 패턴에서 ViewModel이 UI와 데이터를 연결할 수 있게 해주며,
// Name 값이 변경될 때마다 바인딩된 UI 컨트롤이 자동으로 갱신됩니다.
using System;
using System.ComponentModel;
using System.Collections.Generic;
public class MainViewModel : INotifyPropertyChanged
{
private string? name;
public string Name
{
get => name ?? string.Empty;
set
{
if (!EqualityComparer<string>.Default.Equals(name, value))
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
// INotifyPropertyChanged 구현
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
📁 2.3 XAML 화면 구성하기
xmlns:vm="clr-namespace:WpfMvvmToolkit03_INotifyPropertyChanged.ViewModel"
Title="INotifyPropertyChanged Demo" Height="200" Width="400">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<StackPanel Margin="20">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
FontSize="14" Margin="0,0,0,10"/>
<TextBlock Text="{Binding Name}" FontSize="20" FontWeight="Bold"/>
</StackPanel>
🔍 3. [ObservableProperty] 사용 - 상세 주석
// PersonViewModel 클래스는 MVVM 패턴에서 ViewModel 역할을 수행합니다.
// ObservableObject를 상속받아 INotifyPropertyChanged를 자동으로 구현합니다.
using CommunityToolkit.Mvvm.ComponentModel;
public partial class PersonViewModel : ObservableObject
{
// private 필드(name)에 대해 자동으로 public 프로퍼티(Name)이 생성됩니다.
// 또한 프로퍼니가 변경될 때 INotifyPropertyChanged 인터페이스를 통해 UI에 반영되도록 합니다.
[ObservableProperty]
private string? name;
}
3️⃣ 예제1
✔️ 1. 동작 시나리오

- TextBox에 입력하면
- 콘솔에 변경 로그가 찍힙니다.
- ListBox에서 아이템을 클릭하면
- 해당 아이템의 IsSelected가 true로 변경되고 출력됩니다.
✔️ 2. [ObservableProperty] 사용 예제
📁 2.1 프로젝트 구조
WpfMvvmToolkit03_ObservableProperty01/
├── Models/
│ └── ChildViewModel.cs
├── ViewModels/
│ ├── MainViewModel.cs ←
├── Views/
│ ├── MainWindow.xaml ← 메인 뷰
│ ├── MainWindow.xaml.cs
└── App.xaml
└── App.xaml.cs ←
📁 2.2 Model 클래스 만들기

public partial class ChildViewModel : ObservableObject
{
[ObservableProperty]
private string? title;
[ObservableProperty]
private bool isSelected;
}
📁 2.3 ViewModel 클래스 만들기

public partial class MainViewModel : ObservableObject
{
public ObservableCollection<ChildViewModel> Items { get; } = new()
{
new ChildViewModel { Title = "항목 1" },
new ChildViewModel { Title = "항목 2" },
new ChildViewModel { Title = "항목 3" }
};
[ObservableProperty]
private string? name;
partial void OnNameChanging(string? value)
{
Console.WriteLine($"이름이 곧 '{value}'(으)로 변경됩니다.");
}
partial void OnNameChanged(string? value)
{
Console.WriteLine($"이름이 '{value}'(으)로 변경되었습니다.\n");
}
[ObservableProperty]
private ChildViewModel? selectedItem;
partial void OnSelectedItemChanging(ChildViewModel? oldValue, ChildViewModel? newValue)
{
if (oldValue is not null)
oldValue.IsSelected = false;
if (newValue is not null)
newValue.IsSelected = true;
}
}
📁 2.4 XAML 화면 구성

xmlns:vm="clr-namespace:WPF_ToolKit02.ViewModels"
Title="속성 변경 감지 예제" Height="300" Width="400">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<StackPanel Margin="10">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<ListBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Title"/>
<TextBlock Text="선택된 항목 상태:"
Margin="0,10,0,0"/>
<TextBlock Text="{Binding SelectedItem.IsSelected}"/>
</StackPanel>
📁 2.5 콘솔 설정


*WPF 앱에서는 기본적으로 Console.WriteLine이 눈에 보이지 않기 때문에 디버그 출력 창에서 확인하거나, Debug.WriteLine()을 사용하는 것이 좋습니다
📁 2.6 결과
- 텍스트 박스에 입력 → 콘솔에 OnNameChanging과 OnNameChanged의 구현부 로그 출력
실제로 속성 변경 시점에 실행되는지 학생들이 눈으로 확인 - ListBox에 선택된 아이템에 해당하는 문자열이 TextBlock 에 출력
4️⃣ 예제1 - 상세 주석
✔️ Model 클래스 만들기
// ChildViewModel 클래스는 각 항목(Item)에 대한 ViewModel로 사용됩니다.
// MVVM 패턴에서 View와 데이터를 연결하는 역할을 하며, UI 요소와의 바인딩을 위해 속성 변경 통지를 제공합니다.
// CommunityToolkit.Mvvm.ComponentModel.ObservableObject를 상속받아 INotifyPropertyChanged 구현을 자동으로 처리합니다.
using CommunityToolkit.Mvvm.ComponentModel;
public partial class ChildViewModel : ObservableObject
{
// [ObservableProperty] 특성을 사용하면 아래의 private 필드(title)에 대해 자동으로 public 속성 Title이 생성됩니다.
// 속성이 변경될 때 자동으로 PropertyChanged 이벤트가 발생하여 UI에 즉시 반영됩니다.
// string?은 null을 허용하는 문자열입니다.
[ObservableProperty]
private string? title;
// isSelected는 UI에서 특정 항목이 선택되었는지를 나타내는 상태입니다.
// 예: ListBox에서 항목을 선택하면 해당 ViewModel의 IsSelected가 true로 설정될 수 있습니다.
// 이 또한 속성 변경 시 자동으로 UI에 반영되며, 주로 시각적 상태 변경(색상 등)에 활용됩니다.
[ObservableProperty]
private bool isSelected;
}
✔️ ViewModel 클래스 만들기
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
// MainViewModel은 MVVM 패턴에서 View (UI)와 Model 간의 연결을 담당하는 ViewModel입니다.
// ObservableObject를 상속받아 INotifyPropertyChanged를 자동으로 구현합니다.
public partial class MainViewModel : ObservableObject
{
// Items 컬렉션은 UI에 바인딩되어 ListBox 등의 컨트롤에 데이터를 제공합니다.
// ObservableCollection을 사용하면 컬렉션에 변경이 생길 때 UI가 자동으로 반영됩니다.
public ObservableCollection<ChildViewModel> Items { get; } = new()
{
new ChildViewModel { Title = "항목 1" },
new ChildViewModel { Title = "항목 2" },
new ChildViewModel { Title = "항목 3" }
};
// ObservableProperty 특성을 사용하면 name 필드에 대해 자동으로 Name 속성이 생성되고,
// 속성 변경 시 INotifyPropertyChanged 이벤트가 발생하도록 코드가 자동 생성됩니다.
[ObservableProperty]
private string? name;
// name 속성이 변경되기 직전에 실행되는 partial 메서드입니다.
// 이 메서드는 사용자가 직접 정의할 수 있으며, 주로 로그 출력, 유효성 검사 등에 사용됩니다.
partial void OnNameChanging(string? value)
{
Console.WriteLine($"이름이 곧 '{value}'(으)로 변경됩니다.");
}
// name 속성이 변경된 이후에 실행되는 partial 메서드입니다.
// 변경된 값에 대해 후처리 작업을 수행할 수 있습니다.
partial void OnNameChanged(string? value)
{
Console.WriteLine($"이름이 '{value}'(으)로 변경되었습니다.");
}
// ObservableProperty 특성은 selectedItem 필드에 대해서도 동일하게 적용됩니다.
// selectedItem 속성이 UI의 선택 항목과 바인딩되며, 변경 감지를 자동으로 수행합니다.
[ObservableProperty]
private ChildViewModel? selectedItem;
// selectedItem이 변경되기 직전에 실행되는 메서드입니다.
// 이전 항목의 IsSelected 속성을 false로 설정하고,
// 새로 선택된 항목의 IsSelected를 true로 설정하여 UI 상태를 자동으로 관리합니다.
partial void OnSelectedItemChanging(ChildViewModel? oldValue, ChildViewModel? newValue)
{
if (oldValue is not null)
oldValue.IsSelected = false;
if (newValue is not null)
newValue.IsSelected = true;
}
}
✔️ XAML 화면 구성
<!--
이 XAML 코드는 MVVM 패턴을 따르는 WPF 애플리케이션의 MainWindow 정의입니다.
ViewModel을 DataContext로 바인딩하고, 사용자 입력과 UI 요소가 ViewModel 속성과 실시간으로 연결됩니다.
-->
<Window x:Class="MVVMPropertyChangedSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:MVVMPropertyChangedSample.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="속성 변경 데모" Height="300" Width="400">
<!-- MainViewModel을 이 뷰의 데이터 컨텍스트로 설정합니다. -->
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<!-- StackPanel은 UI 요소를 수직으로 배치하는 레이아웃 컨테이너입니다. -->
<StackPanel Margin="10">
<!--
TextBox의 Text 속성을 ViewModel의 Name 속성과 바인딩합니다.
UpdateSourceTrigger=PropertyChanged 설정을 통해 입력할 때마다 ViewModel에 값이 즉시 반영됩니다.
-->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="이름을 입력하세요"/>
<!--
ListBox의 항목 목록을 ViewModel의 Items 속성과 바인딩합니다.
SelectedItem 속성은 사용자가 선택한 항목을 ViewModel의 SelectedItem에 실시간으로 반영합니다.
DisplayMemberPath="Title" 설정은 각 항목의 Title 속성을 UI에 표시합니다.
-->
<ListBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Title"/>
<!-- 선택된 항목의 상태를 확인할 수 있는 텍스트 블록입니다. -->
<TextBlock Text="선택된 항목 상태:"
Margin="0,10,0,0"/>
<!--
현재 선택된 항목의 IsSelected 속성을 바인딩하여 UI에 상태를 표시합니다.
선택되었을 경우 true, 선택되지 않았을 경우 false가 표시됩니다.
-->
<TextBlock Text="{Binding SelectedItem.IsSelected}"/>
</StackPanel>
</Window>
5️⃣ 예제1 - UI에 색상 추가
✔️ 과제 내용
- IsSelected 상태를 UI에 색상으로 표현
✔️ XAML 화면 수정
<ListBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="5" Margin="2">
<TextBlock Text="{Binding Title}" />
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="White"/>
<Style.Triggers>
<!-- IsSelected 속성이 true일 때 배경색 변경 -->
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
✔️ 결과
- 항목 클릭 시 ViewModel.SelectedItem이 변경되며, 해당 항목의 IsSelected가 true가 됩니다.
- 트리거에 의해 선택된 항목만 배경색이 변경됩니다.
✔️ 상세 주석
<!--
이 ListBox는 MVVM 패턴에서 ViewModel의 Items 컬렉션을 항목으로 사용하고,
사용자가 선택한 항목을 SelectedItem 속성에 바인딩합니다.
각 항목은 DataTemplate을 통해 렌더링되며, 선택 상태(IsSelected)에 따라 배경색이 바뀌도록 설정되어 있습니다.
-->
<ListBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<!--
ItemTemplate은 각 항목을 어떻게 시각적으로 표현할지를 정의합니다.
여기서는 Border 안에 TextBlock을 넣어 각 항목의 제목을 보여줍니다.
-->
<ListBox.ItemTemplate>
<DataTemplate>
<!--
Border는 각 항목의 배경 및 마진, 패딩 등 레이아웃 및 시각 효과를 담당합니다.
-->
<Border Padding="5" Margin="2">
<!--
TextBlock은 ChildViewModel의 Title 속성을 출력합니다.
-->
<TextBlock Text="{Binding Title}" />
<!--
Style을 통해 Border의 배경색을 동적으로 제어합니다.
기본 배경은 흰색(White)이며, 특정 조건이 만족될 때만 변경됩니다.
-->
<Border.Style>
<Style TargetType="Border">
<!-- 기본 배경색 설정 -->
<Setter Property="Background" Value="White"/>
<!--
트리거 설정: ViewModel의 IsSelected 속성이 true인 경우
배경색을 연한 하늘색(LightSkyBlue)으로 변경합니다.
-->
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="LightSkyBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
6️⃣ 예제2
✔️ 1. 동작 시나리오

- TextBox에 입력하면 콘솔에 출력되던 로그를, ListBox UI 에 출력되도록 변경합니다.
- ObservableCollection<string> Logs를 만들고,
OnNameChanging과 OnNameChanged에서 로그를 추가하고,
ListBox UI에 바인딩하여 실시간으로 로그가 출력되도록 구현합니다.
✔️ 2. [ObservableProperty] 사용 예제
📁 2.1 MainViewModel.cs 수정

// 로그 출력을 위한 컬렉션 추가
public ObservableCollection<string> Logs { get; } = new();
// 콘솔 출력에, 로그에 저장 기능 추가 구현
partial void OnNameChanging(string? value)
{
Console.WriteLine($"이름이 곧 '{value}'(으)로 변경됩니다.");
// 추가
Logs.Add($"[변경 전] 이름이 '{value}'(으)로 변경될 예정입니다.");
}
partial void OnNameChanged(string? value)
{
Console.WriteLine($"이름이 '{value}'(으)로 변경되었습니다.\n");
// 추가
Logs.Add($"[변경 완료] 이름이 '{value}'(으)로 변경되었습니다.");
}
📁 2.2 XAML 화면 수정

<!-- 로그 출력 영역 추가 -->
<TextBlock Text="로그:" Margin="0,10,0,5" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Logs}" Height="100" />
📁 2.3 결과
- 텍스트 박스에 입력 → 콘솔에 OnNameChanging과 OnNameChanged의 구현부 로그 출력
실제로 속성 변경 시점에 실행되는지 학생들이 눈으로 확인 - 기존 콘솔에 출력되던 로그 외, UI ListBox 에 로그가 출력됩니다.
7️⃣ 예제3
✔️ 1. 동작 시나리오

- TextBox에 입력하면
- 리스트 박스와, 콘솔에 변경 로그가 찍힙니다.
- 로그 필터링(키워드) 기능과
- 로그 초기화 기능을 추가합니다.
✔️ 2. [ObservableProperty] 사용 예제
📁 2.1 MainViewModel.cs 수정

// 로그 출력을 위한 컬렉션 추가
public ObservableCollection<string> Logs { get; } = new();
// 필터링된 로그 보기
public ICollectionView FilteredLogs { get; }
public MainViewModel()
{
// 필터링 뷰 구성
FilteredLogs = CollectionViewSource.GetDefaultView(Logs);
FilteredLogs.Filter = FilterLogs;
}
// 필터 문자열
[ObservableProperty]
private string? logFilter;
partial void OnLogFilterChanged(string? value)
{
FilteredLogs.Refresh();
}
private bool FilterLogs(object item)
{
if (string.IsNullOrWhiteSpace(LogFilter))
return true;
return item is string log && log.Contains(LogFilter, StringComparison.OrdinalIgnoreCase);
}
// 로그 초기화 명령
[RelayCommand]
private void ClearLogs()
{
Logs.Clear();
}
📁 2.2 XAML 화면 수정

<!-- 로그 사용 부분 수정 -->
<TextBlock Text="로그:" Margin="0" FontWeight="Bold"/>
<!-- 로그 필터 입력 -->
<TextBox Text="{Binding LogFilter, UpdateSourceTrigger=PropertyChanged}"
Margin="0" />
<!-- 로그 출력 영역 -->
<ListBox ItemsSource="{Binding Logs}" Height="50" />
<!-- 로그 초기화 버튼 -->
<Button Content="로그 초기화" Command="{Binding ClearLogsCommand}"
Margin="0,5,0,0"/>
📁 2.3 결과
- 텍스트 박스에 입력 → 콘솔에 OnNameChanging과 OnNameChanged의 구현부 로그 출력
실제로 속성 변경 시점에 실행되는지 학생들이 눈으로 확인 - 기존 콘솔에 출력되던 로그 외, UI에 로그가 출력됩니다.
- 로그 초기화 버튼 동작
- 로그가 키워드를 기반으로 필터링