ListBoxやDataGridなどのItemsControlでSelectedItemsやIsSelectedをBindingする


はじめに

WPFでDataGridなどのItemControlを使用していると複数を選択した際のSelectedItemsを取得したいということがあります。
この際に、コードビハインドを使用すればあまり苦労せずに使用できますが、ViewModelとBindingしようとすると、難しくなってしまいます。
ググるとこれでもかというほどたくさんの実装方法が出ていますが、自分の記録用としてもここに記載しようと思います。
ここではDataGridを例に取り上げてみます。

IsSelectedのバインディング

まずはシンプルなものからIsSelectedのバインディング方法ですが、これはDataGridの場合、DataGrid.RowStyleを以下のコードのように設定するとできます

        <DataGrid ItemsSource="{Binding SampleList}">
            <DataGrid.RowStyle>
                <Style TargetType="DataGridRow">
                    <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                </Style>
            </DataGrid.RowStyle>
        </DataGrid>

SelectedItemsのバインディング

DataGridにはSelectedItemsというDependency Propertyがないので、この場合はビヘイビアを作成してバインドできるようにします
ビヘイビアのコードは以下の通りです
DataGridで使用するだけでなく、ItemsControlを継承したコントロールで使用できるようにSelectorのビヘイビアとして作成しています。
そのためにOnSelectionChangedでvar selector = AssociatedObjectとしています。varはdynamicとしてもかまいません

using Microsoft.Xaml.Behaviors;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace WPF.Behaviors
{
    public class SelectedItemsBehavior : Behavior<Selector>
    {
        public IList SelectedItems
        {
            get { return (IList)GetValue(SelectedItemsProperty); }
            set { SetValue(SelectedItemsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedItemsProxy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register("SelectedItems",
                typeof(IList),
                typeof(SelectedItemsBehavior),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.SelectionChanged += OnSelectionChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.SelectionChanged -= OnSelectionChanged;
        }

        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (sender == null) return;

            var selector = AssociatedObject;

            if(e.AddedItems != null && e.AddedItems.Count > 0 && this.SelectedItems != null)
            {
                foreach(var item in e.AddedItems)
                {
                    this.SelectedItems.Add(item);
                }
            }

            if(e.RemovedItems != null && e.RemovedItems.Count > 0 && this.SelectedItems != null)
            {
                foreach(var item in e.RemovedItems)
                {
                    this.SelectedItems.Remove(item);
                }
            }
        }
    }
}

Dependency PropertyとしてSelectedItemsを作成し、OnSelectionChangedでAddedItems、RemovedItemsで項目を追加削除しています
あとは、Viewで以下のように使用します

        <DataGrid ItemsSource="{Binding SampleList}">
            <i:Interaction.Behaviors>
                <behavior:SelectedItemsBehavior 
                    SelectedItems="{Binding SelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            </i:Interaction.Behaviors>
        </DataGrid>

これでSelectedItemsとのバインディングが可能となりました。