WPFのDataGridに対してDataTableをバインドする際、ヘッダに特定の文字が含まれていると正しく表示が行われません。
これは、/.[]() が予約文字として扱われたり、アンダースコア ‘_’ がアクセスキーとして扱われることに起因します。
今回は、これを回避するための方法と、それを簡単に実装するためのメソッドを紹介します。
同様の現象に悩まれている方は、コピペしていただければ解決できますので、是非お試しください。
発生する現象
DataTableのカラム名(ColumnName)に /.[]() が含まれている場合、DataGridにバインドした瞬間、その列は値が表示されなくなります。

DataTableのカラム名に []() が含まれていて、[] 又は () 同士が対になっていない場合、DataGridにバインドした瞬間にエラー(例外)が発生します。
例えば “住所[]” なら列の値が表示されないだけですが、”住所[” とか ”住所[[1]” とかだとエラーになります。

DataTableのカラム名にアンダースコア ‘_’ が含まれている場合、DataGridのHeaderにはアンダースコアが削除された状態で表示されます。

いずれの場合もWPFとXAMLの仕様によるものなので、これを回避する必要があります。
_/.[]()を解決する方法
原因は、バインド時にPropertyNameというプロパティにDataTableのカラム名(ColumnName)がセットされる際、予約文字やアクセスキーとして解釈されてしまうからなので、PropertyNameに予約文字が含まれないようにすれば解決できます。
具体的には、DataTableのカラム名を Caption プロパティに退避し、代わりにカラム名に連番の文字列を設定します。
こうすることで、PropertyNameには連番の文字列がセットされるため、値が表示されなくなったり、エラーになることが回避できます。
そして、バインド時のカラム自動生成時に発生する AutoGeneratingColumn イベントの中で、Captionに対ししていた本来のカラム名を、DataGridのHeaderにセットしてあげます。
アンダースコアについては、’_’ を ‘__’ という具合に2個連ねることで回避でるため、このタイミングで Replace(“_”,”__”) という風に文字列置換を行います。
ただし、この方法は元のDataTableのカラム名を書き換えてしまいますので、以降はそれを意識しておく必要はあります。
自作関数のソースコード
自作関数は次の3つを用意しています。
- AutoGeneratingColumn のイベントハンドラを設定する EnableHeaderFromCaptionme()
- カラム名(DataColumn)をCaptionに退避する StoreColumnNameToCaption()
- Captionからカラム名を復元する RestoreColumnNameToCaption()
EnableHeaderFromCaptionme()の引数はDataGridのインスタンス、StoreColumnNameToCaption()とRestoreColumnNameToCaption() はDataTableのインスタンスを渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
/// <summary> /// カラム生成時、Captionをヘッダに代入 /// </summary> /// <param name="dataGrid"></param> private void EnableHeaderFromCaption(DataGrid dataGrid) { dataGrid.AutoGeneratingColumn += (s, e) => { var column = (DataGridBoundColumn)e.Column; var dv = (DataView)((DataGrid)s).ItemsSource; var index = dv.Table.Columns.IndexOf(e.PropertyName); column.Header = dv.Table.Columns[index].Caption.Replace("_","__"); }; } /// <summary> /// カラム名をCaptionに退避し、ColumnNameに連番のカラム名をセット /// </summary> /// <param name="dt"></param> /// <returns></returns> private DataTable StoreColumnNameToCaption(DataTable dt) { Enumerable.Range(0, dt.Columns.Count).Select(i => { dt.Columns[i].Caption = dt.Columns[i].ColumnName; dt.Columns[i].ColumnName = "Columns" + i.ToString(); return i; }).ToArray(); return dt; } /// <summary> /// カラム名をCaptionからColumNameに復元 /// </summary> /// <param name="dt"></param> /// <returns></returns> private DataTable RestoreColumnNameToCaption(DataTable dt) { Enumerable.Range(0, dt.Columns.Count).Select(i => { dt.Columns[i].ColumnName = dt.Columns[i].Caption; return i; }).ToArray(); return dt; } |
自作関数の使い方
以下の解説は、DataGridのインスタンスに “uxDataGrid” という名前を付けていることを前提にしています。
まず最初に、EnableHeaderFromCaptionme() は、コンストラクタや Loaded イベントハンドラに記述します。
1 2 3 4 5 6 7 8 |
public DataGridControl() { InitializeComponent(); EnableHeaderFromCaption(uxDataGrid); } |
次に、DataGridのDataContextに対してDataTableを代入する際、StoreColumnNameToCaption() を読んでください。
1 |
uxDataGrid.DataContext = StoreColumnNameToCaption(datatable); |
画面に表示するだけならこれでOKですが、例えば画面で編集したDataTableをCSVなどに保存する場合、本来のカラム名が出力されません。
そこで、DataTableのカラム名を本来のカラム名に戻さなければなりません。
この時にRestoreColumnNameToCaption() を使って、DataTableのカラム名を復元します。
1 |
RestoreColumnNameToCaption(datatable); |
この時点で、DataTableのカラム名には _/.[]() が含まれてしまうので、再び連番の文字列にしておかないと、表示がおかしくなってしまいます。
そこで、StoreColumnNameToCaption() を使ってDataTableのカラム名をCaptionに退避するとともに、カラム名を連番の文字列にしてしまいます。
1 |
StoreColumnNameToCaption(datatable) |
例えば、 CsvUtil().Write() というCSV出力用のメソッドを呼びだすことを例にすると次のようになります。
1 2 3 |
RestoreColumnNameToCaption(DataSource); new CsvUtil().Write(DataSource, filename, true); StoreColumnNameToCaption(DataSource); |
まとめ
今回はDataGridにDataTableをバインドし、カラム名を自動生成させた場合、予約語がカラム名に含まれていると不具合(値が表示されない、エラーになる、アンダースコアが消える)が起きるので、その回避方法と、回避方法が簡単にできる関数を紹介しました。
カラム名が固定の場合はあまり関係ありませんが、任意のCSVを読み込んでDataGridに表示する場合、今回の不具合に遭遇する可能性があるので、その場合はこの回避方法をご検討ください。