Model-View-ViewModel パターンで Silverlight アプリケーションを作成していて、ListBox で選択されている項目を ViewModel 側で取得したい場面に遭遇しました。
選択されている項目が1個だけなら、ViewModel に SelectedObject みたいなプロパティを用意して、それを ListView の SelectedItem プロパティにバインドすれば済みます。しかし、複数の項目が選択されている場合、この方法は使えません。ListBox には SelectedItems プロパティはありますが、これは依存関係プロパティではないので。
そこで、ListBox の SelectedItems プロパティの変更を ViewModel が持つコレクションに反映させるために、以下の添付ビヘイビアを作成してみました。
public static class ListBoxBehavior { private static readonly DependencyProperty SelectedItemsBehaviorProperty = DependencyProperty.RegisterAttached( "SelectedItemsBehavior", typeof(SelectedItemsBehavior), typeof(ListBox), null); public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached( "SelectedItems", typeof(IList), typeof(ListBoxBehavior), new PropertyMetadata(null, ItemsPropertyChanged)); public static void SetSelectedItems(ListBox listBox, IList list) { listBox.SetValue(SelectedItemsProperty, list); } public static IList GetSelectedItems(ListBox listBox) { return listBox.GetValue(SelectedItemsProperty) as IList; } private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var target = d as ListBox; if (target != null) { GetOrCreateBehavior(target, e.NewValue as IList); } } private static SelectedItemsBehavior GetOrCreateBehavior(ListBox target, IList list) { var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior; if (behavior == null) { behavior = new SelectedItemsBehavior(target, list); target.SetValue(SelectedItemsBehaviorProperty, behavior); } return behavior; } private class SelectedItemsBehavior { private readonly ListBox _listBox; private readonly IList _boundList; private bool _listBoxSelectionChanging = false; private bool _collectionChanging = false; public SelectedItemsBehavior(ListBox listBox, IList boundList) { _boundList = boundList; if (_boundList is INotifyCollectionChanged) { ((INotifyCollectionChanged)_boundList).CollectionChanged += SelectedItemsBehavior_CollectionChanged; } _listBox = listBox; _listBox.SelectionChanged += OnSelectionChanged; } private void SelectedItemsBehavior_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (_listBoxSelectionChanging == false) { _collectionChanging = true; _listBox.SelectedItems.Clear(); foreach (var item in _boundList) { _listBox.SelectedItems.Add(item); } _collectionChanging = false; } } private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (_collectionChanging == false) { _listBoxSelectionChanging = true; _boundList.Clear(); foreach (var item in _listBox.SelectedItems) { _boundList.Add(item); } _listBoxSelectionChanging = false; } } } }
ViewModel に選択された項目を格納するためのコレクションを用意し、この添付プロパティにバインドしてやればいい。
<ListBox my:ListBoxBehavior.SelectedItems="{Binding Path=SelectedItems}"/>
バインドした ViewModel 内のコレクションが変更されたとき、ListBox に反映されるようにもなっています。
自分用に作ったものなので、十分に作り込んでいません。使っていて問題が見つかったら、その都度修正する予定。