【WPF】DataGridTextColumnの実装を少しずつ進化させてWPFのイベント周りを理解してみよう


# 概要

Windows環境において、
**DataGridを使いたいからWPFを使う**
みたいな意見は結構あるだろう。

しかし、DataGridの各列にイベントを付与するのは思ったより大変。

そこで、DataGridのシンプルな列機能DataGridTextColumnを段階的に改造することで、
より汎用的な特性を持つDataGridを作ってみよう、というのがこの記事である。

WPFに慣れるための、私自身の整理としても使いたい。

# 進化のさせ方

見た目はこんな感じのやつを進化させる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/fd1fdab0-edf8-5d97-8214-3b772c0c5d54.png)


### デフォルトの列

　何にもないシンプルなデータ列。
　当然、デフォルトのままなので、機能なんて存在しない。

### コードビハインドのデータ列 ###

　コードビハインドで、以下の機能が追加されたデータ列。

- 入力中にEscapeボタンがおされたら、セルが入力終了されてしまうのを抑制する機能
- 入力中に数字以外のキーが押されたら、セルに値を入力しない機能。

　コードビハインドと聞くと、MVVMをやっている人から見たら蕁麻疹が出るかもしれないが、
　別に*.xaml.csに書くわけではないので、そこまで辛いコードにはならない。
　**状況次第では、この機能で十分良い場合も割と多い気がする。**

### Behaviorのデータ列 ###

　コードビハインドはごっつい気持ち悪いというときのために、
　Behaviorを使って同じ機能を実装する。

- メリットは、**Behaviorの使いまわしが出来る**点。
- デメリットは、**BehaviorとDataGridTextColumn用のクラス最低2種類を管理しないといけない**点。

　WPFをより使いこなしたい人にとっては重要項目であるが、
　ここまでやるかは正直チーム次第なところもあるような気はしている。

### UserStyleBehaviorのデータ列 ###

```
　Behaviorが専用だなんて、Behaviorの最大のメリットが生かせてないじゃないか。
　BehaviorとDataGridTextColumnは分離されて実装すべきだ。
```

　こういった声にこたえるのが、汎用的なBehaviorのデータ列である。

　この例は、割と近年のWPFの世界ではメジャーな解決策になることが多い印象がある。

　だが、何が問題かというと、**とにかく元コードが難しい。**
　リフレクションを使ったり、DependencyPropertyなどを使ったり、
　普通なら管理者がきちんとルールを作らないと、辛くなりそうな部分のコード。
　私自身もこの世界ではベテランではないので、挫折するところだ。

　これが必要な場合、パターン化して使っているが、
　私は、これで解決する必要がない場合は、結構避けている印象あり。

# 参考サイト

https://stackoverflow.com/questions/23008670/wpf-and-mvvm-how-to-move-focus-to-the-next-control-automatically

https://blog.okazuki.jp/entry/2014/12/21/205558

https://blog.okazuki.jp/entry/2016/07/19/192918

# 実現方法

## ライブラリ

- Prism.Wpfをnugetで入れてしまいましょう。今回は8.1.97で検証
- .NET 6を使ってみます
- 開発環境はVisual Studio 2022 Community

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/2061e8d1-335d-8e7b-44d0-2159e021cf08.png)

## 0.準備

https://github.com/belre/CSharpCodeSamples/tree/main/CustomizedDataGrid001/CustomizedDataGrid001

### DataGridTextColumnを継承したクラスを使用する

基本的には次のような構成でデータ列を使います

```C#:BaseUserTextColumn.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.VisualBasic;

namespace CustomizedDataGrid001
{
  public class BaseUserTextColumn : DataGridTextColumn
  {
    /// <summary>
    /// 列が生成されるときに実行される
    /// </summary>
    /// <param name="cell">セル</param>
    /// <param name="dataItem">Context</param>
    /// <returns></returns>
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
      return base.GenerateElement(cell, dataItem);
    }

    /// <summary>
    /// 列が編集されるときに実行される
    /// </summary>
    /// <param name="cell">セル</param>
    /// <param name="dataItem">Context</param>
    /// <returns></returns>
    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
      var textbox = (TextBox)base.GenerateEditingElement(cell, dataItem);
      return textbox;
    }
  }
}
```

ポイントとして、GenerateEditingElementでは、DataGridCellではなく**TextBox**をあてがっている点である。

