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에서 바인딩 가능한 프로퍼티 및 기능이 생성됩니다.

 

 

예제 소스코드

WpfMvvmToolkit03_ObservableProperty.zip
0.20MB

 


 

2️⃣ 비교( 순수C#MVVM vs CommunityToolkit )

더보기

*예제 파일의 프로젝트명: WpfMvvmToolkit03_INotifyPropertyChanged

 


✔️ 1. 동작 시나리오

  1. TextBox에 문자열을 입력하면
  2. 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. 동작 시나리오

  1. TextBox에 입력하면
  2. 콘솔에 변경 로그가 찍힙니다.
  3. ListBox에서 아이템을 클릭하면
  4. 해당 아이템의 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에 색상 추가

더보기

✔️ 과제 내용

  1. 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. 동작 시나리오

  1. TextBox에 입력하면 콘솔에 출력되던 로그를, ListBox UI 에 출력되도록 변경합니다.
  2. 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. 동작 시나리오

  1. TextBox에 입력하면
  2. 리스트 박스와, 콘솔에 변경 로그가 찍힙니다.
  3. 로그 필터링(키워드) 기능과
  4. 로그 초기화 기능을 추가합니다.

 


✔️ 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에 로그가 출력됩니다.
  • 로그 초기화 버튼 동작
  • 로그가 키워드를 기반으로 필터링