WPFでread-onlyなプロパティと双方向バインドする

タイトル通りなのですが、日本語のページが見つからなかったのでメモ。
以下、ビヘイビアを使ってDataGridのSelectedItemsとバインドする例になります。

# ビヘイビアの作成

```behaviors/DataGridSelectedItemsBlendBehavior.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace WpfApp3.Behaviors
{
    class DataGridSelectedItemsBlendBehavior : Behavior<DataGrid>
    {
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.Register(
                "SelectedItems",
                typeof(IList<object>),
                typeof(DataGridSelectedItemsBlendBehavior),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged)
            );

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

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

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

            if (e.AddedItems != null)
            {
                foreach (var item in e.AddedItems) SelectedItems.Add(item);
            }

            if (e.RemovedItems != null)
            {
                foreach (var item in e.RemovedItems) SelectedItems.Remove(item);
            }
        }

        private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is DataGridSelectedItemsBlendBehavior behavior)
            {
                var dataGrid = behavior.AssociatedObject;

                if (behavior.SelectedItems != null && dataGrid?.SelectedItems != null)
                {
                    dataGrid.SelectionChanged -= behavior.OnSelectionChanged;

                    dataGrid.SelectedItems.Clear();
                    foreach (var item in behavior.SelectedItems)
                    {
                        dataGrid.SelectedItems.Add(item);
                    }

                    dataGrid.SelectionChanged += behavior.OnSelectionChanged;
                }
            }
        }

        public IList<object> SelectedItems
        {
            get { return (IList<object>)GetValue(SelectedItemsProperty); }
            set { SetValue(SelectedItemsProperty, value); }
        }
    }
}
```

依存関係プロパティの規約に沿って、static readonlyなDependencyPropertyのインスタンスを～Propertyという名前で作成します。
FrameworkPropertyMetaDataコンストラクタの第3引数にコールバックメソッドを指定するのを忘れないでください。
私はこれを忘れてソースからターゲットへの反映ができずハマりました。
OnSelectedItemsChangedでイベントハンドラを一度解除しているのは、ClearやAddごとにSelectionChangedが発生してしまうからです。
WpfApp3？そこに気づくとは…やはり天才か（適当）

# View側の設定

```Views/MainWindow.xaml
<Window x:Class="WpfApp3.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp3"
        xmlns:behaviors="clr-namespace:WpfApp3.Behaviors"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="25" />
            <RowDefinition Height="25" />
        </Grid.RowDefinitions>

        <DataGrid Grid.Row="0" ItemsSource="{Binding Members, Mode=OneTime}">
            <i:Interaction.Behaviors>
                <behaviors:DataGridSelectedItemsBlendBehavior SelectedItems="{Binding SelectedMembers}" />
            </i:Interaction.Behaviors>

            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectedCellsChanged">
                    <i:InvokeCommandAction Command="{Binding SelectedCellsChangedCommand}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </DataGrid>

        <TextBox Grid.Row="1" Text="{Binding SelectedMembersNames, Mode=OneWay}" />

        <Button Grid.Row="2" Content="(・8・)" Command="{Binding ButtonClickCommand}" />
    </Grid>
</Window>
```

ViewでDataGridにビヘイビアの指定とSelectedItemsのバインド先を指定します。
TextBoxに選択中のアイテムを表示して、Buttonクリックでプログラム側から選択中のアイテムを変更する想定です。

# ViewModelの作成

```ViewModels/MainWindowViewModel.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;

using Prism.Commands;
using Prism.Mvvm;

namespace WpfApp3.ViewModels
{
    using Models;

    class MainWindowViewModel : BindableBase
    {
        public List<Person> Members { get; }

        private ObservableCollection<object> selectedMembers = new ObservableCollection<object>();
        public ObservableCollection<object> SelectedMembers
        {
            get { return selectedMembers; }
            set { SetProperty(ref selectedMembers, value); }
        }

        public string SelectedMembersNames => string.Join(", ", SelectedMembers.Select(p => ((Person)p).Name));

        public ICommand ButtonClickCommand { get; }
        public ICommand SelectedCellsChangedCommand { get; }

        public MainWindowViewModel()
        {
            Members = new List<Person>
            {
                new Person("Honoka Kousaka", 16),
                new Person("Eli Ayase", 17),
                new Person("Kotori Minami", 16),
                new Person("Umi Sonoda", 16),
                new Person("Rin Hoshizora", 15),
                new Person("Maki Nishikino", 15),
                new Person("Nozomi Tojo", 17),
                new Person("Hanayo Koizumi", 15),
                new Person("Nico Yazawa", 17),
            };

            ButtonClickCommand = new DelegateCommand(
                () => SelectedMembers = new ObservableCollection<object> { Members[2] });

            SelectedCellsChangedCommand = new DelegateCommand(
                () => RaisePropertyChanged(nameof(SelectedMembersNames)));
        }
    }
}
```

ViewModelです。PersonクラスはModels名前空間で適当に定義しています。
ボタンを押したり選択行を適当に変えたりしてTextBoxに正しく表示されることを確認してみてください。

ボタンをクリックした時
![ボタンをクリックした時.png](https://qiita-image-store.s3.amazonaws.com/0/243230/baeafb5d-dcef-9c97-f7c5-dd813947115e.png "ボタンをクリックした時")

行をいくつか選択した時
![行をいくつか選択した時.png](https://qiita-image-store.s3.amazonaws.com/0/243230/a939d710-6702-8459-4213-114d882a74bc.png "行をいくつか選択した時")


# 余談
片方向でいいから直接バインドしたいと思っても、setterがないとOneWayToSourceですらバインドできないというWPFの仕様ェ…
あと、ModelにIsSelectedみたいなプロパティを準備できるならそれもありだと思います。
(・8・)

# 参考
[WPF/MVVM: Binding to Read-Only Properties Using Behaviors - TechNet Articles](https://social.technet.microsoft.com/wiki/contents/articles/22871.wpfmvvm-binding-to-read-only-properties-using-behaviors.aspx)
[WPFのDataGridで選択された複数のアイテムをバインドするためのビヘイビア](http://chorusde.hatenablog.jp/entry/2013/02/28/064747) ※ターゲットからソースのバインド