CODEKILLER

반응형

▶ 리스트박스에 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>
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band