2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ListBox.SelectedItemsにBindingしたい

Posted at

はじめに

WPFListBoxSelectedItemsには以下のようにプロパティをバインドできません。

 <ListBox SelectedItems="{Binding Selection, Mode=OneWayToSource}" />

Microsoft.Xaml.Behaviors.Wpfを使うことで以下のように間接的にバインドできるようにします。

<ListBox>
    <b:Interaction.Behaviors>
        <local:HogeListBoxSelectionBehavior
            SelectedItems="{Binding Selection}" />
    </b:Interaction.Behaviors>
</ListBox>

ここでbxmlns:b="http://schemas.microsoft.com/xaml/behaviors"によって定義されます。

ビヘイビアクラス

XAMLではジェネリックなクラスの要素をルート以外に記述できないので、ListBoxSelectionBehavior<T>を継承したクラスHogeListBoxSelectionBehaviorを定義しておきます。

class HogeListBoxSelectionBehavior : Behaviors.ListBoxSelectionBehavior<Hoge> { }
ListBoxSelectionBehavior.cs
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;

using Microsoft.Xaml.Behaviors;

public class ListBoxSelectionBehavior<T> : Behavior<ListBox>
{
    #region == SelectedItems ==

    public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register(
                nameof(SelectedItems),
                typeof(IList<T>),
                typeof(ListBoxSelectionBehavior<T>),
                new FrameworkPropertyMetadata
                {
                    DefaultValue = null,
                    PropertyChangedCallback = OnSelectedItemsChanged,
                });

    public IList<T> SelectedItems
    {
        get => (IList<T>)GetValue(SelectedItemsProperty);
        set => SetValue(SelectedItemsProperty, value);
    }

    private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        if (sender is not ListBoxSelectionBehavior<T> behavior)
        {
            return;
        }

        if (args.OldValue is INotifyCollectionChanged oldObservableSelectedItems)
        {
            oldObservableSelectedItems.CollectionChanged -= behavior.OnSourceSelectedItemsChanged;
        }

        if (args.NewValue is INotifyCollectionChanged newObservableSelectedItems)
        {
            newObservableSelectedItems.CollectionChanged += behavior.OnSourceSelectedItemsChanged;
        }

        if (args.NewValue is not IList<T> newSelectedItems)
        {
            return;
        }

        behavior.SelectItems(newSelectedItems);
    }

    private void OnSourceSelectedItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        ApplyChange(_listBox?.SelectedItems, selectedItems =>
        {
            if (e.OldItems is not null)
            {
                foreach (var item in e.OldItems)
                {
                    selectedItems.Remove(item);
                }
            }

            if (e.NewItems is not null)
            {
                foreach (var item in e.NewItems)
                {
                    selectedItems.Add(item);
                }
            }
        });
    }

    #endregion

    private ListBox? _listBox;
    private bool _isChanging = false;

    protected override void OnAttached()
    {
        base.OnAttached();

        _listBox = AssociatedObject;
        _listBox.SelectionChanged += OnSelectionChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (_listBox is null)
        {
            return;
        }

        _listBox.SelectionChanged -= OnSelectionChanged;
        _listBox = null;
    }

    private void SelectItems(IList<T> items)
    {
        ApplyChange(_listBox?.SelectedItems, selectedItems =>
        {
            selectedItems.Clear();

            foreach (var item in items)
            {
                selectedItems.Add(item);
            }
        });
    }

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ApplyChange(SelectedItems, selectedItems =>
        {
            foreach (var item in e.RemovedItems)
            {
                selectedItems.Remove((T)item);
            }

            foreach (var item in e.AddedItems)
            {
                selectedItems.Add((T)item);
            }
        });
    }

    private void ApplyChange(System.Collections.IList? items, Action<System.Collections.IList> action)
    {
        if (_isChanging || items is null)
        {
            return;
        }

        _isChanging = true;
        action(items);
        _isChanging = false;
    }

    private void ApplyChange(IList<T>? items, Action<IList<T>> action)
    {
        if (_isChanging || items is null)
        {
            return;
        }

        _isChanging = true;
        action(items);
        _isChanging = false;
    }
}

おわりに

Behavior<T>.AssociatedObjectがnullableじゃないのにnullになることがあるのは罠です。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?