まめ - たんたんめん

備忘録 C# / WPF 多め

(WPF) SelectedItems をViewModelで取得する

MVVMパターンWPFアプリを作成しているとListViewやDataGridのSelectedItemsが取得したくなることがあります。
しかしSelectedItemsはDependencyPropertyではないので直接バインドすることはできません。
そこで以下の様なビヘイビアを作成してすることで問題を解決しました。

    [TypeConstraint(typeof(Selector))]
    public class SelectedItemsBehavior : Behavior<Selector>
    {
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register("SelectedItems", 
                                        typeof(IEnumerable), 
                                        typeof(SelectedItemsBehavior),
                new FrameworkPropertyMetadata(null, 
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

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

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

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

        private void SelectionChanged(object sender, SelectionChangedEventArgs args)
        {
            //! MultiSelector.SelectedItems や ListBox.SelectedItems を同じ物として扱いたい。
            dynamic selector = AssociatedObject;

            SelectedItems = Enumerable.ToArray(selector.SelectedItems);
        }

使いかた

<ListView>
    <i:Interaction.Behaviors>
        <behaviors:SelectedItemsBehavior SelectedItems="{Binding SelectedItems}" />
    </i:Interaction.Behaviors>
</ListView>

<DataGrid>
    <i:Interaction.Behaviors>
        <behaviors:SelectedItemsBehavior SelectedItems="{Binding SelectedItems}" />
    </i:Interaction.Behaviors>
</DataGrid>

実はDataGrid.SelectedItems と ListView.SelectedItems は同一のプロパティでは無いのですが
dynamic構文を使うことでそのあたりの曖昧さを吸収することができるのです。
但しdynamicでは拡張メソッドが上手く解決できないので注意してください。