WPF 개발, INotifyCollectionChanged

WPF (Windows Presentation Foundation)는 Microsoft에서 제공하는 GUI 프레임워크로, 강력한 데이터 바인딩과 유연한 UI 설계를 지원합니다. WPF에서 컬렉션과 데이터를 다루는 것은 매우 중요하며, 이 과정에서 INotifyCollectionChanged 인터페이스가 핵심적인 역할을 합니다. 이 글에서는 INotifyCollectionChanged 인터페이스의 개념, 사용 방법, 그리고 실제 예제를 통해 이를 어떻게 활용할 수 있는지를 깊이 있게 설명하겠습니다.

INotifyCollectionChanged란?

INotifyCollectionChanged는 컬렉션에서 항목이 추가, 제거 또는 변경될 때 발생하는 변경 사항을 알리기 위한 이벤트를 제공하는 인터페이스입니다. 이 인터페이스는 주로 데이터 바인딩이 이루어지는 WPF와 같은 MVVM (Model-View-ViewModel) 아키텍처에서 사용됩니다.

뷰(View)는 모델(Model)로부터 데이터를 바인딩 받고, 모델의 변경 사항을 반영하기 위해서 INotifyCollectionChanged를 통해 이벤트를 수신합니다. 이 컬렉션의 변화가 발생하면 CollectionChanged 이벤트가 발생하여 UI가 자동으로 갱신됩니다.

INotifyCollectionChanged 인터페이스의 메서드

이 인터페이스는 다음과 같은 이벤트를 정의합니다.

  • CollectionChanged: 컬렉션의 변경 사항을 알리는 이벤트입니다. 이 이벤트는 다음과 같은 매개변수를 가집니다:
    • sender: 이벤트를 발생시킨 객체입니다.
    • args: NotifyCollectionChangedEventArgs 형식의 객체로, 변경된 내용에 대한 정보를 담고 있습니다.

또한, NotifyCollectionChangedEventArgs는 변화의 유형을 알려주는 NotifyCollectionChangedAction 열거형을 사용할 수 있습니다. 변화 유형에는 다음과 같은 것들이 있습니다:

  • Add: 항목이 추가되었습니다.
  • Remove: 항목이 제거되었습니다.
  • Replace: 항목이 대체되었습니다.
  • Move: 항목의 위치가 변경되었습니다.
  • Reset: 컬렉션이 초기화되었습니다.

예제: INotifyCollectionChanged의 사용

이제, INotifyCollectionChanged 인터페이스를 사용하는 간단한 예제를 살펴보겠습니다. 이 예제에서는 사용자 정의 컬렉션 클래스를 작성하고, 컬렉션의 변화에 따라 UI가 자동으로 업데이트되는 모습을 보여주겠습니다.

1단계: 사용자 정의 컬렉션 클래스 작성

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

public class ObservableCollectionEx<T> : ICollection<T>, INotifyCollectionChanged 
{
    private readonly List<T> _items;

