実際のアプリケーションで使うことは無いだろうけど、WPFを理解する過程でWPFで表示されてるものが最終的に何で構成されているのかというのを見たいことは多々ある。
ということで、そういう用途に使えるものとしてVisualTreeHelperとLogicalTreeHelperがある。
LogicalTreeHelperは、XAMLに書いたそのままを取得できるようなイメージになる。
VisualTreeHelperは、現在表示されてるものをそのまま表示するようなイメージになる。
後者のほうが勉強目的にはよさげ。
ということで、どういう風に使うのかさくっと書いてみた。
とりあえずのとっかかりなので、Windowのコード内にべたっと書いてるけど気にしない。
実際に勉強用に使いまわすときは、ある程度汎用化しませふ。
まずは、XAMLから。
<Window x:Class="WpfHack.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfHack="clr-namespace:WpfHack"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<WpfHack:Person x:Key="person" Name="太郎" Age="20" />
<DataTemplate x:Key="personTemplate" DataType="{x:Type WpfHack:Person}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Content="{StaticResource person}"
Click="PrintLogicalTree"/>
<Button Grid.Row="1"
Content="{StaticResource person}"
ContentTemplate="{StaticResource personTemplate}"
Click="PrintVisualTree"/>
</Grid>
</Window>
7行目で使ってるPersonクラスは、いつも定義してるものと同じなので割愛。
ウィンドウには、Grid内にボタンが2つあってPersonオブジェクトをコンテンツに持っている。
2つ目のボタンでは、DataTemplateをあててそれっぽい見た目にしている。
さらに、ボタンのクリックイベントでさっきいったLogicalTreeHelperとVisualTreeHelperを使ってWindow1の中身をダンプする処理が書かれている。
どんなコードが書かれているかは置いといて、とりあえず実行したときの見た目はこんな感じ。
続いて、Window1.xaml.csのコードにうつる。
LogicalTreeHelperはGetChildren(DependencyObject obj)というメソッドを持っているので戻り値をforeachでまわしてやるだけでOK。
子要素がDependencyObjectだったら再起呼び出しを行ってる。
VisualTreeHelperにはなぜかGetChildrenというメソッドがなくて、GetChildrenCount(DependencyObject obj)とGetChild(DependencyObject obj, int index)という感じのメソッドの構成になってる。
なので、サンプルではイテレータを使ってIEnumerable<object>に変換してforeachでまわしている。
説明は、あまり好きじゃないのでコード全体をさくっとのせる。
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
namespace WpfHack
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void PrintLogicalTree(object sender, RoutedEventArgs e)
{
Debug.WriteLine("PrintLogicalTree");
PrintLogicalTree(0, this);
}
// 論理ツリー?を出力する。
// DependencyObjectの場合は、子要素も再帰的に表示する
private void PrintLogicalTree(int level, DependencyObject obj)
{
PrintObject(level, obj);
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (child is DependencyObject)
{
PrintLogicalTree(level + 1, (DependencyObject)child);
}
else
{
PrintObject(level + 1, child);
}
}
}
private void PrintVisualTree(object sender, RoutedEventArgs e)
{
Debug.WriteLine("PrintVisualTree");
PrintVisualTree(0, this);
}
// VisualTreeを表示する。
// DependencyObjectの場合はVisualTree上の子要素も再帰的に出力していく
private void PrintVisualTree(int level, DependencyObject obj)
{
PrintObject(level, obj);
foreach (var child in GetVisualChildren(obj))
{
if (child is DependencyObject)
{
PrintVisualTree(level + 1, (DependencyObject)child);
}
else
{
PrintObject(level + 1, child);
}
}
}
// VisualTreeの子要素の列挙を返す
private IEnumerable<object> GetVisualChildren(DependencyObject obj)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
yield return VisualTreeHelper.GetChild(obj, i);
}
}
// ToStringの結果をインデントつきで出力
private void PrintObject(int level, object obj)
{
Debug.WriteLine(new string('\t', level) + obj);
}
}
}
これを実行すると、下のような出力が得られる。
PrintLogicalTree
WpfHack.Window1
System.Windows.Controls.Grid
System.Windows.Controls.RowDefinition
System.Windows.Controls.RowDefinition
System.Windows.Controls.Button: WpfHack.Person
WpfHack.Person
System.Windows.Controls.Button: WpfHack.Person
WpfHack.Person
PrintVisualTree
WpfHack.Window1
System.Windows.Controls.Border
System.Windows.Documents.AdornerDecorator
System.Windows.Controls.ContentPresenter
System.Windows.Controls.Grid
System.Windows.Controls.Button: WpfHack.Person
Microsoft.Windows.Themes.ButtonChrome
System.Windows.Controls.ContentPresenter
System.Windows.Controls.TextBlock
System.Windows.Controls.Button: WpfHack.Person
Microsoft.Windows.Themes.ButtonChrome
System.Windows.Controls.ContentPresenter
System.Windows.Controls.StackPanel
System.Windows.Controls.TextBlock
System.Windows.Controls.TextBlock
System.Windows.Documents.AdornerLayer
最初のほうに書いたけど、LogicalTreeHelperでの出力は、XAMLの中身と変わりない。
見て勉強になるのはVisualTreeHelperで出力したほうだと思う。もうちょい凝ってプロパティとかも出すようにすると素敵なことになるかもしれないけど今日は眠いのでお預け。
ちなみに、VisualTreeの表示を汎用化してるものはえむナウさんが過去のエントリで書かれています。
みんなはそっちを参考にしよう!