03.1 ObservableObject
1️⃣ ObservableProperty 개요
📚 Microsoft Docs: Microsoft CommunityToolkit MVVM - ObservableObject

✅ ObservableProperty 개요
- ObservableObject는 MVVM Toolkit에서 제공하는 MVVM의 핵심 클래스입니다.
- ObservableObject는 INotifyPropertyChanged, INotifyPropertyChanging 인터페이스가 구현되어 있습니다.
- 즉, MVVM 구조에서 Observable하지 않은 기존 객체를 MVVM에 통합하는 핵심 기술입니다.
✅ ObservableProperty 사용법
ㅇ
✅ 예제 소스 코드
2️⃣ 비교 ( 전통적 구현 vs ObservableObject vs [ObservableProperty] )
✔️ 1. 동작 시나리오

- 공통 시나리오: 이름(Name)을 입력하고
- 인사 메시지(Greeting)를 출력
✔️ 2. 구현
2.1 (공통) XAML 구현
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
Title="INotifyPropertyChanged 비교" Height="200" Width="300">
<Window.DataContext>
<vm:ManualViewModel />
<!-- 변경 가능: ManualViewModel, ObjectViewModel -->
</Window.DataContext>
<StackPanel Margin="20">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 10" />
<TextBlock Text="{Binding Greeting}" FontSize="16" />
</StackPanel>
</Window>
2.2 INotifyPropertyChanged
class ManualViewModel : INotifyPropertyChanged
{
private string? name;
public string Name
{
get => name ?? string.Empty;
set
{
if (name != value)
{
name = value;
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Greeting));
}
}
}
public string Greeting => $"안녕하세요, {Name}님!";
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
🟢 장점
- Source Generator 없이 작동
- 완전한 제어 가능
🔴 단점
- 매 속성마다 반복되는 boilerplate 코드
2.2 [ObservableProperty]
public partial class AttributeViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Greeting))]
private string? name;
public string Greeting => $"안녕하세요, {Name}님!";
}
🟢 장점
- 가장 간결함 (속성 한 줄이면 끝)
- OnNameChanged 같은 partial method 지원
- 관련된 속성 알림도 연동 가능 (NotifyPropertyChangedFor)
- 커맨드 상태 자동 갱신도 가능 (NotifyCanExecuteChangedFor)
🔴 단점
- C# Source Generator 기능이 필요한 환경에서만 작동 (Visual Studio 2019+ / .NET 5+)
2.3 ObservableObject
public class ObjectViewModel : ObservableObject
{
private string? name;
public string Name
{
get => name ?? string.Empty;
set
{
if (SetProperty(ref name, value))
{
OnPropertyChanged(nameof(Greeting));
}
}
}
public string Greeting => $"안녕하세요, {Name}님!";
}
SetProperty 매개변수 | 설명 |
ref name | 실제 저장되는 필드 |
value | 새로 설정할 값 |
내부 처리 | 값이 다를 경우 필드를 변경하고 PropertyChanged 이벤트 발생 |
🟢 장점
- SetProperty를 통해 OnPropertyChanged를 깔끔하게 처리
- Source Generator 없이도 깔끔한 구조
- 커스텀 로직을 섞기 용이함
🔴 단점
- 여전히 property 선언을 직접 작성해야 함
- [ObservableProperty]보다 코드량 많음
✔️ 3. MVVM 구현 방식 비교
방식 | 핵심 키워드 | 코드 작성량 | 유지보수성 | 가독성 | Source Generator |
전통적 방식 | INotifyPropertyChanged 수동 구현 | ❌ 많음 | ❌ 낮음 | ❌ 불편 | ❌ 필요 없음 |
Toolkit 자동 | [ObservableProperty] + [INotifyPropertyChanged] |
✅ 매우 적음 | ✅ 높음 | ✅ 매우 좋음 | ✅ 필요 |
Toolkit 수동 | ObservableObject 상속 SetProperty 수동 작성 |
🔸 보통 | 🔸 보통 | 🔸 명시적 | ✅ 필요 |
3️⃣ 예제1
✔️ 구현 목표
- ObservableObject + SetProperty 방식과
[ObservableProperty] 방식의 차이점을 직접 구현하여 비교할 수 있도록 구현
📁 프로젝트 구조
ObservableComparisonDemo/
├── ViewModel/
│ ├── SetPropertyViewModel.cs
│ └── ObservablePropertyViewModel.cs
├── MainWindow.xaml
└── MainWindow.xaml.cs
✔️ ViewModel 클래스 만들기 (ObservablePropertyViewModel.cs)

