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 패턴을 유지하면서도 코드비하인드를 사용하지 않고 컨트롤의 상태를 관리할 수 있습니다.
'프로그래밍 > WPF' 카테고리의 다른 글
[WPF] 이벤트: PreviewMouseDown과 MouseDown의 차이 (0) | 2024.11.18 |
---|---|
[WPF] DataGrid 행 더블 클릭 시 Command 실행하기 (0) | 2023.11.03 |
[WPF] HelixToolkit.Wpf.SharpDX 사용하여 3D 공간에서 마우스 드래그로 선택 영역 그리기 (0) | 2023.10.30 |
[WPF] Slider 드래그 완료 시점에 값 업데이트하기 (0) | 2023.07.26 |
[WPF] Data Binding에서 StringFormat 사용하기 (0) | 2023.07.25 |