WPF の ListBox と DataGrid で Dcitionary をバインドする

# WPF の ListBox と DataGrid で Dcitionary をバインドする

名前と値を Dictionary を使ってバインドしようとして少しはまって結果的に ViewModel に Dictionary の Value についての Binding 設定を一切書かずにすっきりできたのでメモ。

- [WPF の ListBox と DataGrid で Dcitionary をバインドする](#wpf-%E3%81%AE-listbox-%E3%81%A8-datagrid-%E3%81%A7-dcitionary-%E3%82%92%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%99%E3%82%8B)
  - [開発環境](#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83)
    - [ソリューションの拡張機能](#%E3%82%BD%E3%83%AA%E3%83%A5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD)
  - [新規プロジェクトの作成](#%E6%96%B0%E8%A6%8F%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
  - [RegionManagerにViewを登録](#regionmanager%E3%81%ABview%E3%82%92%E7%99%BB%E9%8C%B2)
  - [Person クラス を作成](#person-%E3%82%AF%E3%83%A9%E3%82%B9-%E3%82%92%E4%BD%9C%E6%88%90)
  - [ContentViewModel を ViewModels に作成](#contentviewmodel-%E3%82%92-viewmodels-%E3%81%AB%E4%BD%9C%E6%88%90)
  - [ContentView を Views に作成](#contentview-%E3%82%92-views-%E3%81%AB%E4%BD%9C%E6%88%90)
  - [実行](#%E5%AE%9F%E8%A1%8C)
  - [まとめ](#%E3%81%BE%E3%81%A8%E3%82%81)

## 開発環境

* Windows 10
* Visual Studio 2017

---

### ソリューションの拡張機能

* Prism.Unity

---

## 新規プロジェクトの作成

[Template Pack]("") を参照ください。

---

## RegionManagerにViewを登録

MainWindow に ContentView を登録

```MainWindowViewModel.cs
        public MainWindowViewModel(IRegionManager regionManager)
        {
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(ContentView));
        }
```

---

## Person クラス を作成

Models フォルダを作り Person クラスを作成

```Person.cs
    class Person
    {
        public string Name { get; set; }

        public string Sex { get; set; }

        public int Age { get; set; }
    }
```

## ContentViewModel を ViewModels に作成

バインド用プロパティを定義してコンストラクタでサンプルデータ設定

```ContentViewModel.cs
        public ContentViewModel()
        {
            this.PersonDictionaries = new Dictionary<string, Person>()
            {
                {"長男", new Person(){Name="Ichiro", Sex ="男", Age=10, } },
                {"次男", new Person(){Name="Jiro", Sex ="男", Age=9, } },
                {"三男", new Person(){Name="Saburo", Sex ="男", Age=8, } },
                {"四男", new Person(){Name="Shiro", Sex ="男", Age=7, } },
                {"長女", new Person(){Name="Goro", Sex ="男", Age=8, } },
            };

            this.Items = new Dictionary<string, Dictionary<string, string>>()
            {
                {"長男", new Dictionary<string, string>() {{"名前", "一郎"}, { "性別", "男" }, { "年齢", "20"}} },
                {"次男", new Dictionary<string, string>() {{"名前", "二郎"}, { "性別", "男" }, { "年齢", "19"}} },
                {"三男", new Dictionary<string, string>() {{"名前", "三郎"}, { "性別", "男" }, { "年齢", "18"}} },
                {"四男", new Dictionary<string, string>() {{"名前", "四郎"}, { "性別", "男" }, { "年齢", "17"}} },
                {"長女", new Dictionary<string, string>() {{"名前", "花子"}, { "性別", "女" }, { "年齢", "18"}} },
            };
        }

        private Dictionary<string, Person> _personDictionaries;

        public Dictionary<string, Person> PersonDictionaries
        {
            get { return _personDictionaries; }
            set { SetProperty(ref _personDictionaries, value); }
        }

        private Dictionary<string, Dictionary<string, string>> _Items;

        public Dictionary<string, Dictionary<string, string>> Items
        {
            get { return _Items; }
            set { SetProperty(ref _Items, value); }
        }
```

---

## ContentView を Views に作成

ここで親データ（ListBox）と子データ（TextBlock or DataGrid）で直接バインド設定する

* ListBox は x:Name で名前付けする

* ListBox で Dictionary をバインドして、DisplayMenber に Key を設定して SelectedValuePath に Value を設定

* Value は 名前付けした ListBox の SelectedValue を直接参照するため ViewModel 側でバインドプロパティを定義する必要がない

```ContentView.xaml
<UserControl x:Class="PrismDictionaryBinding.Views.ContentView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ListBox x:Name="lb1" ItemsSource="{Binding PersonDictionaries}" DisplayMemberPath="Key" SelectedValuePath="Value" />
        <Viewbox Grid.Column="1" >
            <StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Name}"/>
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Sex}"/>
                <TextBlock Text="{Binding ElementName=lb1, Path=SelectedValue.Age}"/>
            </StackPanel>
        </Viewbox>
        <ListBox x:Name="lb2" Grid.Row="1" ItemsSource="{Binding Items}" DisplayMemberPath="Key" SelectedValuePath="Value" />
        <DataGrid Grid.Row="1" Grid.Column="1" ItemsSource="{Binding ElementName=lb2, Path=SelectedValue}" />
    </Grid>
</UserControl>
```

---

## 実行

```
上が Dictionary<string, person>

下が Dictionary<string, Dictionary<string, string>>
```

![実行画面](https://qiita-image-store.s3.amazonaws.com/0/256143/31703b1b-d240-c68c-a3f3-af0db0683eb9.png)

## まとめ

最初は SelectedItem をバインドしてそのプロパティセッターで対象データを表示しるということを ViewModel 側でやっていたんですが直接親コントロールとなる ListBox のプロパティを参照するようにしたことでシンプルに書くことが出来ました。

あと、SelectedItem の型を Dictionary<string, string> にしていてエラーになってなぜかーと思っていたら KeyValuePair の存在をすっかり忘れていてハマりました…。

ソースは [こちら](https://github.com/noda13/DictionaryBinding.git) に置いてあります。