まあ、確かによく見ると、DataGridが文字列入力可能な時はTextBoxが表示されていることが分かるであろう。
DataGridTextColumnの編集時にはこのような挙動になることがポイントで、
他のコンポーネントを使用する際にはこの概念を応用すればよい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/4ceff351-6938-0afb-7b00-c4cf7f0e5a2e.png)


※GenerateElementメソッドについてはこちら

https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.datagridcolumn.generateelement?view=windowsdesktop-6.0


### Viewを作成する

```XML:MainWindow.xaml
<Window x:Class="CustomizedDataGrid001.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:CustomizedDataGrid001"
        xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.DataContext>
    <local:MainViewModel />
  </Window.DataContext>

  <DataGrid ItemsSource="{Binding Items}" 
            AutoGenerateColumns="False">
    <DataGrid.Columns>
      <DataGridTextColumn Header="デフォルトの列"
                          Binding="{Binding DefaultItem}">
      </DataGridTextColumn>
      
      <local:CodeBehindTextColumn Header="TextColumnコードビハインドの列" 
                                  Binding="{Binding CodeBehindItem}">
      </local:CodeBehindTextColumn>

      <local:BehaviorTextColumn Header="Behaviorの列"
                                Binding="{Binding BehaviorItem}">
      </local:BehaviorTextColumn>

      <local:UserStyleBehaviorTextColumn Header="UserStyleBehaviorの列"
                               Binding="{Binding UserStyleBehaviorItem}">
         <!-- ☆ここにいろいろ書く -->
      </local:UserStyleBehaviorTextColumn>
    </DataGrid.Columns>
  </DataGrid>
</Window>

```

- コードビハインドで使用するクラスをCodeBehindTextColumn
- 専用のBehaviorで使用するクラスをBehaviorTextColumn
- 汎用のBehaviorで使用するクラスをUserStyleBehaviorTextColumn

とする。

Behaviorを使うのに、肝心のBehaviorの記述がないじゃんと思った方、それは適切である。
その話については、UserStyleBehaviorTextColumnで、**☆ここにいろいろ書く**を埋めるところだ。

### ViewModelを作成する

DataGridなので、ViewModelとしてはObservableCollectionクラスでバインドする。

とりあえずシンプルな構成で、

- MainViewModel
  - BranchViewModel 

という感じで。

```C#:MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Prism.Mvvm;
using Prism.Commands;

namespace CustomizedDataGrid001
{
  public class MainViewModel : BindableBase
  {
    public ObservableCollection<BranchViewModel> Items
    {
      get;
      set;
    }

    public MainViewModel()
    {
      Items = new ObservableCollection<BranchViewModel>()
      {
        new BranchViewModel()
        {
          DefaultItem = "デフォルト",
          CodeBehindItem = "コードビハインド", 
          BehaviorItem = "ビヘイビア",
          UserStyleBehaviorItem = "ビヘイビアインタラクション",
        },
        new BranchViewModel()
        {
          DefaultItem = "DEFAULT",
          CodeBehindItem = "CODEBEHIND",
          BehaviorItem = "BEHAVIOR",
          UserStyleBehaviorItem = "BEHAVIOR INTERACTION",
        }
      };
    }
  }
}
```

```C#:BranchViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Prism.Mvvm;
using Prism.Commands;

namespace CustomizedDataGrid001
{
  public class BranchViewModel : BindableBase
  {
    public string DefaultItem
    {
      get;
      set;
    }

    public string CodeBehindItem
    {
      get;
      set;
    }

    public string BehaviorItem
    {
      get;
      set;
    }

    public string UserStyleBehaviorItem
    {
      get;
      set;
    }

  }
}
```



## 1. コードビハインド

コードビハインドは、俗にいうxaml.csに書かれるいわゆるゴリゴリにイベントを書くコードである。
現在ではPrismを使う上ではあまりここを書くのは美しくないとされるものの、どうしても書かないといけないときもある。

とは言え今回書くコードは、xaml.csには書かない。
コードビハインドではあるものの、単に該当する処理が書かれないという感じで、
こちらの方が扱いの都合がいい場合もまあまああると思う。

```C#:CodeBehindTextColumn.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.VisualBasic;

namespace CustomizedDataGrid001
{
  public class CodeBehindTextColumn : DataGridTextColumn
  {
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
      cell.PreviewKeyDown -= Cell_PreviewKeyDown;
      cell.PreviewKeyDown += Cell_PreviewKeyDown;

      return base.GenerateElement(cell, dataItem);
    }


    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
      cell.PreviewTextInput -= Cell_PreviewTextInput;
      cell.PreviewTextInput += Cell_PreviewTextInput;
      return base.GenerateEditingElement(cell, dataItem);
    }


    private void Cell_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
      if(e.Key == System.Windows.Input.Key.Escape)
      {
        e.Handled = true;
      }
    }

    private void Cell_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
      int val = 0;
      if(!int.TryParse(e.Text, out val))
      {
        e.Handled = true;
      }
    }
  }
}
```

