ページ

2009年6月19日金曜日

[Silverlight] DataGrid のセルの見た目にアクセスする

前 2つの記事
  「[WPF] ListBox や ComboBox の各行の見た目にアクセスする
  「[Silverlight] ListBox や ComboBox の各行の見た目にアクセスする
に関連しますが、Silverlight の DataGrid では ItemContainerGenerator のようなアプローチが使えません。
というのも、Silverlight の DataGrid は Control の子になっていて ItemContainerGenerator に必要なメソッドがないからです。
しかし、ちょっとした工夫で同じようなことができますので、それを紹介してみます。

さっそくですが必要となるコードを載せちゃいます。
そんなに難しい話では無くて、必要なのは以下のコードだけです。

public partial class MainPage : UserControl
{
private Dictionary<int, DataGridRow> rowContainer = new Dictionary<int, DataGridRow>();

public MainPage() { InitializeComponent();
this.dataGrid1.LoadingRow += dataGrid1_LoadingRow; }
void dataGrid1_LoadingRow(object sender, DataGridRowEventArgs e) { this.rowContainer[e.Row.GetIndex()] = e.Row; }
private FrameworkElement GetDataGridCell(int columnIndex, int rowIndex) { var e = this.dataGrid1.Columns[columnIndex].GetCellContent(this.rowContainer[rowIndex]); while (true) { if (e == null) { return null; } if (e is DataGridCell) { return e; } e = e.Parent as FrameworkElement; } }
private void button1_Click(object sender, RoutedEventArgs e) { var cell = GetDataGridCell(1, 2); var v = VisualTreeHelper.GetChild(cell, 0); if (v is Grid) { ((Grid)v).Background = new SolidColorBrush(Colors.Red); } } }

上記は MainPage に dataGrid1 という名前の DataGrid が置いてある場合のコードです。
まず、DataGrid の LoadingRow イベントを受けるようにします。
このイベントは DataGridRow が作られるたびに呼び出されます。
LoadingRow イベントでは作られた DataGridRow を Dictionary に保存しておきます。(キーは行のインデックス、値は DataGridRow)

GetDataGridCell() メソッドがこのコードのキモです。(キモって言うほどたいしたことしてるわけじゃないですが)
DataGridColumn.GetCellContent() メソッドを使うとセルの中のコンテントを取得することができます。
DataGridColumn はカラム位置のインデックスで簡単に取得できます。
行の指定は DataGridRow を渡すことによって行います。
これに必要なので LoadingRow イベントで DataGridRow を取っておいたわけです。
さて、この DataGridColumn.GetCellContent() メソッドですが、これで返ってくるのはセルの中のコンテントそのもの、たとえば、TextBlock などです。
これだとちょっと使いにくいので親をさかのぼって DataGridCell を探し、それを返します。

DataGridCell ってのが ListBox の項目コンテナ (ListBoxItem) に相当すると思ってもらえばいいかと思います。
ListBox では、ItemContainerGenerator が無かったとはいえ PrepareContainerForItemOverride() メソッドなど項目コンテナを取得するすべは用意されていました。
DataGrid にはそういったものが無いためコンテントを取得し、そっから親をたどることによって項目コンテナに相当するものを取得しているわけです。

DataGridCell が取得できたら後は ListBox と同じようにビジュアルにアクセスできます。
上記のボタンクリックイベントでは (桁インデックス:1、行インデックス:2) のセルを取得し、その子ビジュアルを取得して背景色を変更しています。

ここでちょっと注意。
Silverlight の DataGrid は必要となったときに初めて DataGridRow が作られるようになっています。
初期化時にすべての行が作られるわけではありません。
隠れていて表示されていない行の DataGridRow はスクロールして表示されたときに初めて作られたりします。
ですので、データはあっても DataGridRow はまだ無いという状況がありえます。
上記のコードはそういったチェックをしていないため、まだ作られていない行にアクセスすると死にます。
本当ならちゃんと DataGridRow が生成済みかどうかをチェックしてやるようにしてください。

ちょっと調子に乗って DataGrid のセルの中に DataGrid が入っているというのを試してみましたw
XAML はこんな感じ。

<data:DataGrid x:Name="dataGrid1" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Grid">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Padding="4,4,4,4" Text="グリッド"/>
<data:DataGrid AutoGenerateColumns="True"/>
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>

ボタンが押された時のコードはこんな感じ。

private DataGrid FindInnerDataGrid(DependencyObject o)
{
if (o is DataGrid)
{
return o as DataGrid;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); ++i)
{
var x = FindInnerDataGrid(VisualTreeHelper.GetChild(o, i));
if (x != null)
{
return x;
}
}
return null;
}

private void button2_Click(object sender, RoutedEventArgs e) { var cell = GetDataGridCell(3, 2); var grid = FindInnerDataGrid(cell); grid.ItemsSource = new[] { "あああ", "いいい", "ううう" }; }

GetDataGridCell() メソッドを使って DataGridCell を取得するのは先のコードと同じです。
DataGridCell を取得したら、そこからビジュアルツリーをたどって最初に見つけた DataGrid を目的のものとしています。
セルの中に DataGrid が入っているなんて変態的な状況は普通おこらないはずなのでとりあえずこれで動きます。
もうちょっとまともにやるなら、DataGridCell から最初に ContentPresenter を探して、さらにその子供から DataGrid を取得するようにした方がいいかもしれません。
まぁ、こうやって DataGrid が取得できれば後はどうとでもできます。
試しに ItemsSource を設定してみたらちゃんとデータバインドされて表示が更新されました。

上記に上げたコードほとんどそのまんまですが、Visual Studio 2008 のソリューションを上げておきました。
Silverlight 3 beta 1 のプロジェクトですがソースコード自体は Silverlight 2 でも問題無いはずです。
SilverlightDataGridSample.ZIP-download


0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。