프로그래밍/WPF

[WPF] DataGrid 가상화와 ComboBox SelectedItem 불일치 문제 해결: MVVM 패턴에서의 효율적인 접근 방법

흔한티벳여우 2024. 10. 15. 15:40
반응형

WPF(Windows Presentation Foundation)에서 DataGrid는 대량의 데이터를 효율적으로 표시하기 위한 강력한 컨트롤입니다. 그러나 가상화(Virtualization)를 사용할 때 ComboBox와 같은 편집 컨트롤을 포함하면 예상치 못한 문제가 발생할 수 있습니다. 특히, MVVM 패턴을 사용하는 경우에는 이러한 문제가 더욱 복잡해질 수 있습니다.

 

이번 글에서는 DataGrid의 가상화로 인해 발생하는 ComboBox의 SelectedItem 불일치 문제를 분석하고, MVVM 패턴을 유지하면서 이를 해결하는 방법을 소개하겠습니다.

 

문제 상황

DataGrid에 가상화를 적용하고 각 행(Row)에 ComboBox를 포함시켰을 때, SelectedIndex가 -1인 경우 ComboBox가 잘못된 항목을 표시하는 문제가 발생합니다. 실제 데이터는 정상적으로 유지되지만, UI에 표시되는 내용이 데이터와 일치하지 않는 현상이 나타납니다.

 

기존 XAML 코드 예시

<DataGridTemplateColumn Header="Class" SortMemberPath="ClassData.ClassInfo.Name">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                DisplayMemberPath="Name"
                ItemsSource="{Binding DataContext.ClassList, RelativeSource={RelativeSource AncestorType=UserControl}}"
                SelectedItem="{Binding ClassData.ClassInfo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                <!-- 기타 설정 -->
            </ComboBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

 

문제 원인

가상화와 컨테이너 재활용

DataGrid의 가상화는 VirtualizingPanel.VirtualizationMode가 Recycling으로 설정되어 있을 때 컨트롤의 **컨테이너 재활용(Container Recycling)**을 활성화합니다. 이는 성능 향상을 위해 스크롤할 때 보이지 않는 행의 컨트롤을 제거하지 않고 재사용하는 방식입니다.


그러나 ComboBox와 같은 컨트롤이 포함된 경우, 컨트롤의 **상태(State)**가 이전 행의 값을 유지하여 새로운 데이터 컨텍스트에 맞게 업데이트되지 않는 문제가 발생할 수 있습니다. 특히 SelectedIndex가 -1(선택되지 않음)인 경우, 이전에 선택된 항목이 그대로 표시되거나 잘못된 값이 표시됩니다.

 

해결 방법

MVVM 패턴을 유지하면서 이 문제를 해결하기 위해 Attached Behavior를 사용하는 것을 추천합니다. 이는 코드비하인드를 사용하지 않고도 컨트롤의 동작을 확장하여 원하는 기능을 구현할 수 있는 방법입니다.

 

1. Attached Behavior 클래스 생성

우선, ComboBox의 동작을 확장하는 Attached Behavior 클래스를 작성합니다.

// 네임스페이스는 프로젝트 구조에 맞게 수정하세요.
namespace YourNamespace.Behaviors
{
    public static class ComboBoxBehaviors
    {
        public static readonly DependencyProperty UpdateSelectedItemOnDataContextChangedProperty =
            DependencyProperty.RegisterAttached(
                "UpdateSelectedItemOnDataContextChanged",
                typeof(bool),
                typeof(ComboBoxBehaviors),
                new UIPropertyMetadata(false, OnUpdateSelectedItemOnDataContextChangedChanged));

        public static bool GetUpdateSelectedItemOnDataContextChanged(DependencyObject obj)
        {
            return (bool)obj.GetValue(UpdateSelectedItemOnDataContextChangedProperty);
        }

        public static void SetUpdateSelectedItemOnDataContextChanged(DependencyObject obj, bool value)
        {
            obj.SetValue(UpdateSelectedItemOnDataContextChangedProperty, value);
        }

        private static void OnUpdateSelectedItemOnDataContextChangedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is ComboBox comboBox)
            {
                if ((bool)e.NewValue)
                {
                    comboBox.DataContextChanged += ComboBox_DataContextChanged;
                }
                else
                {
                    comboBox.DataContextChanged -= ComboBox_DataContextChanged;
                }
            }
        }

        private static void ComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is ComboBox comboBox)
            {
                // BindingExpression을 통해 SelectedItem 바인딩을 강제로 업데이트
                var bindingExpression = comboBox.GetBindingExpression(ComboBox.SelectedItemProperty);
                bindingExpression?.UpdateTarget();
            }
        }
    }
}

 

  • 설명
    • UpdateSelectedItemOnDataContextChanged라는 Attached Property를 정의하여 ComboBox의 DataContextChanged 이벤트를 처리합니다.
    • DataContext가 변경될 때마다 SelectedItem 바인딩을 강제로 업데이트하여 올바른 값이 표시되도록 합니다.

2. XAML에서 Attached Behavior 적용

이제 XAML에서 해당 Behavior를 ComboBox에 적용합니다.

<!-- 네임스페이스 선언 -->
xmlns:behaviors="clr-namespace:YourNamespace.Behaviors"

<!-- ComboBox 수정 -->
<ComboBox
    behaviors:ComboBoxBehaviors.UpdateSelectedItemOnDataContextChanged="True"
    DisplayMemberPath="Name"
    ItemsSource="{Binding DataContext.ClassList, RelativeSource={RelativeSource AncestorType=UserControl}}"
    SelectedItem="{Binding ClassData.ClassInfo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <!-- 기타 설정 -->
</ComboBox>

 

  • 설명
    • behaviors:ComboBoxBehaviors.UpdateSelectedItemOnDataContextChanged="True"를 추가하여 Behavior를 활성화합니다.
    • 나머지 바인딩은 기존과 동일하게 유지합니다.

3. 바인딩 및 데이터 모델 확인

  • SelectedItem 바인딩 확인: ClassData와 ClassInfo가 INotifyPropertyChanged를 구현하여 변경 사항을 알릴 수 있도록 합니다.
  • ItemsSource의 데이터 확인: ClassList가 올바르게 로드되고 변경 사항이 UI에 반영되는지 확인합니다.

 

결론

WPF의 DataGrid에서 가상화를 사용할 때 발생하는 ComboBox의 표시 문제는 Attached Behavior를 사용하여 효과적으로 해결할 수 있습니다. 이를 통해 MVVM 패턴을 유지하면서도 코드비하인드를 사용하지 않고 컨트롤의 상태를 관리할 수 있습니다.

 

 

반응형