说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。
WPF中,XAML来呈现用户界面,其层次化的特性构建了用户界面需要的对象树,这棵树为逻辑树。通过XAML与过程式代码(C#)都可以实现逻辑树。逻辑树概念很重要,其与WPF的属性,事件,资源等很多方面相关联。如属性值会沿着树自动传递给子元素(后文要介绍的依赖属性),而触发的事件可以自底向上或自顶向下遍历树(后文要介绍的路由事件)。
可视树类似于逻辑树,其基本上是逻辑树的扩展,在可视树中,节点都被打散,分散到核心可视组件,这样其就提供了一些详细的可视化实现。
如逻辑树中的节点ListBox在可视化树中对应到一个Border对象,两个ScrollBar及其它元素。
只有由System.Window.Media.Visual或System.Window.Media.Visual3D派生的元素才会出现可视树中。其它元素不出现在可视树中的原因很简单 – 它们并没有呈现自己的能力。
查看可视树的方法,将XAML复制到XamlPad中,将最外层容器由Window改为Page(并删除SizeToContent属性),这样点击相应按钮就可以看XAML对应的可视树(及其中每个元素的属性),下面给出一个示例:
XAML代码:
<StackPanel> <Label FontWeight="Bold" FontSize="20" Foreground="White" Background="OrangeRed"> Show Visual Tree Label> <Label>by hystarLabel> <ListBox> <ListBoxItem>Chapter 1ListBoxItem> <ListBoxItem>Chapter 2ListBoxItem> ListBox> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <Button MinWidth="75" Margin="10">HelpButton> <Button MinWidth="75" Margin="10">OKButton> StackPanel> <StatusBar>This is a StatusBar an bottom.StatusBar> StackPanel>
将其拷贝到XamlPad中下方的
如下图:点击这个红色圆圈标记的按钮(Show/Hide visual tree),即可显示树状展示的可视化树与每个元素的属性的树状列表。
WPF中有一个核心原则就是 – 可视部分与逻辑部分分开。这两部分大致分别对应可视树与逻辑树。可视树会受到用户切换Window主题的影响,但逻辑树不会,其是静态的,动态添加删除元素也不会影响逻辑树。
使用System.Window.LogicalTreeHelper与System.Window.Media.VisualTreeHelper这两个类可以方便的遍历一段代码的逻辑树与可视树。下面是两个代码,分别实现遍历前文那段XAML代码的逻辑树与可视树:
遍历逻辑树:
C#:
public Window1() { InitializeComponent(); PrintLogicalTree(0, this); } private 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); } protected override void OnContentRendered(EventArgs e) { base.OnContentRendered(e); PrintVisualTree(0, this); } private 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)); }
以上代码,构造函数中调用的函数就实现遍历逻辑树,这也证明了前文所述逻辑树的静态性。但可视树需要一次布局后才会生成,所以遍历可视树需要在布局完成后的Content Rendered事件处理函数OnContentRendered中遍历。
另外,元素自己的实例方法也可以操作两种树。
对于可视树,Visual类的3个protected成员VisualParent,VisualChildrenCount和GetVisualChild可以用来访问一个可视节点的父节点与子节点。
对于逻辑树,所有通用控件(如Button和Label)的基类,FrameworkElement的Parent公共属性用来访问父节点。对于子节点由此元素的Children集合属性提供,有些以Content提供(说明这个元素只能有一个子元素/节点,如Button与Label – 具体原因前文有说明)。
参考:
《WPF揭秘》