▶ 리스트박스에 SelectedItems에 대한 양방향 바인딩 Behavior (Twoway Binding)
public class ListBoxSelectionBehavior : Behavior<ListBox>
{
#region Static
/// <summary>
/// SelectedItems 종속성 속성
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList),
typeof(ListBoxSelectionBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedItemsChanged));
/// <summary>
/// SelectedValues 종속성 속성
/// </summary>
public static readonly DependencyProperty SelectedValuesProperty =
DependencyProperty.Register(nameof(SelectedValues), typeof(IList),
typeof(ListBoxSelectionBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedValuesChanged));
/// <summary>
/// 선택한 항목 속성이 바인딩 소스에서 변경될 때 호출됩니다.
/// </summary>
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var behavior = (ListBoxSelectionBehavior)sender;
if (behavior._modelHandled) return;
if (behavior.AssociatedObject == null)
return;
behavior._modelHandled = true;
behavior.SelectedItemsToValues();
behavior.SelectItems();
behavior._modelHandled = false;
}
/// <summary>
/// 선택한 항목 속성이 바인딩 소스에서 변경될 때 호출됩니다.
/// </summary>
private static void OnSelectedValuesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var behavior = (ListBoxSelectionBehavior)sender;
if (behavior._modelHandled) return;
if (behavior.AssociatedObject == null)
return;
behavior._modelHandled = true;
behavior.SelectedValuesToItems();
behavior.SelectItems();
behavior._modelHandled = false;
}
private static object GetDeepPropertyValue(object obj, string path)
{
if (string.IsNullOrWhiteSpace(path)) return obj;
while (true)
{
if (path.Contains('.'))
{
var split = path.Split('.');
var remainingProperty = path.Substring(path.IndexOf('.') + 1);
obj = obj?.GetType().GetProperty(split[0])?.GetValue(obj, null);
path = remainingProperty;
continue;
}
return obj?.GetType().GetProperty(path)?.GetValue(obj, null);
}
}
#endregion
private bool _viewHandled;
private bool _modelHandled;
/// <summary>
/// 바인딩 가능한 선택 항목 배열
/// </summary>
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
/// <summary>
/// 바인딩 가능한 선택된 값 배열
/// </summary>
public IList SelectedValues
{
get => (IList)GetValue(SelectedValuesProperty);
set => SetValue(SelectedValuesProperty, value);
}
/// <summary>
/// SelectedItems 속성을 기반으로 목록 상자에서 항목을 선택합니다.
/// </summary>
private void SelectItems()
{
_viewHandled = true;
AssociatedObject.SelectedItems.Clear();
if (SelectedItems != null)
{
foreach (var item in SelectedItems)
AssociatedObject.SelectedItems.Add(item);
}
_viewHandled = false;
}
/// <summary>
/// SelectedValues를 기반으로 SelectedItems 업데이트
/// </summary>
private void SelectedValuesToItems()
{
if (SelectedValues == null)
SelectedItems = null;
else
SelectedItems =
AssociatedObject.Items.Cast<object>()
.Where(i => SelectedValues.Contains(GetDeepPropertyValue(i, AssociatedObject.SelectedValuePath)))
.ToArray();
}
/// <summary>
/// SelectedItems를 기반으로 SelectedValues를 업데이트합니다.
/// </summary>
private void SelectedItemsToValues()
{
if (SelectedItems == null)
SelectedValues = null;
else
SelectedValues =
SelectedItems.Cast<object>()
.Select(i => GetDeepPropertyValue(i, AssociatedObject.SelectedValuePath))
.ToArray();
}
/// <summary>
/// 보기에서 목록 상자 선택이 변경될 때 호출됩니다.
/// </summary>
private void OnListBoxSelectionChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection == null) return;
SelectedItems = AssociatedObject.SelectedItems.Cast<object>().ToObservableCollection();
}
/// <summary>
/// 목록 상자 항목이 변경될 때 호출됩니다.
/// </summary>
private void OnListBoxItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_viewHandled) return;
if (AssociatedObject.Items.SourceCollection == null) return;
SelectItems();
}
/// <summary>
/// override 함수입니다.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += OnListBoxSelectionChanged;
((INotifyCollectionChanged)AssociatedObject.Items).CollectionChanged += OnListBoxItemsChanged;
_modelHandled = true;
SelectedValuesToItems();
SelectItems();
_modelHandled = false;
}
/// <summary>
/// override 함수입니다.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectionChanged -= OnListBoxSelectionChanged;
((INotifyCollectionChanged)AssociatedObject.Items).CollectionChanged -= OnListBoxItemsChanged;
}
}
}
▶ Xaml에서 사용
<ListBox ItemsSource="{Binding CategoryItemSource}"
SelectedItem="{Binding SelectedCategory}"
SelectionMode="Extended"
>
<i:Interaction.Behaviors>
<behaviors:ListBoxSelectionBehavior
SelectedItems="{Binding SelectedCategory, Mode=TwoWay}" />
</i:Interaction.Behaviors>
...
</ListBox>