1️⃣ [ObservableProperty] 사용 예제 - 1단계

더보기

✔️ 1. 학습 목표

  • CommunityToolkit.Mvvm 패키지를 사용해 프로젝트를 구현하는 방법을 학습합니다.

 

✔️ 2. 동작 시나리오

  1. TextBox에 입력하면
  2. 콘솔에 변경 로그가 찍힙니다.
  3. ListBox에서 아이템을 클릭하면
  4. 해당 아이템의 IsSelected가 true로 변경되고 출력됩니다. 

 

2️⃣ Ex03_Step01

: CommunityToolkit.Mvvm 패키지를 활용한 프로젝트를 단계별로 따라하며 구현해봅니다.

더보기

📁 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. 프로젝트 구조

Ch02_Sec01_Ex03_Step01/
├── Models/
│   └── ChildViewModel.cs
├── ViewModels/
│   ├── MainViewModel.cs         ← 메인 뷰모델     
├── Views/
│   ├── MainWindow.xaml          ← 메인 뷰
│   └── MainWindow.xaml.cs
├── App.xaml
└── App.xaml.cs

 

 

📁 4. Model 클래스 만들기

public partial class ChildViewModel : ObservableObject
{
    [ObservableProperty]
    private string? title;

    [ObservableProperty]
    private bool isSelected;
}

 

 

📁 5. 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;
    }
}

 

 

📁 6. 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>

 


📁7. App 수정

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

 

 

📁 8. 콘솔 실행 설정

*WPF 앱에서는 기본적으로 Console.WriteLine이 눈에 보이지 않기 때문에 디버그 출력 창에서 확인하거나, Debug.WriteLine()을 사용하는 것이 좋습니다

 

 

📁 9. 결과

  • 텍스트 박스에 입력 →  콘솔에 OnNameChanging과 OnNameChanged의 구현부 로그 출력
    실제로 속성 변경 시점에 실행되는지 학생들이 눈으로 확인
  • ListBox에 선택된 아이템에 해당하는 문자열이 TextBlock 에 출력

 

3️⃣ 예제 자세히 살펴보기

: 프로젝트 소스코드의 기능을 주석을 통해 파악하고, 동작 구조를 자세히 이해합니다.

더보기

✔️  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>


4️⃣ UI 색상 추가하기

: 기존 예제에게 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>