using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
public partial class AboutDialog : System.Windows.Window
{
public AboutDialog()
{
InitializeComponent();
PrintLogicalTree(0, this);
}
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
PrintVisualTree(0, this);
}
void PrintLogicalTree(int depth, object obj)
{
// 按深度打印缩进空白
Debug.WriteLine(new string(' ', depth) + obj);
// 有时候叶子节点不是DependencyObject(如string)
if (!(obj is DependencyObject)) return;
// 递归打印逻辑树的节点
foreach (object child in LogicalTreeHelper.GetChildren(
obj as DependencyObject))
{
PrintLogicalTree(depth + 1, child);
}
}
void PrintVisualTree(int depth, DependencyObject obj)
{
// 按深度打印缩进空白
Debug.WriteLine(new string(' ', depth) + obj);
// 递归打印视觉树的节点
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
}
}
}
在AboutDialog的构造方法中的InitCompoment方法之后,可以调用PrintLogicalTree遍历逻辑树上的节点。然而对于视觉树,直到AboutDialog完成一次布局之前,它都将是空的,因此要在OnContentRendered方法中调用PrintVisualTree实现遍历。
有时候在这两棵树上的导航操作可由元素自己的实例方法来完成。比如,Visual类包含三个受保护的成员方法VisualParent、VisualChordCount和GetVisualChild,它们用来检视它的视觉父元素和子元素。比如,Button、Label等控件的通用基类FrameworkElement定义了一个公共的Parent属性用来表示逻辑父元素。再比如,特定的FramewordElement子类们以不同的方式暴露了它们的逻辑子元素。如一些类暴露了Children集合,其它类(如Button和Label)则暴露了一个Content属性,用以表示这个元素只能包含一个逻辑子元素。
图2表示的树通常也被称作“元素树”,因为它同时包含了逻辑树元素和特定的视觉树元素。由此,术语“视觉树”用来描述任何仅包含了视觉元素的子树。例如,许多人会说Window的默认视觉树由一个Border、一个AdornerDecorator、两个AdornerLayers和一个ContentPresenter组成。图2中,顶级StackPannel通常不被算做ContentPresenter的视觉子元素,尽管它可以由VisualTreeHelper输出。
(WPF中的这两棵树是一个非常神奇有趣的特色。我们可以看到,现在的控件不再像以前那样需要在一个或多个函数中“重绘”了。从前,如果我们想要在VC中制作一个自定义的菜单,需要响应WM_DRAWITEM和WM_MEASUREITEM两条消息,然后根据参数在响应函数中生画,比如先画背景,然后是左侧的图标,然后是右侧的菜单文本,总之是一个烦人的工作。这也是很多人说的,用VC做界面不方便的地方。后来到了Windows Form,控件已经有了很好的编程模型,但是仍旧没有摆脱重绘的模式。想我曾经自绘了一个仿IE7的标签页控件,那叫一个累。不过如今好了,WPF中的控件是以树形的层次结构叠加而成的,这样使得修改控件的外观变得很方便。比如之前在《XAML揭秘》中的那个按钮示例,它的中心不是文本,而是一个黑色的方块,这正是因为,组成按钮的TextBlock被替换为Rectangle。使用如下XAML代码配合上面的程序代码即可看出。
<Window x:Class="AboutDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="关于" SizeToContent="WidthAndHeight"
Background="OrangeRed" Height="200">
<StackPanel>
<Button Content="OK" />
<Button>
<Button.Content>
<Rectangle Height="40" Width="40" Fill="Black" />
</Button.Content>
</Button>
</StackPanel>
</Window>)
第一个Button输出的视觉树为:
System.Windows.Controls.Button: OK
Microsoft.Windows.Themes.ButtonChrome
System.Windows.Controls.ContentPresenter
System.Windows.Controls.TextBlock
第二个Button输出的视觉树为:
System.Windows.Controls.Button
Microsoft.Windows.Themes.ButtonChrome
System.Windows.Controls.ContentPresenter
System.Windows.Shapes.Rectangle
很有趣吧!)