やっていることは、とても単純だ。

- GenerateElementでセルを生成するときに、PreviewKeyDownイベントをデリゲートで指定。
- GenerateEditingElementを生成するときに、PreviewTextInputイベントをデリゲートで指定。
- デリゲート先に実行したい処理を書く（e.Handled = trueとすれば、入力が終わった扱いにできる)



## 2. 専用のビヘイビア

だが、コードビハインドを使うと、いくつかの問題もある。
それは**イベントの汎用性が失われる**。

**ビヘイビアはコンポーネントに対して直接イベントを記述することが出来るので、イベント自体を汎用的に取り扱える**。
数字以外入力禁止だったり、Escキー禁止だったり、いろいろ入れるわけだが、こういったパターンさえ持っておけば、１つのクラスで管理出来るのは強みであろう。

とは言っても、ビヘイビアの組み方は単純。

```C#:BehaviorTextColumn.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;

namespace CustomizedDataGrid001
{
  public class BehaviorTextColumn : DataGridTextColumn
  {
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
      Interaction.GetBehaviors(cell).Add(new KeyEscapeBehavior());

      return base.GenerateElement(cell, dataItem);
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
      var textbox = (TextBox)base.GenerateEditingElement(cell, dataItem);
      Interaction.GetBehaviors(textbox).Add(new KeyOnlyNumericBehavior());
  
      return textbox;
    }

  }
}
```

ここで新たに、**KeyEscapeBehaviorクラス**と**KeyOnlyNumericBehaviorクラス**が追加された。

まず、KeyEscapeBehaviorビヘイビアを次のように書いてしまおう。


```C#:KeyEscapeBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;

namespace CustomizedDataGrid001
{
  public class KeyEscapeBehavior : Behavior<DataGridCell>
  {
    protected override void OnAttached()
    {
      AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
    }

    protected override void OnDetaching()
    {
      AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown;
    }

    private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
      if (e.Key == System.Windows.Input.Key.Escape)
      {
        e.Handled = true;
      }
    }
  }
}
```

Behaviorクラスはジェネリックが使われていて、割り当てたいコンポーネントを**AssociatedObjectプロパティ**として割り当てることが出来る。

---

### ビヘイビアを直接割り当てるのがなぜ難しいのか

本来通常のコンポーネントであれば、次のようにxamlで書くことでビヘイビアを割り当てられるはず

```xml
<!-- 上省略 -->
<bh:Interaction.Behaviors>
  <local:KeyEscapeBehavior />
</bh:Interaction.Behaviors>
<!-- 下省略 -->
```

ところが、これはこのDataGridTextColumnでは実現できない。

実現するには、CellStyleを使って

```xml
<local:BehaviorTextColumn.CellStyle>
  <Style TargetType="{x:Type DataGridCell}">
    <Setter Property="bh:Interaction.Behaviors">   <!-- 実行エラーが発生する -->
      <Setter.Value>
        <local:KeyEscapeBehavior />
      </Setter.Value>
    </Setter>
  </Style>
</local:BehaviorTextColumn.CellStyle>
```

などとして、Interaction.Behaviorsに直接KeyEscapeBehaviorを割り当てていく必要があるのだが、これは案の定ArgumentNullExceptionエラーとなってしまう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281173/ba6c6554-5281-7a88-f2fd-c7f921e8f098.png)

でも、それは当然なのだ。

Setter Propertyと書いてある項目は、BehaviorTextColumnのプロパティでなければいけないが、
そもそもInteractionというのは別のクラスであり、Behaviorsは実は単なる静的プロパティに過ぎないのだ
(詳しい技術情報は分からない部分も多いので、勉強中であるがそういうことだと思われる)。

---

専用のビヘイビアは、上記の問題を解決するための手段の一つであり、
**ビヘイビアの動きを継承で組み込むことで**、
ビヘイビアを使えるようにしている。

それが、GenerateElementの以下の部分

```C#
Interaction.GetBehaviors(cell).Add(new KeyEscapeBehavior());
```

そのものなのだ。
これを本来xamlで組めさえすればいいが、実用上はこれでも割と十分なことも多い。

なお、KeyOnlyNumericBehavior.csも大体こんな感じで書ける。