    public ObservableCollectionEx() 
    {
        _items = new List<T>();
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public void Add(T item) 
    {
        _items.Add(item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }

    public void Remove(T item) 
    {
        if (_items.Remove(item)) 
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
        }
    }

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    {
        CollectionChanged?.Invoke(this, e);
    }

    public int Count => _items.Count;


    public bool IsReadOnly => false;

    public void Clear() 
    {
        _items.Clear();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    public bool Contains(T item) 
    {
        return _items.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex) 
    {
        _items.CopyTo(array, arrayIndex);
    }

    public IEnumerator<T> GetEnumerator() 
    {
        return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() 
    {
        return GetEnumerator();
    }

    public bool Remove(T item) 
    {
        return _items.Remove(item);
    }
}

2단계: WPF 뷰(View) 만들기

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox Name="ItemsListBox" />
        <Button Content="Add Item" Width="100" Height="30" Click="AddItem_Click" />
    </Grid>
</Window>

3단계: 코드 비하인드 작성

using System.Windows;

public partial class MainWindow : Window 
{
    private ObservableCollectionEx<string> _items;

    public MainWindow() 
    {
        InitializeComponent();
        _items = new ObservableCollectionEx<string>();
        _items.CollectionChanged += Items_CollectionChanged;
        ItemsListBox.ItemsSource = _items;
    }

    private void AddItem_Click(object sender, RoutedEventArgs e) 
    {
        _items.Add("New Item " + (_items.Count + 1));
    }

    private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    {
        // 컬렉션 변경 시 추가적인 처리를 이곳에 작성할 수 있습니다.
    }
}

최종 결과 확인

이제 프로그램을 실행하면 “Add Item” 버튼을 클릭할 때마다 새로운 항목이 ListBox에 추가됩니다. ListBox는 INotifyCollectionChanged 덕분에 컬렉션의 변화에 자동으로 반응하여 UI를 업데이트합니다.

결론

INotifyCollectionChanged 인터페이스는 WPF에서 데이터 바인딩을 통한 UI 업데이트를 매우 간편하게 만들어 줍니다. 이 인터페이스를 잘 활용하면 MVVM 아키텍처를 사용하는 WPF 애플리케이션을 보다 효율적으로 구성할 수 있습니다. 사용자 정의 컬렉션을 쉽게 만들고, UI와의 데이터 동기화를 간단히 처리할 수 있다는 점에서 매우 유용합니다.

이 글을 통해 INotifyCollectionChanged에 대한 이해와 사용 방법에 대해 충분히 알았기를 바랍니다. 더 복잡한 시나리오에서도 이 개념을 적용하여 WPF 애플리케이션에서 강력한 데이터 관리를 구현해 보세요.

WPF 개발, DataContext

WPF(Windows Presentation Foundation)는 .NET Framework에서 제공하는 강력한 사용자 인터페이스(UI) 프레임워크로, 다양한 비즈니스 애플리케이션을 쉽고 유연하게 제작할 수 있도록 돕습니다. WPF의 데이터 바인딩(Data Binding) 기능은 UI와 데이터 소스 간의 연결을 쉽게 해주어 MVVM(Model-View-ViewModel) 아키텍처를 구현할 때 매우 중요합니다. 이 강좌에서는 WPF에서의 DataContext의 개념 및 활용 방법에 대해 자세히 알아보겠습니다.

DataContext란?

WPF에서 DataContext는 데이터 바인딩을 수행하는 데이터 소스를 지정하는 속성입니다. 각 UI 요소는 이 DataContext를 갖고 있으며, 해당 UI 요소에 바인딩된 데이터 소스는 이 속성을 통해 접근됩니다. 기본적으로 DataContext는이러한 데이터 바인딩이 작동하는 기반을 제공합니다.

DataContext의 역할

  • 데이터 소스 지정: UI 요소에 데이터 소스를 지정하여 UI와 데이터를 연결합니다.
  • Hierarchy: 상위 요소에 설정된 DataContext는 하위 요소에도 자동으로 상속됩니다. 이를 통해 중복 설정을 피할 수 있습니다.
  • MVVM 패턴 활용: MVVM 설계 패턴에서 ViewModel을 설정하여 UI와 논리적 데이터 간의 분리를 이루어냅니다.

DataContext 설정 방법

DataContext는 XAML 및 코드 비하인드에서 모두 설정할 수 있습니다. 다음 예제를 통해 각각의 방법을 살펴보겠습니다.

XAML에서 DataContext 설정

XAML에서 DataContext를 설정할 때는 주로 Window 또는 UserControl 요소에 설정합니다. 다음 예제는 Person 클래스를 데이터 모델로 사용하는 WPF 애플리케이션에서 DataContext를 설정하는 방법을 보여줍니다.


<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DataContext Example" Height="200" Width="300">
    <Window.DataContext>
        <local:Person Name="John Doe" Age="30" />
    </Window.DataContext>

    <StackPanel>
        <TextBlock Text="{Binding Name}" FontSize="20"/>
        <TextBlock Text="{Binding Age}" FontSize="20"/>
    </StackPanel>
</Window>

코드 비하인드에서 DataContext 설정

코드 비하인드 파일(MainWindow.xaml.cs)에서는 생성자에서 DataContext를 설정할 수 있습니다. 다음 코드는 코드 비하인드에서 DataContext를 설정하는 예제입니다.


// MainWindow.xaml.cs
using System.Windows;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new Person { Name = "Jane Doe", Age = 28 };
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

Binding 경로와 DataContext의 관계

DataContext가 설정된 후, UI 요소에서는 Binding을 통해 객체의 속성에 접근할 수 있습니다. Binding 구문에서 경로(Path)를 수정해 데이터의 깊은 계층 구조에 접근할 수 있습니다.

중첩 객체 및 Binding

예를 들어, 아래와 같이 Person 클래스에 Address 속성이 있는 경우를 살펴보겠습니다.


public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string Country { get; set; }
}

이 경우, Address 객체를 DataContext에 설정한 후, 해당 속성에 접근하기 위해서는 다음과 같이 경로를 지정할 수 있습니다.


<TextBlock Text="{Binding Address.City}" FontSize="20"/>
<TextBlock Text="{Binding Address.Country}" FontSize="20"/>

Commands와 DataContext

MVVM 패턴을 활용하여 커맨드를 사용하는 경우에도 DataContext의 개념이 중요한 역할을 합니다. 각 ViewModel에 커맨드를 설정하고 View에서 이 커맨드를 호출할 수 있도록 바인딩할 수 있습니다.

ViewModel 생성 및 Command 구현


using System.Windows.Input;

public class PersonViewModel
{
    public Person Person { get; set; }

    public ICommand UpdateNameCommand { get; set; }

    public PersonViewModel()
    {
        Person = new Person { Name = "Initial Name", Age = 20 };
        
        UpdateNameCommand = new RelayCommand(UpdateName);
    }

    private void UpdateName(object parameter)
    {
        Person.Name = parameter.ToString();
    }
}

public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Predicate _canExecute;

    public RelayCommand(Action execute, Predicate canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;
    
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }
}

RelayCommand를 사용하면 사용자가 버튼 클릭 시 UpdateName 메서드를 호출하도록 설정할 수 있습니다.


<Button Command="{Binding UpdateNameCommand}" CommandParameter="New Name" Content="Update Name"/>

DataContext 변경

DataContext는 애플리케이션의 실행 중 언제든지 변경할 수 있습니다. 이는 동적 데이터 변경에 유용합니다. 다음은 DataContext를 업데이트하는 예시입니다.


private void ChangeDataContext()
{
    this.DataContext = new Person { Name = "New Name", Age = 35 };
}

최적의 DataContext 사용법

  • 명확한 설정: 각 UI 요소에 명확하게 DataContext를 설정하여 데이터 바인딩 충돌을 방지합니다.
  • ViewModel 분리: 데이터와 UI 로직을 분리하여 유지보수성을 높입니다.
  • 경로 정규화: Binding 경로를 간결하게 유지하여 가독성을 개선합니다.

결론

DataContext는 WPF에서 데이터 바인딩의 중심으로 작용하며 MVVM 아키텍처의 필수 요소입니다. 이 강좌에서는 DataContext의 기본 개념에서부터, 데이터 모델과의 연결, 커맨드 사용, 그리고 동적 데이터 변경에 이르기까지 다양한 측면을 다루었습니다. 이러한 이해를 바탕으로 더욱 다양한 WPF 애플리케이션을 개발해 나갈 수 있을 것입니다.

추가적으로 WPF에서의 특징적인 기능 및 다양한 데이터 바인딩 기법에 대해서도 깊이 있는 연구를 진행하는 것이 좋습니다. 풍부한 WPF 앱을 개발하는 데 있어 DataContext는 핵심적인 역할을 하니, 다양한 상황에서 이 개념을 활용하여 높은 품질의 애플리케이션을 만들어 보시기 바랍니다.