DataGridコントロールなどのセル内部にDataTemplateでコントロールを配置した場合、コントロールにNameを付けていてもプログラム側から直接アクセスすることはできません。複数行ある場合にどの行のコントロールを表しているのか分からないので当たり前と言えば当たり前の仕様なのですが、それでもアクセスしたいことってありますよね!
いろいろと悩んだのですが、「VisualTreeから引っ張ってくるしかない」という結論に至りました。
(他の方法があれば是非ご教授ください・・・^^;)
別に何のコントロールでもよいのですが、とりあえずDataGridで話を進めていきます。まず、元となるDataTemplate部分。
XAML
3 | < DataGridTemplateColumn > |
4 | < DataGridTemplateColumn.CellTemplate > |
6 | < TextBox x:Name = "tbxHoge" /> |
8 | < DataGridTemplateColumn.CellTemplate > |
9 | < DataGridTemplateColumn > |
上記のTextBoxにアクセスしたいからといって、プログラムに”tbxHoge”と書いてもコンパイラに怒られてしまいます。
面倒ですが、地道にVisualTreeを辿っていきましょう。
VisualTreeから対象要素を取り出す
VisualTreeを辿って行く方法ですが、今回はVisualTreeHelperクラスのGetChild()を使用します。名前からも想像できるかと思いますが、子要素を取得するためのメソッドです。
任意のコントロールを取得できるようにジェネリックメソッドとして実装しておきます。
ソース
1 | private T GetElement<T>(DependencyObject reference) where T : FrameworkElement |
11 | DependencyObject elem = null ; |
12 | int count = VisualTreeHelper.GetChildrenCount(reference); |
13 | for ( int idx = 0; idx < count; idx++) |
15 | elem = GetChildElement<T>(reference, idx); |
27 | private T GetChildElement<T>(DependencyObject reference, int childIdx) where T : FrameworkElement |
30 | var child = VisualTreeHelper.GetChild(reference, childIdx); |
42 | DependencyObject elem = reference; |
43 | int count = VisualTreeHelper.GetChildrenCount(child); |
44 | for ( int idx = 0; idx < count; idx++) |
46 | elem = GetChildElement<T>(child, idx); |
同じようなメソッドを2つも定義するなと突っ込まれそうですが、呼び出し元のコードをシンプルにするためにこのような形にしています。
対象コントロールが子要素に見つからなかった場合は孫、孫に見つからなかったら更にその子・・・といった具合に、どんどんと子を探しに行くように再帰関数として実装しています。
さて、準備が整ったのでさっそくTextBoxにアクセスしてみましょう。
GetElement()の引数にはコンテナオブジェクトを指定するのですが、DataGridでコンテナを引っ張ってくる方法を考えなければなりません。実はこれまた面倒・・・ここにきてDataGridを例にあげたことを後悔・・・(苦笑)
DataGridのコンテナ取得
DataGridのコンテナに相当するものはDataGridCellなのですが、残念ながらさっくりと取得できるようなメソッドは(たぶん)用意されていません。
ではどのように取得するのかですが、次のステップを踏まなければなりません。
- LoadingRowイベントハンドラでDataGridRowデータを取得
- ColumnsおよびDataGridRowデータからDataGridCellオブジェクトを取得
LoadingRowイベントハンドラでDataGridRowデータを取得
DataGridCell取得時にDataGridRowオブジェクトが必要となります。DataGridRowオブジェクトはLoadingRowイベントで取得できるので、このタイミングで適当な領域に格納しておきます。Dictionaryを使えばインデックスとDataGridRowオブジェクトの関係を分かりやすく管理できます。
ソース
1 | private Dictionary< int , DataGridRow> rowContainer = new Dictionary< int , DataGridRow>(); |
3 | void DataGrid_LoadingRow( object sender, DataGridRowEventArgs e) |
5 | rowContainer[e.Row.GetIndex()] = e.Row; |
ColumnsおよびDataGridRowデータからDataGridCellオブジェクトを取得
DataGridCellはDataGridColumnクラスのGetCellContent()により取得することができます。そして、このメソッドの引数としてDataGridRowオブジェクトが必要となってくるわけです。
row行、2列目のセル取得の場合は次のようになります。
ソース
1 | var elem = dataGrid.Columns[2].GetCellContent(rowContainer[row]); |
これでDataTemplateの内のコントロールにアクセスする準備が全て整いました。
DataTemplate内コントロール取得
上記で作った関数を呼び出すことで任意コントロールを簡単に取得できます。
ソース
4 | TextBox tbTmp = GetTextBox(1); |
9 | private TextBox GetTextBox( int row) |
12 | var elem = dataGrid.Columns[0].GetCellContent(rowContainer[row]); |
20 | TextBox tb = GetElement<TextBox>(elem); |
お気づきの方もいるかも知れませんが、今回作ったメソッドには1つ問題があります。例えば、DataTemplate内に複数のTextBoxがあるようなケースでは最初の1つのTextBoxしか取り出すことが出来ません。
まぁ、必要に応じていろいろ弄ってみてください。
関連があると思われる記事:
[`google_buzz` not found]
[`yahoo` not found]
[`livedoor` not found]
[`friendfeed` not found]
[`grow` not found]