```C#:KeyOnlyNumericBehavior.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;

namespace CustomizedDataGrid001
{
  public class KeyOnlyNumericBehavior : Behavior<TextBox>
  {
    protected override void OnAttached()
    {
      AssociatedObject.PreviewTextInput += AssociatedObject_PreviewTextInput;
    }


    protected override void OnDetaching()
    {
      AssociatedObject.PreviewTextInput -= AssociatedObject_PreviewTextInput;
    }


    private void AssociatedObject_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
      int val = 0;
      if (!int.TryParse(e.Text, out val))
      {
        e.Handled = true;
      }
    }
  }
}

```



## 3. 汎用的ビヘイビア

先ほどのコード

```xml
<local:BehaviorTextColumn.CellStyle>
  <Style TargetType="{x:Type DataGridCell}">
    <Setter Property="bh:Interaction.Behaviors">   <!-- 実行エラーが発生する -->
      <Setter.Value>
        <local:KeyEscapeBehavior />
      </Setter.Value>
    </Setter>
  </Style>
</local:BehaviorTextColumn.CellStyle>
```

を解決するための手段が、**汎用的なビヘイビアの活用である**。

この実装をするためのコードUserStyleBehaviorCollection.csは以下の通り。
ここで、UserStyleBehaviorCollectionというのは、bh:Interaction.Behaviorsのラッパーの役割を果たす。
従って、bh:Interaction.Behaviorsの記述の代替をすることで、汎用的なビヘイビアを実装できるようになるのだ。

```C#:UserStyleBehaviorCollection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Xaml.Behaviors;

namespace CustomizedDataGrid001
{
  public class UserStyleBehaviorCollection : FreezableCollection<Behavior>
  {
    public static readonly DependencyProperty GeneratedCellBehaviorsProperty =
      DependencyProperty.RegisterAttached(
        "GeneratedCellBehaviors",
        typeof(UserStyleBehaviorCollection),
        typeof(UserStyleBehaviorCollection),
          new PropertyMetadata((sender, e) =>
          {
            if (e.OldValue == e.NewValue) { return; }

            var value = e.NewValue as UserStyleBehaviorCollection;
            if (value == null) { return; }

            var behaviors = Interaction.GetBehaviors(sender);
            behaviors.Clear();
            foreach (var b in value.Select(x => (Behavior)x.Clone()))
            {
              behaviors.Add(b);
            }
          }));

    public static readonly DependencyProperty GeneratedEditingCellBehaviorsProperty =
      DependencyProperty.RegisterAttached(
        "GeneratedEditingCellBehaviors",
        typeof(UserStyleBehaviorCollection),
        typeof(UserStyleBehaviorCollection),
          new PropertyMetadata((sender, e) =>
          {
            if (e.OldValue == e.NewValue) { return; }

            var value = e.NewValue as UserStyleBehaviorCollection;
            if (value == null) { return; }

            var behaviors = Interaction.GetBehaviors(sender);
            behaviors.Clear();
            foreach (var b in value.Select(x => (Behavior)x.Clone()))
            {
              behaviors.Add(b);
            }
          }));

    public static UserStyleBehaviorCollection GetGeneratedCellBehaviors(DependencyObject obj)
    {
      return (UserStyleBehaviorCollection)obj.GetValue(GeneratedCellBehaviorsProperty);
    }

    public static UserStyleBehaviorCollection GetGeneratedEditingCellBehaviors(DependencyObject obj)
    {
      return (UserStyleBehaviorCollection)obj.GetValue(GeneratedEditingCellBehaviorsProperty);
    }
    
    public static void SetGeneratedCellBehaviors(DependencyObject obj, UserStyleBehaviorCollection value)
    {
      obj.SetValue(GeneratedCellBehaviorsProperty, value);
    }

    public static void SetGeneratedEditingCellBehaviors(DependencyObject obj, UserStyleBehaviorCollection value)
    {
      obj.SetValue(GeneratedEditingCellBehaviorsProperty, value);
    }

    protected override Freezable CreateInstanceCore()
    {
      return new UserStyleBehaviorCollection();
    }
  }
}
```

このやり方に関する詳しい解説は

https://blog.okazuki.jp/entry/2016/07/19/192918

に掲載されている。

ざっくりかいつまむと、

- GeneratedCellBehaviorsPropertyプロパティを、セルが生成されたときのビヘイビアの集合として定義する。**xamlから見えるプロパティ名はGeneratedCellBehaviorsである。**
- GeneratedEditingCellBehaviorsPropertyプロパティを、セルが編集開始のときのビヘイビアの集合として定義する。**xamlから見えるプロパティ名はGeneratedEditingCellBehaviorsである。**


