ページ

2009年6月19日金曜日

[Silverlight] ListBox や ComboBox の各行の見た目にアクセスする

たとえば、ボタンをクリックされたら ListBox の 2行目の背景色を変えたい、なんてことがあったとします。
ListBox のそれぞれの行はたぶん Border とか Rectangle とか TextBlock とかを組み合わせて描かれてるんだと思いますが、そいつらにアクセスして背景色を変えたいというわけです。

WPF 編はこちら → 「[WPF] ListBox や ComboBox の各行の見た目にアクセスする

■ Silverlight 2、3 の場合
WPF と同じように VisualTreeHelper を使って ListBox の頭からビジュアルツリーをたどっていけば各行のビジュアルにもたどり着くことができます。
具体的には、ビジュアルツリーをたどっていくと行数分だけ ListBoxItem が出てきます。
この ListBoxItem の下がそれぞれの行のビジュアルです。

Silverlight でも WPF と同じように ItemContainerGenerator が使えると便利なんですが Silverlight には ItemContainerGenerator がありません。
(ItemContainerGenerator が何か知らないって人は WPF 編 を見てください)
けど、実は Silverlight Toolkit に ItemContainerGenerator クラスがそのままの名前で含まれています。
なので、こいつを使えば、、、って、実は Silverlight Toolkit に含まれている ItemContainerGenerator クラスはコンストラクタが internal になっていて外部から使えません orz
いけずぅ

というわけで、以下、自分で実装する方法です。
Silverlight Toolkit に含まれているものを参考にして必要最小限のところだけ実装すると以下のようになります。

public class ItemContainerGenerator
{
private List<KeyValuePair<DependencyObject, object>> container = new List<KeyValuePair<DependencyObject, object>>();

public ItemContainerGenerator() { }
public void PrepareContainerForItemOverride(DependencyObject element, object item) { this.container.Add(new KeyValuePair<DependencyObject, object>(element, item)); }
public void ClearContainerForItemOverride(DependencyObject element, object item) { foreach (var x in this.container) { if (x.Key == element) { this.container.Remove(x); return; } } }
public DependencyObject ContainerFromIndex(int index) { return this.container[index].Key; }
public DependencyObject ContainerFromItem(object item) { foreach (var x in this.container) { if (x.Value == item) { return x.Key; } } return null; } }

次に ListBox を継承して ItemContainerGenerator 対応版にしてやります。
ここでは MyListBox という名前にしました。

public class MyListBox : ListBox
{
private ItemContainerGenerator itemContainerGenerator = new ItemContainerGenerator();

public ItemContainerGenerator ItemContainerGenerator { get { return this.itemContainerGenerator; } }
public MyListBox() { }
protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { this.itemContainerGenerator.PrepareContainerForItemOverride(element, item); base.PrepareContainerForItemOverride(element, item); }
protected override void ClearContainerForItemOverride(DependencyObject element, object item) { this.itemContainerGenerator.ClearContainerForItemOverride(element, item); base.ClearContainerForItemOverride(element, item); } }

XAML を書き変えて ListBox の代わりに自分の MyListBox を使うようにしてやります。

<UserControl x:Class="ItemContainerSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ItemContainerSample"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<my:MyListBox x:Name="listBox1">
<ListBoxItem Content="Item 1" />
<ListBoxItem Content="Item 2" />
<ListBoxItem Content="Item 3" />
<ListBoxItem Content="Item 4" />
<ListBoxItem Content="Item 5" />
</my:MyListBox>
<Button x:Name="button1" Click="button1_Click" Content="Button" />
</StackPanel>
</Grid>
</UserControl>

これで WPF と同じように ItemContainerGenerator を使って項目コンテナを取得することができるようになりました。

private void button1_Click(object sender, RoutedEventArgs e)
{
var item = this.listBox1.ItemContainerGenerator.ContainerFromIndex(2);
var v = VisualTreeHelper.GetChild(item, 0);
if (v is Grid)
{
((Grid)v).Background = new SolidColorBrush(Colors.Red);
}
}

ListBox の項目コンテナが ListBoxItem だというのは WPF と同じです。
ただ、この ListBoxItem の最初の子ビジュアルは WPF と違い Grid になっていました。
また、いくつかビジュアルツリーをたどっていくと ContentPresenter が出てきて、データテンプレートを使っている場合はそいつの子がデータテンプレートの内容になっているというのは WPF と同じです。

上記のコードから明らかなように、ItemContainerGenerator 対応版にするためにはそのコントロールが PrepareContainerForItemOverride() メソッド、ClearContainerForItemOverride() メソッドをサポートしている必要があります。
これらのメソッドは ItemsControl クラスのものです。
ですから、WPF と同じように ItemsControl から継承している ComboBox なども ItemContainerGenerator 対応版にすることができると思います。
また、Silverlight Toolkit に含まれている TreeView などは始めから ItemContainerGenerator に対応していたりします。

内容的には上に書いたものと同じですが、VisualStudio 2008 のソリューションを↓に置いときました。
Silverlight 3 beta 1 のプロジェクトですが、ソースコード自体は Silverlight 2 でも問題無いはずです。
ItemContainerSample.ZIP-download

ところで、Silverlight には DataGrid がありますが、あれは ItemsControl の子じゃないし、このアプローチは無理なんですね。
というわけで、DataGrid についてはこちら → 「[Silverlight] DataGrid のセルの見た目にアクセスする


0 件のコメント:

コメントを投稿