【WPF+Prism】入力項目のヘッダーを専用プロパティを用意せずにバインドする方法③（実装方法）

前回までの記事で入力項目のヘッダーをバインドする基礎的な部分について紹介いたしました。

[①　基礎編：DynamicObjectのバインド](https://qiita.com/jakucho0926/items/7fbab51a2f7dbeb2d5ec)
[②　基礎編：コントロールのプロパティ拡張](https://qiita.com/jakucho0926/items/a87cc32245df6efbad92)

今回は、現実的な実装方法について紹介したいと思います。

また、実装環境については以下の通りです
VisualStudio 2019
.NET 5
Prism.DryIoc 8.0.0.1909

# 実装方法について
ViewModelに入力項目に関連するプロパティを用意するのはあまり好きではないので、ViewModelにModelを保持する形で実装したいと思います。
まずは、プロジェクトを作成します。今回もPrism Blank Appでプロジェクトを作成しました。

次にModelを作成しましょう。今回は、商品コード・商品名を管理するModelクラスを作成しました。

Goods.cs
```cs
namespace Sample.Models
{
    public class Goods : Prism.Mvvm.BindableBase
    {
        private string _code;
        [System.ComponentModel.DisplayName("商品コード")]
        public string Code
        {
            get { return _code; }
            set { SetProperty(ref _code, value); }
        }

        private string _name;
        [System.ComponentModel.DisplayName("商品名")]
        public string Name
        {
            get { return _name; }
            set { SetProperty(ref _name, value); }
        }
    }
}
```
各プロパティに「DisplayNameAttribute」を設定し、表示したい項目名を保持しています。
今回はこちらの項目名を画面項目とバインドする形で紹介していきます。

続いてViewModel側です。
といっても、ViewModelに先ほど作成したGoodsを保持するだけの簡単なViewModelです。

MainWindowViewModel.cs
```cs
using Prism.Mvvm;

namespace Sample.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private Models.Goods _model;
        public Models.Goods Model
        {
            get { return _model; }
            set { SetProperty(ref _model, value); }
        }

        public MainWindowViewModel(Models.Goods model)
        {
            this.Model = model;
        }
    }
}
```

※コンストラクタでDIされるようにしているので、App.RegisterTypesで登録する必要がありますが、そちらについては後ほど記載するGithubのコードを確認してください。

## １．DynamicObjectの実装
[①　基礎編：DynamicObjectのバインド](https://qiita.com/jakucho0926/items/7fbab51a2f7dbeb2d5ec)で省略した部分の実装を行います。

DynamicObjectを継承した、PropertyAttributesというクラスを作成します。

PropertyAttributes.cs
```cs
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Dynamic;

namespace Sample
{
    public class PropertyAttributes : System.Dynamic.DynamicObject
    {
        protected PropertyAttributes() { }

        protected Dictionary<string, object> Properties { get; } = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return this.Properties.TryGetValue(binder.Name, out result);
        }

        public static PropertyAttributes GetInstance<T>(Type t) where T : System.Attribute
        {
            var properties = t.GetProperties();

            var result = new PropertyAttributes();
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<T>();
                if (attribute == null)
                {
                    continue;
                }
                result.Properties.Add(property.Name, attribute);
            }
            return result;
        }

    }
}
```
このクラスはプロパティ名をキーにAttributeを返すDynamicObjectです。
GetInstance関数にて、Model・Attributeの型を受け取り各プロパティに付与されている属性を保持したインスタンスを返します。

## ２．表示名用プロパティの実装
次に、Model側を少し修正していきたいと思います。
まずは、Model用インターフェースを作成しましょう。

IModel.cs
```cs
namespace Sample.Models
{
    public interface IModel
    {
        public System.Dynamic.DynamicObject DisplayName { get; }
    }
}
```
「DisplayName」というプロパティを実装するインターフェースです。
Modelに直接プロパティを実装してもよいのですが、後の変更を考えるとインターフェース化しておいたほうがよいでしょう。


上記のインターフェースをGoodsクラスに実装します。

Goods.cs
```cs
namespace Sample.Models
{
    public class Goods : Prism.Mvvm.BindableBase, IModel
    {

        private System.Dynamic.DynamicObject _displayName;
        public System.Dynamic.DynamicObject DisplayName
        {
            get
            {
                if (this._displayName == null)
                {
                    this._displayName = PropertyAttributes.GetInstance<System.ComponentModel.DisplayNameAttribute>(this.GetType());
                }
                return this._displayName;
            }
        }

        private string _code;
        [System.ComponentModel.DisplayName("商品コード")]
        public string Code
        {
            get { return _code; }
            set { SetProperty(ref _code, value); }
        }

        private string _name;
        [System.ComponentModel.DisplayName("商品名")]
        public string Name
        {
            get { return _name; }
            set { SetProperty(ref _name, value); }
        }
    }
}
```
「IModel」インターフェースの「DisplayName」プロパティを実装しました。

## ３．Viewの修正
続いてViewの修正です。

MainWindow.xaml
```xml
<Window x:Class="Sample.Views.MainWindow"
        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"
        Title="{Binding Title}" Height="350" Width="525" >
    <Grid>
        <StackPanel>
            <DockPanel>
                <TextBlock Text="{Binding Model.DisplayName.Code.DisplayName}" Width="100" TextAlignment="Center" />
                <TextBox Text="{Binding Model.Code}"/>
            </DockPanel>
            <DockPanel>
                <TextBlock Text="{Binding Model.DisplayName.Name.DisplayName}" Width="100" TextAlignment="Center" />
                <TextBox Text="{Binding Model.Name}"/>
            </DockPanel>
        </StackPanel>
    </Grid>
</Window>
```

これで画面を表示してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2591086/c7ad1100-7f68-8ffe-c401-86bc6bf5bb02.png)

Goodsクラスの各プロパティに設定されている「DisplayNameAttribute.DisplayName」の値が表示されていますね。
このままでも、十分に実用的な範囲だとは思いますが、今回は更に掘り下げてみましょう

## ４．拡張プロパティの実装
[②　基礎編：コントロールのプロパティ拡張](https://qiita.com/jakucho0926/items/a87cc32245df6efbad92)にて作成した「ExtendProperties」クラスを実装します。
内容は全く同じなので、気になる方は上記の記事を確認してください。

## ５．拡張プロパティのバインディング
さらにViewを修正してみましょう。
今回はTextBoxにHeaderを表示するStyleを作成してみました。
MainWindow.xaml
```xml
<Window x:Class="Sample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        xmlns:Sample="clr-namespace:Sample"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >
    <Window.Resources>
        <Style x:Key="HeaderTextBox" TargetType="{x:Type TextBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <DockPanel>
                            <TextBlock Text="{TemplateBinding Sample:ExtendProperties.Title}" Width="100" TextAlignment="Center" />
                            <TextBox Text="{TemplateBinding Text}"/>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <StackPanel>
            <DockPanel>
                <TextBox Text="{Binding Model.Code}" 
                         Sample:ExtendProperties.Title="{Binding Model.DisplayName.Code.DisplayName}" 
                         Style="{StaticResource HeaderTextBox}"/>
            </DockPanel>
            <DockPanel>
                <TextBox Text="{Binding Model.Name}" 
                         Sample:ExtendProperties.Title="{Binding Model.DisplayName.Name.DisplayName}" 
                         Style="{StaticResource HeaderTextBox}"/>
            </DockPanel>
        </StackPanel>
    </Grid>
</Window>
```

画面を起動すると以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2591086/b31e1678-9431-dc46-3b7e-89af6e8e5e65.png)
# まとめ
今回は、より現実的な実装方法について紹介してみました。
各内容について、大雑把な紹介になってしまいましたが、詳細な内容については各自で補完していただけると助かります。

Githubに今回までのサンプルコードを下記にアップしました。
https://github.com/jakucho/Prism_BindingSample1

まだ勉強途中ですので、より良い方法等があるかもしれません。
改善箇所がありましたらご指摘いただけると幸いです。