public partial class ObservablePropertyViewModel : ObservableObject
{
// 자동으로 public Name 속성 생성 + PropertyChanged 알림
[ObservableProperty]
private string? name;
}
✔️ ViewModel 클래스 만들기 (SetPropertyViewModel.cs)

public class SetPropertyViewModel : ObservableObject
{
private string name = string.Empty;
public string Name
{
get => name;
set => SetProperty(ref name, value); // 수동 구현
}
}
✔️ XAML 화면 구성

Title="ObservableProperty 비교 실습" Height="250" Width="400">
<StackPanel Margin="20">
<TextBlock Text="① SetProperty 방식 (수동)" FontWeight="Bold"/>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,5"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
<Separator Margin="10"/>
<TextBlock Text="② ObservableProperty 방식 (자동)" FontWeight="Bold"/>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,5"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</StackPanel>
✔️ XAML 코드 비하인드 구성

// 아래 둘 중 하나를 주석 해제하여 실습
// DataContext = new SetPropertyViewModel();
DataContext = new ObservablePropertyViewModel();
4️⃣ 예제2
✔️ 구현 목표
- 탭 전환 비교
UI ObservableObject + SetProperty 방식 VS [ObservableProperty] 방식 비교 - OnChanged 후처리 비교
SetProperty 방식의 조건부 후처리 vs [ObservableProperty]의 partial void On___Changed() 활용
📁 프로젝트 구조
WPF_ToolKit06_Ex01/
├── ViewModel/
│ ├── SetPropertyViewModel.cs
│ └── ObservablePropertyViewModel.cs
├── MainWindow.xaml
└── MainWindow.xaml.cs
✔️ ViewModel 클래스 만들기 (ObservablePropertyViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPF_ToolKit06_Ex01.ViewModels
{
// SetPropertyViewModel은 MVVM Toolkit의 ObservableObject를 상속받아
// INotifyPropertyChanged를 간편하게 구현하는 ViewModel입니다.
// 이 클래스에서는 수동으로 속성을 정의하고, SetProperty() 메서드를 통해 속성 변경 시 알림을 발생시킵니다.
public class SetPropertyViewModel : ObservableObject
{
// 이름을 저장하는 private 필드
private string name = string.Empty;
// 사용자에게 보여줄 메시지를 저장하는 private 필드
private string message = string.Empty;
// Name 속성은 사용자의 입력값을 바인딩합니다.
// set 접근자 내부에서 SetProperty()를 사용하면 값이 변경될 때 자동으로 UI에 알림이 전파됩니다.
// 또한 값이 바뀐 이후에 메시지를 업데이트하는 후처리 로직을 직접 구현할 수 있습니다.
public string Name
{
get => name;
set
{
// 값이 실제로 바뀌었을 때만 PropertyChanged 이벤트 발생
if (SetProperty(ref name, value))
{
// 후처리: 메시지 속성에 변경 내용을 저장
Message = $"[SetProperty] 이름이 '{name}'으로 변경되었습니다.";
}
}
}
// Message 속성은 후처리 결과를 화면에 출력하기 위해 바인딩됩니다.
// SetProperty를 통해 변경되었음을 UI에 알립니다.
public string Message
{
get => message;
set => SetProperty(ref message, value);
}
}
}
✔️ ViewModel 클래스 만들기 (SetPropertyViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPF_ToolKit06_Ex01.ViewModels
{
// ObservablePropertyViewModel 클래스는 MVVM Toolkit의 기능을 활용하여
// 속성을 간결하게 정의하고, 변경 알림 및 후처리까지 자동화하는 예제입니다.
// ObservableObject를 상속하여 INotifyPropertyChanged를 내부적으로 처리하며,
// [ObservableProperty] 특성을 사용해 필드 기반으로 속성을 자동 생성합니다.
public partial class ObservablePropertyViewModel : ObservableObject
{
// [ObservableProperty]를 선언하면 다음과 같은 기능이 자동 생성됩니다:
// - public string Name { get; set; } 속성
// - 값이 변경되면 PropertyChanged 이벤트 자동 발생
// - partial void OnNameChanged(string value) 메서드 호출
[ObservableProperty]
private string name = string.Empty;
[ObservableProperty]
private string message = string.Empty;
// OnNameChanged 메서드는 Name 속성의 값이 변경되었을 때 자동 호출됩니다.
// 여기서는 후처리로 Message 속성을 업데이트하여 UI에 알림 메시지를 전달합니다.
// MVVM Toolkit은 이러한 후처리 메서드를 partial 메서드로 지원합니다.
partial void OnNameChanged(string value)
{
Message = $"[ObservableProperty] 이름이 '{value}'으로 변경되었습니다.";
}
}
}
✔️ XAML 화면 구성
xmlns:vm="clr-namespace:WPF_ToolKit06_Ex01.ViewModels"
Title="비교 실습" Height="300" Width="400">
<!-- ViewModel 인스턴스를 정적으로 생성하여 리소스로 등록 -->
<Window.Resources>
<!-- SetProperty 방식 ViewModel 등록 -->
<vm:SetPropertyViewModel x:Key="SetVM"/>
<!-- ObservableProperty 방식 ViewModel 등록 -->
<vm:ObservablePropertyViewModel x:Key="ObsVM"/>
</Window.Resources>
<Grid>
<!-- TabControl을 사용하여 두 가지 ViewModel의 차이를 비교 실습할 수 있도록 구성 -->
<TabControl Margin="10">
<!-- 탭 1: 수동 방식 (SetProperty) -->
<TabItem Header="SetProperty 방식">
<!-- 해당 탭의 DataContext는 SetVM 리소스(ViewModel) -->
<StackPanel DataContext="{StaticResource SetVM}" Margin="10">
<TextBlock Text="이름 입력:"/>
<!-- TextBox: Name 속성과 양방향 바인딩. 텍스트가 변경될 때마다 ViewModel에 전달됨 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="메시지:" Margin="0,10,0,0"/>
<!-- 메시지 출력: ViewModel에서 후처리로 생성된 문자열 출력 -->
<TextBlock Text="{Binding Message}" FontWeight="Bold"/>
</StackPanel>
</TabItem>
<!-- 탭 2: 자동 방식 (ObservableProperty) -->
<TabItem Header="ObservableProperty 방식">
<!-- 해당 탭의 DataContext는 ObsVM 리소스(ViewModel) -->
<StackPanel DataContext="{StaticResource ObsVM}" Margin="10">
<TextBlock Text="이름 입력:"/>
<!-- ObservableProperty에 의해 자동 생성된 Name 속성과 바인딩 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="메시지:" Margin="0,10,0,0"/>
<!-- OnNameChanged()를 통해 자동 생성된 메시지 출력 -->
<TextBlock Text="{Binding Message}" FontWeight="Bold"/>
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>
5️⃣ 예제3 - Wrapping a Non-Observable Model
✔️ 구현 목표
항목 | 설명 |
문제 상황 | Model은 DB 엔터티나 외부 API 결과 등 외부 구조체인 경우 많음 |
제약 | 대부분 INotifyPropertyChanged를 구현하지 않음 (바인딩 불가) |
해결 방법 | ViewModel이 Model을 "감싸고(wrap)" 속성을 노출하며 변경 감지를 구현 |
핵심 도구 | ObservableObject + ViewModel 속성에서 SetProperty() 사용 |
📁 프로젝트 구조
WPF_ToolKit06_Ex01/
├── ViewModel/
│ ├── SetPropertyViewModel.cs
│ └── ObservablePropertyViewModel.cs
├── MainWindow.xaml
└── MainWindow.xaml.cs
✔️ ViewModel 클래스 만들기 (ObservablePropertyViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPF_ToolKit06_Ex01.ViewModels
{
// SetPropertyViewModel은 MVVM Toolkit의 ObservableObject를 상속받아
// INotifyPropertyChanged를 간편하게 구현하는 ViewModel입니다.
// 이 클래스에서는 수동으로 속성을 정의하고, SetProperty() 메서드를 통해 속성 변경 시 알림을 발생시킵니다.
public class SetPropertyViewModel : ObservableObject
{
// 이름을 저장하는 private 필드
private string name = string.Empty;
// 사용자에게 보여줄 메시지를 저장하는 private 필드
private string message = string.Empty;
// Name 속성은 사용자의 입력값을 바인딩합니다.
// set 접근자 내부에서 SetProperty()를 사용하면 값이 변경될 때 자동으로 UI에 알림이 전파됩니다.
// 또한 값이 바뀐 이후에 메시지를 업데이트하는 후처리 로직을 직접 구현할 수 있습니다.
public string Name
{
get => name;
set
{
// 값이 실제로 바뀌었을 때만 PropertyChanged 이벤트 발생
if (SetProperty(ref name, value))
{
// 후처리: 메시지 속성에 변경 내용을 저장
Message = $"[SetProperty] 이름이 '{name}'으로 변경되었습니다.";
}
}
}
// Message 속성은 후처리 결과를 화면에 출력하기 위해 바인딩됩니다.
// SetProperty를 통해 변경되었음을 UI에 알립니다.
public string Message
{
get => message;
set => SetProperty(ref message, value);
}
}
}
✔️ ViewModel 클래스 만들기 (SetPropertyViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel;
namespace WPF_ToolKit06_Ex01.ViewModels
{
// ObservablePropertyViewModel 클래스는 MVVM Toolkit의 기능을 활용하여
// 속성을 간결하게 정의하고, 변경 알림 및 후처리까지 자동화하는 예제입니다.
// ObservableObject를 상속하여 INotifyPropertyChanged를 내부적으로 처리하며,
// [ObservableProperty] 특성을 사용해 필드 기반으로 속성을 자동 생성합니다.
public partial class ObservablePropertyViewModel : ObservableObject
{
// [ObservableProperty]를 선언하면 다음과 같은 기능이 자동 생성됩니다:
// - public string Name { get; set; } 속성
// - 값이 변경되면 PropertyChanged 이벤트 자동 발생
// - partial void OnNameChanged(string value) 메서드 호출
[ObservableProperty]
private string name = string.Empty;
[ObservableProperty]
private string message = string.Empty;
// OnNameChanged 메서드는 Name 속성의 값이 변경되었을 때 자동 호출됩니다.
// 여기서는 후처리로 Message 속성을 업데이트하여 UI에 알림 메시지를 전달합니다.
// MVVM Toolkit은 이러한 후처리 메서드를 partial 메서드로 지원합니다.
partial void OnNameChanged(string value)
{
Message = $"[ObservableProperty] 이름이 '{value}'으로 변경되었습니다.";
}
}
}
✔️ XAML 화면 구성
xmlns:vm="clr-namespace:WPF_ToolKit06_Ex01.ViewModels"
Title="비교 실습" Height="300" Width="400">
<!-- ViewModel 인스턴스를 정적으로 생성하여 리소스로 등록 -->
<Window.Resources>
<!-- SetProperty 방식 ViewModel 등록 -->
<vm:SetPropertyViewModel x:Key="SetVM"/>
<!-- ObservableProperty 방식 ViewModel 등록 -->
<vm:ObservablePropertyViewModel x:Key="ObsVM"/>
</Window.Resources>
<Grid>
<!-- TabControl을 사용하여 두 가지 ViewModel의 차이를 비교 실습할 수 있도록 구성 -->
<TabControl Margin="10">
<!-- 탭 1: 수동 방식 (SetProperty) -->
<TabItem Header="SetProperty 방식">
<!-- 해당 탭의 DataContext는 SetVM 리소스(ViewModel) -->
<StackPanel DataContext="{StaticResource SetVM}" Margin="10">
<TextBlock Text="이름 입력:"/>
<!-- TextBox: Name 속성과 양방향 바인딩. 텍스트가 변경될 때마다 ViewModel에 전달됨 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="메시지:" Margin="0,10,0,0"/>
<!-- 메시지 출력: ViewModel에서 후처리로 생성된 문자열 출력 -->
<TextBlock Text="{Binding Message}" FontWeight="Bold"/>
</StackPanel>
</TabItem>
<!-- 탭 2: 자동 방식 (ObservableProperty) -->
<TabItem Header="ObservableProperty 방식">
<!-- 해당 탭의 DataContext는 ObsVM 리소스(ViewModel) -->
<StackPanel DataContext="{StaticResource ObsVM}" Margin="10">
<TextBlock Text="이름 입력:"/>
<!-- ObservableProperty에 의해 자동 생성된 Name 속성과 바인딩 -->
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="메시지:" Margin="0,10,0,0"/>
<!-- OnNameChanged()를 통해 자동 생성된 메시지 출력 -->
<TextBlock Text="{Binding Message}" FontWeight="Bold"/>
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>