zoukankan      html  css  js  c++  java
  • 理解WPF中的视觉树和逻辑树

    轉載地址:http://blog.csdn.net/changtianshuiyue/article/details/26981797

    理解WPF中的视觉树和逻辑树  Understanding the Visual Tree and Logical Tree in WPF
    这篇文章讨论WPF中视觉树和逻辑树的细微差别。同时提供了一个小程序供读者稍后分析。如果你已经对着两个概念完全不熟悉,我建议你先读SDK文档中的这篇文章“URL”。

    背景

    目前SDK文档中关于视觉树和逻辑树的介绍还不是很完全。从我一开始接触WPF, 我就对这两个概念的区别很模糊。我认为这两个树都包含可视化元素和控件,对吧? 错!我认为Window/Page/Control等有且仅有一棵逻辑树,对吧?还是错!我知道我在干嘛吗?完全不知道!
    事实证明WPF中的元素树相当复杂而且要求WPF类库中很底层的知识来理解这些树。怎样用通用的方式遍历元素树;你觉得在什么地方无法得知元素成分;这些并不像看起来那么简单。不幸的是,WPF没有暴露接口来简化对元素树的遍历。
    现在你可能会为什么遍历元素树会这么麻烦。 答案分成几个部分,在下面讨论。

    可视化树

    可视化树代表你界面上所有的渲染在屏幕上的元素。可视化树用于渲染,事件路由,定位资源(如果该元素没有逻辑父元素)等等等等。向上或者乡下遍历可视化树可以简单的使用VisualTreeHelper和简单的递归方法。

    然后,还是有个小别扭让它变得复杂。任何承继自ContentElement的东西都可以在UI上显示,但其实并不在可视化树中。WPF会假定这些元素也在可视化树中,来保持事件路由的一致性,但这只是个幻觉。VisualTreeHelper对ContentElement对象不起作用,因为ContentElement不是继承自Visual或者Visual3D. 下面是Framework中所有承继自ContentElement的类从Reflector中可以看到:


    这个文档介绍了为什么ContentElement并不真正存在可视化树中。
    内容元素(继承自ContentElement的类)不是可视化树的一部分;他们不是继承自Visual而且没有可视化表示。为了显示在UI上,ContentElement必须寄宿在一个Visual主体上,通常是一个FrameworkElement。你可以认为主体类似于一个可以选择如何展示该ContentElement的浏览器。一旦一个Content被显示主体捕获,这个Content就可以加入到一个特定的和可视化树相关的树处理过程中。一般说来,FrameworkElement类都会包含一段代码用来把ContentElement添加到事件路由中去,通过这个content逻辑树的某个子节点,尽管这个content并不是可视化树的一部分。这很必要,因为content也需要找到路由事件的源头。
    这意味着你永远没办法仅仅使用VisualTreeHelper来遍历可视化树。如果你把一个ContentElement传递给VisualTreeHelper的GetParent或者GetChild方法,会抛出一个异常。因为ContentElement不是Visual或者Visual3D的子类,你只能沿着逻辑树查找ContentElement的父元素,直到找到一个Visual对象。这里有个例子遍历到可视化树的根元素。

    [csharp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. DependencyObject FindVisualTreeRoot (DependencyObject initial)  
    2. {  
    3.     DependencyObject current = initial;  
    4.     DependencyObject result = initial;  
    5.   
    6.   
    7.     While(current !=null)  
    8.     {  
    9.         result = current;  
    10.         if(current is Visual || current is Visual3D)  
    11.         {  
    12.             current = VisualTreeHelper.GetParent(current);  
    13.         }  
    14.         else  
    15.         {  
    16.             current = LogicalTreeHelper.GetParent(current);  
    17.         }  
    18.     }  
    19.     return result;  
    20. }  


    这段代码在必要的时候沿着逻辑树上溯,如else子句所示。这很有用,假如说用户点击一个在TextBlock中的Run元素你需要从Run元素开始上溯可视化树。由于Run类继承自ContentElement, 所以它不真在可视化树中。所以我们需要走出逻辑树直到我们找到了那个包好Run元素的TextBlock。之后我们就可以回到可视化树上来了,因为TextBlock不是ContentElement的子类。

    逻辑树

    逻辑树表示UI的核心结构。和XAML文件中定义的元素近乎相等,排除掉内部生成的那些用来帮助渲染的可视化元素。WPF用逻辑树来决定依赖属性,值继承,资源解决方案等。
    逻辑树用起来不像可视化树那么简单。对于新手来说,逻辑树可以包含类型对象,这一点和可视化树不同,可视化树只包含Dependancy子类的实例。遍历逻辑树时,要记住逻辑树的叶子可以是任何类型。由于LogicTreeHelper只对DependencyObject有效,遍历逻辑树时需要非常小心,最好做类型检查。看个例子:

    [csharp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void WalkDownLogicalTree(object current)  
    2. {  
    3.     DoSomethingWithObjectInLogicalTree(current);  
    4.   
    5.   
    6.     DependencyObject depObj = current as DependencyObject;  
    7.   
    8.   
    9.     if(depObj != null)  
    10.     {  
    11.         foreach(object logicalChild in LogicalTreeHelper.GetChildren(depObj))  
    12.             WalkDownLogicalTree(logicalChild);  
    13.     }  
    14. }  

    一个给定的Window/Page/Control会有一棵视觉树,但是可以有几个逻辑树。这些逻辑树互相不相连。可以仅仅使用LogicalTreeHelper来在几棵逻辑树之间遍历。在这篇文章中,我会把顶层控件的逻辑树称作主逻辑树,在他里面的其他逻辑树称作逻辑岛。逻辑岛实际上就是普通的逻辑树但是“岛”可以帮助说明它们和主逻辑树并不相连。
    这种无关性可以归结于一个概念:模板。
    控件和数据对象本身并没有可见的外观。相反,它们依赖模板来决定怎样进行渲染。一个模板就像一个“拼图块”可以扩展开来以便展示正真的用来渲染的可视元素。这些元素是可扩展模板的一部分,称之为“模板元素”。这些元素有自己的逻辑树,和生成这些元素的对象所拥有的逻辑树不相连。这些小的逻辑树就是我说的逻辑岛。
    你只能写额外的代码来在不同的逻辑树或者逻辑岛之间进行切换。遍历逻辑树时,为了连接这些岛,需要使用类似FrameworkElement.TemplateParent,FrameworkContentElement.TemplateParent这些属性来返回持有这些模板的元素,这样就把逻辑岛包含进来了。以下是找到任意元素
    的TemplateParent的一个方法:

    [csharp] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. DependencyObject GetTemplatedParen(DependencyObject depObj)  
    2. {  
    3.     FrameworkElement fe = depObj as FrameworkElement;  
    4.     FrameworkElementElement fce = depObj as FrameworkContentElement;  
    5.   
    6.   
    7.     DependencyObject result;  
    8.   
    9.   
    10.     if(fe != null)  
    11.         result = fe.TemplatedParent;  
    12.   
    13.   
    14.     else if(fce != null)  
    15.         result = fce.TemplateParent;  
    16.       
    17.     else  
    18.         return null;  
    19.   
    20.   
    21.     return result;  
    22. }  


    下溯逻辑树并且在逻辑树之间切换更难因为TemplatedChild属性并不存在。你得检查逻辑树叶子元素的可视化子元素,然后看看这些子元素是不是别的逻辑树的成员。代码留给大家自己练习。

    研究工具

    这篇文章提供一个小的控制台程序来帮助你体验各种元素树。它会打开一个WPF窗口,然后在控制台输出任何你点击的元素的逻辑树和可视化树。怎样用它在WPF窗口中有说明,先来看看它都提供了些什么功能。

    打开工具:

    然后最大化控制台,把WPF窗体移动到控制台上面,在窗口中间的按钮上按Ctrl+鼠标左键(不要按到字母上),工具会导出点击到的ButtonChrome元素的逻辑树, 如下:

    逻辑树现在变的非常不一样。它比之前的那棵树要小很多,根是ButtonChrome而不是Button, 叶子是一个ContentPresenter而不是一个string.之所以不同,是因为我们在看的是一个逻辑岛。这个逻辑岛是Button内容的模板元素所用的逻辑树。

    如果我们按住Ctrl+Left, 点击ButtonChrome, 控制台窗口就会显示整棵可视化树, 如下:


    很明显可视化树比之前的逻辑树大很多。 值得注意的是,可视化树包括了所有的可视化元素,包括刚刚测试过的按钮,它不关心这些元素是否来自于模板。所有显示的元素都可以在可视化树中找到。 这样就更好理解了。

    结论

    第一次了解元素树的时候,可能会觉得很好理解。深入研究会发现其实它们也没那么简单。对于大多数WPF编程,知道这些细节都没有什么害处,而对于一些高级场景,这些知识就变得很必须。希望这篇文章对于理解这些晦涩的细节能有所裨益。

  • 相关阅读:
    RHEL7: How to configure a rc-local service
    安装 jemalloc for mysql
    aws rhel 7 安装GUI ,配置VNC
    官方推荐的MySQL参数设置值
    Linux HugePages及MySQL 大页配置
    Linux Transparent Huge Pages 对 Oracle 的影响
    Linux的Transparent Hugepage与关闭方法
    Linux HugePages 配置与 Oracle 性能关系说明
    How To Change Log Rate Limiting In Linux
    MySQL 8.0窗口函数
  • 原文地址:https://www.cnblogs.com/CoreXin/p/5072527.html
Copyright © 2011-2022 走看看