» [WPF]INotifyCollectionChanged를 이용한 Dictionary

[WPF]INotifyCollectionChanged를 이용한 Dictionary

by DUBUKIMCH

INotifyCollectionChanged 인터페이스는 컬렉션의 변경 사항(추가, 삭제, 갱신 등)을 알리는 기능을 제공하는 인터페이스로, WPF에서 ObservableCollection에서 사용하는 인터페이스입니다. 이를 이용해 키-값 형태의 Dictionary 같은 컬렉션을 구현하려면, DictionaryINotifyCollectionChanged를 추가적으로 구현해 컬렉션의 변경을 알릴 수 있어야 합니다. 이를 통해 WPF 바인딩 시스템에서 변경을 감지하도록 할 수 있습니다.

원리

  • INotifyCollectionChanged 인터페이스는 컬렉션의 변경 이벤트를 알리기 위해 CollectionChanged 이벤트를 제공합니다.
  • 이 이벤트는 컬렉션에 항목이 추가되거나 삭제되었을 때 UI가 이를 감지해 업데이트할 수 있도록 합니다.

구현 방법

  1. INotifyCollectionChanged를 구현하는 클래스 생성.
  2. 내부적으로 Dictionary<TKey, TValue>를 사용해 키-값 구조를 유지.
  3. 항목이 추가, 삭제, 갱신될 때 CollectionChanged 이벤트를 트리거.

다음은 INotifyCollectionChangedINotifyPropertyChanged를 함께 구현하여 ObservableDictionary를 만드는 예시입니다.

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

public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
    private readonly Dictionary<TKey, TValue> _dictionary;

    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public void Add(TKey key, TValue value)
    {
        _dictionary.Add(key, value);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value)));
        OnPropertyChanged("Count");
        OnPropertyChanged("Keys");
        OnPropertyChanged("Values");
    }

    public bool Remove(TKey key)
    {
        if (_dictionary.TryGetValue(key, out TValue value) && _dictionary.Remove(key))
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value)));
            OnPropertyChanged("Count");
            OnPropertyChanged("Keys");
            OnPropertyChanged("Values");
            return true;
        }
        return false;
    }

    public TValue this[TKey key]
    {
        get => _dictionary[key];
        set
        {
            if (_dictionary.ContainsKey(key))
            {
                var oldValue = _dictionary[key];
                _dictionary[key] = value;
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, oldValue)));
                OnPropertyChanged("Values");
            }
            else
            {
                Add(key, value);
            }
        }
    }

    public void Clear()
    {
        _dictionary.Clear();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        OnPropertyChanged("Count");
        OnPropertyChanged("Keys");
        OnPropertyChanged("Values");
    }

    public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
    public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value);
    public ICollection<TKey> Keys => _dictionary.Keys;
    public ICollection<TValue> Values => _dictionary.Values;
    public int Count => _dictionary.Count;
    public bool IsReadOnly => false;

    public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
    public bool Contains(KeyValuePair<TKey, TValue> item) => _dictionary.ContainsKey(item.Key) && _dictionary[item.Key].Equals(item.Value);
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
    public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e);
    protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

설명

  • Dictionary<TKey, TValue>: 키-값 컬렉션을 보유하며, ObservableDictionary는 이를 감싸고 INotifyCollectionChangedINotifyPropertyChanged를 구현해 변경 사항을 알립니다.
  • Add, Remove, Clear 메서드: 컬렉션을 변경할 때 CollectionChanged 이벤트를 발생시켜 WPF 바인딩 시스템에 변경 사실을 알립니다.
  • this[TKey key] 프로퍼티: 인덱서를 통해 값을 설정할 때, 기존 키가 있으면 값을 업데이트하고 없으면 새로운 항목으로 추가합니다.

활용

이렇게 구현된 ObservableDictionaryWPF 프로젝트에서 DataContextBinding으로 연결해 UI 요소가 컬렉션의 변경을 실시간으로 반영하도록 사용할 수 있습니다. 예를 들어, DataGridListBoxItemsSource로 바인딩하면, 컬렉션 변경 시 자동으로 UI에 업데이트가 반영됩니다.

활용 예시 코드

1. WPF XAML 파일 (MainWindow.xaml)

ListBox를 사용하여 ObservableDictionary의 데이터를 키-값 형식으로 표시하는 예제입니다.

<Window x:Class="WpfApp241112.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ObservableDictionary Example" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding MyDictionary}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Key}" Margin="5" />
                        <TextBlock Text=": " />
                        <TextBlock Text="{Binding Value}" Margin="5" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Add Item" Width="100" Height="30" HorizontalAlignment="Left" VerticalAlignment="Bottom" Click="AddItemButton_Click" />
    </Grid>
</Window>

2. 코드 비하인드 파일 (MainWindow.xaml.cs)

WPF 윈도우의 코드 비하인드에서 ObservableDictionary를 생성하고 바인딩합니다.

using System.Windows;

namespace WpfApp241112
{
    public partial class MainWindow : Window
    {
        public ObservableDictionary<string, string> MyDictionary { get; set; }

        public MainWindow ()
        {
            InitializeComponent();
            MyDictionary = new ObservableDictionary<string, string>
            {
                { "Item1", "Value1" },
                { "Item2", "Value2" },
                { "Item3", "Value3" }
            };

            // DataContext를 설정하여 바인딩할 수 있도록 함
            DataContext = this;
        }

        private void AddItemButton_Click (object sender, RoutedEventArgs e)
        {
            int newItemIndex = MyDictionary.Count + 1;
            MyDictionary.Add($"Item{newItemIndex}", $"Value{newItemIndex}");
        }
    }
}

실행 결과

설명

  • ObservableDictionary<string, string>: 키와 값을 문자열로 사용하는 ObservableDictionary입니다.
  • DataContext: WPF의 DataContext로 설정하여 XAML에서 MyDictionary를 바인딩할 수 있도록 합니다.
  • AddItemButton_Click 이벤트: 버튼을 클릭하면 ObservableDictionary에 새로운 항목이 추가되고, 이로 인해 CollectionChanged 이벤트가 발생하여 UI가 업데이트됩니다.

실행 결과

  • 처음 실행하면 ListBoxItem1: Value1, Item2: Value2, Item3: Value3이 표시됩니다.
  • Add Item 버튼을 클릭할 때마다 새로운 항목이 Item4: Value4, Item5: Value5 등의 형태로 추가되고 ListBox가 자동으로 갱신됩니다.

이 코드는 ObservableDictionaryCollectionChanged 이벤트를 발생시켜 WPF UI가 변경 사항을 실시간으로 반영하는 예시입니다.

You may also like

Leave a Comment

error: Content is protected !!