このまま、そして、先ほど[ビヘイビアを直接割り当てるのがなぜ難しいのか](#ビヘイビアを直接割り当てるのがなぜ難しいのか)でも触れたことだが、ここで定義したGeneratedCellBehaviorsやGeneratedEditingCellBehaviorsを直接Behaviorsに突っ込むことで汎用的に活用できるのだ。

また、もう一つやらないといけないこととして、GeneratedCellBehaviorsの方はCellStyleというのが標準でついているが、GeneratedEditingCellBehaviorsについては標準ではないので、新たにStyleクラスを追加する必要がある。

ここではそのプロパティをCellEditingStyleとして、GenerateEditingElementが実行されるときに
新たにStyleとして付与する。

```C#:UserStyleBehaviorTextColumn.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Xaml.Behaviors;

namespace CustomizedDataGrid001
{
  public class UserStyleBehaviorTextColumn : DataGridTextColumn
  {
    public Style CellEditingStyle 
    { 
      get; 
      set; 
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
      return base.GenerateElement(cell, dataItem);
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
      var textbox = (TextBox)base.GenerateEditingElement(cell, dataItem);
      textbox.Style = CellEditingStyle;

      return textbox;
    }
  }
}

```


これで準備は完了、
MainWindow.xamlを改めて書き直して

```XML:MainWindow.xaml
<Window x:Class="CustomizedDataGrid001.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:CustomizedDataGrid001"
        xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.DataContext>
    <local:MainViewModel />
  </Window.DataContext>

  <DataGrid ItemsSource="{Binding Items}" 
            AutoGenerateColumns="False">
    <DataGrid.Columns>
      <DataGridTextColumn Header="デフォルトの列"
                          Binding="{Binding DefaultItem}">
      </DataGridTextColumn>
      
      <local:CodeBehindTextColumn Header="TextColumnコードビハインドの列" 
                                  Binding="{Binding CodeBehindItem}">
      </local:CodeBehindTextColumn>

      <local:BehaviorTextColumn Header="Behaviorの列"
                                Binding="{Binding BehaviorItem}">
      </local:BehaviorTextColumn>


      <local:UserStyleBehaviorTextColumn Header="UserStyleBehaviorの列"
                               Binding="{Binding UserStyleBehaviorItem}">
        <local:UserStyleBehaviorTextColumn.CellStyle>
          <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="local:UserStyleBehaviorCollection.GeneratedCellBehaviors">
              <Setter.Value>
                <local:UserStyleBehaviorCollection>
                  <local:KeyEscapeBehavior />
                </local:UserStyleBehaviorCollection>
              </Setter.Value>
            </Setter>
          </Style>
        </local:UserStyleBehaviorTextColumn.CellStyle>
        <local:UserStyleBehaviorTextColumn.CellEditingStyle>
          <Style TargetType="{x:Type TextBox}">
            <Setter Property="local:UserStyleBehaviorCollection.GeneratedEditingCellBehaviors">
              <Setter.Value>
                <local:UserStyleBehaviorCollection>
                  <local:KeyOnlyNumericBehavior />
                </local:UserStyleBehaviorCollection>
              </Setter.Value>
            </Setter>
          </Style>
        </local:UserStyleBehaviorTextColumn.CellEditingStyle>
      </local:UserStyleBehaviorTextColumn>
    </DataGrid.Columns>
  </DataGrid>
</Window>
```

とすることで、ビヘイビアをxamlに紐づけることが出来る。

WPF公式のやり方に準じたやり方での実装が出来たことになる。


### しかし・・・

正直言って、最後のやり方のコードはxamlファイルのコード量が馬鹿長くなるデメリットがある。

StaticResourceなどを活用して、別のコードに移すなり工夫しないと見づらいが、
その一方でそのルールが不明瞭の場合、あっちこっちに定義が飛んでしまう問題も発生する。


# まとめ

1. コードビハインドを使うやり方
2. 専用のビヘイビアを使うやり方
3. 汎用のビヘイビアをプロパティを新たに定義して使うやり方

をまとめた。

私的には、多くの場合には、1. 2.のやり方の方がやりやすいので、
大規模開発でもない限り、3.を使うことはない気がする。

WPFを使うと、どうしてもxamlが肥大化しやすく、そのxamlも見づらくなりやすい傾向にあるような気がするから。