1. 참고자료

더보기

✔️ Microsoft Learn / Learn / .NET / MVVM

ObservableObject는 MVVM Toolkit에서 제공하는 MVVM의 핵심 클래스로
INotifyPropertyChanged 인터페이스를 구현하고, 속성 변경을 View에 알리는 기능을 자동화해주는 베이스 클래스입니다.

즉, ViewModel이 반드시 상속해야 하는 기본 클래스입니다.

 

CommunityToolkit.Mvvm의 ObservableObject 기능은

Observable하지 않은(Model이 INotifyPropertyChanged를 구현하지 않은) 기존 모델을 MVVM에 통합하는 핵심 기술입니다.

 

 


✔️
소스 코드

 

WPF_ToolKit06.zip
0.14MB

 

 

 

 

 

2. ObservableObject 개요 

더보기

✔️ MVVM 구현 방식 비교

 

구분 INotifyPropertyChanged
상속 구현 (기존 방식)
[ObservableProperty]
어트리뷰트 적용
ObservableObject
수동
구현
구현 방식 OnPropertyChanged( ) 구현  [ObservableProperty] 어트리뷰트 
private 필드에 적용
ObservableObject 상속 구현
SetProperty( ) 수정
자동화
수준
없음 (모든 코드 직접 작성) 매우 높음 (속성 자동 생성) 중간 (알림 로직만 자동화)
커스터마이징 자유도 매우 높음 제한적
(OnChanged 메서드만 가능)
높음 (SetProperty() 활용 가능)
추전 방식   ViewModel에서
간단한 속성만 빠르게 구성
ViewModel에서
속성 변경 시 조건, 로직 추가등
커스텀 로직이 필요한 경우

 

 

 

 

✔️ 기존 방식

// 전통 방식 (비효율)
public class WeatherVM : INotifyPropertyChanged
{
//[1] 시나리오: 도시 검색을 요청(query)한다.
private string query;
public string Query
{
get { return query; }
// [2] Property가 변경되면,
set
{
query = value;
// OnPropertyChanged 함수에서
OnPropertyChanged(nameof(Query));
}
}
// [4] 이벤트 핸들러가 동작하면,
// XAML에 바인딩 되어있는 모든 UI컨트롤에 해당 query 값이 업데이트 된다.
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
// [3] PropertyChanged 이벤트를 실행한다.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
  • WPF에서는 속성이 바뀌어도 자동으로 UI가 갱신되지 않습니다.
  • INotifyPropertyChanged를 구현해 직접 알림을 보내야 하는데, 이 코드는 반복적이고 실수 유발 가능성이 큽니다.
    >> ObservableObject는 이 로직을 SetProperty 한 줄로 대체할 수 있습니다.

 

 

 

 

✔️  [ObservableProperty] 

public partial class MainViewModel : ObservableObject
{
// 이 필드에 대해 자동으로 Name 속성이 생성됨, PropertyChanged 이벤트도 자동 구현됨
[ObservableProperty]
private string? userName;
}
// [ObservableProperty] 어트리뷰트 사용은
// 결과적으로 다음과 같은 속성이 자동 생성됩니다
public string Name
{
get => name;
set
{
if (SetProperty(ref name, value))
{
OnPropertyChanged(nameof(Name));
}
}
}

 

 

 

 

✔️ ObservableObject 사용 방식

using CommunityToolkit.Mvvm.ComponentModel;
public class PersonViewModel : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value); // 변경 시 자동으로 PropertyChanged 이벤트 발생
}
}
SetProperty 매개변수 설명
ref name 실제 저장되는 필드
value 새로 설정할 값
내부 처리 값이 다를 경우 필드를 변경하고 PropertyChanged 이벤트 발생

 

 

 

 


3. 예제1

더보기

✔️ 구현 목표

 

  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

더보기

✔️ 구현 목표

 

  1. 탭 전환 비교
    UI ObservableObject + SetProperty 방식 VS [ObservableProperty] 방식 비교
  2. 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>