自开始接触WPF开始一直对可视化树和逻辑树没有清晰地认识和区别,下面进行一次总结。学海无涯,开始吧!!!

背景

目前SDK文档中关于视觉树和逻辑树的介绍还不是很完全。
事实证明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对象。这里有个例子遍历到可视化树的根元素。

 

//遍历可视化树的根元素

DependencyObject FindVisualTreeRoot (DependencyObject initial) { DependencyObject current = initial; DependencyObject result = initial; While(current !=null) { result = current; if(current is Visual || current is Visual3D) { current = VisualTreeHelper.GetParent(current); } else { current = LogicalTreeHelper.GetParent(current); } } return result; }

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

        /// <summary>  /// 获得指定元素的所有子元素  /// </summary>  /// <typeparam name="T"></typeparam>  /// <param name="obj"></param>  /// <returns></returns>  public List<T> GetChildObjects<T>(DependencyObject obj) where T : FrameworkElement{DependencyObject child = null;List<T> childList = new List<T>();for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++){child = VisualTreeHelper.GetChild(obj, i);if (child is T){childList.Add((T)child);}childList.AddRange(GetChildObjects<T>(child));}return childList;}

该方法获得指定元素的所有子元素

逻辑树

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

void WalkDownLogicalTree(object current)
{DoSomethingWithObjectInLogicalTree(current);DependencyObject depObj = current as DependencyObject;if(depObj != null){foreach(object logicalChild in LogicalTreeHelper.GetChildren(depObj))WalkDownLogicalTree(logicalChild);}
}

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

DependencyObject GetTemplatedParen(DependencyObject depObj)
{FrameworkElement fe = depObj as FrameworkElement;FrameworkElementElement fce = depObj as FrameworkContentElement;DependencyObject result;if(fe != null)result = fe.TemplatedParent;else if(fce != null)result = fce.TemplateParent;elsereturn null;return result;
}

demo

效果图:

<Window x:Class="WpfApplication1.Window3"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  Title="Window3" Height="553.847" Width="774.231">  <DockPanel LastChildFill="True">  <Border Height="50" DockPanel.Dock="Top" BorderBrush="Blue">  <StackPanel Orientation="Horizontal">  <Button x:Name="btnShowLogicalTree" Content="编程方式查看 逻辑树"  Margin="4" BorderBrush="Blue" Height="40" Click="btnShowLogicalTree_Click"/>  <Button x:Name="btnShowVisualTree" Content="编程方式查看 可视化树"  BorderBrush="Blue" Height="40" Click="btnShowVisualTree_Click"/>  </StackPanel>  </Border>  <TextBox x:Name="txtDisplayArea" Margin="10" Background="AliceBlue" IsReadOnly="True" BorderBrush="Red"  HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Width="400"  />  <Border DockPanel.Dock="Right" Margin="10" BorderBrush="DarkGreen" BorderThickness="4" >  <StackPanel>  <Label Content="Enter Full Name of WPF Control(查看控件默认模板)" Width="340" FontWeight="demiBold"/>  <TextBox x:Name="txtFullName" Width="340" BorderBrush="Green" Background="BlanchedAlmond" Height="22"  Text="System.Windows.Controls.Button"/>  <Button x:Name="btnTemplate" Content="See Template" BorderBrush="Green" Height="40" Width="100" Margin="5"  Click="btnTemplate_Click" HorizontalAlignment="Left"/>  <Border BorderBrush="DarkGreen" BorderThickness="2" Height="260" Width="301" Margin="10" Background="LightGreen">  <StackPanel x:Name="stackTemplatePanel"/>  </Border>  </StackPanel>  </Border>  </DockPanel>
</Window>  
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;  using System.Reflection;
using System.Xml;
using System.Windows.Markup;
namespace WpfApplication1
{  /// <summary>  /// Window3.xaml 的交互逻辑  /// </summary>  public partial class Window3 : Window  {  private string dataToShow = string.Empty;  private Control ctrlToExamine = null;  public Window3()  {  InitializeComponent();  }  private void btnShowLogicalTree_Click(object sender, RoutedEventArgs e)  {  dataToShow = "";  BuildLogicalTree(0, this);  this.txtDisplayArea.Text = dataToShow;  }  //编程方式查看 逻辑树  void BuildLogicalTree(int depth,object obj)  {  dataToShow += new string(' ', depth) + obj.GetType().Name + "\n";  if (!(obj is DependencyObject))  return;  //LogicalTreeHelper.GetChildren 获取逻辑树子对象   //obj as DependencyObject  将obj转换成 依赖对象  foreach(object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))  BuildLogicalTree(depth+5,child);  }  private void btnShowVisualTree_Click(object sender, RoutedEventArgs e)  {  dataToShow = "";  BuildVisualTree(0, this);  this.txtDisplayArea.Text = dataToShow;  }  //编程方式查看 可视化树  void BuildVisualTree(int depth, DependencyObject obj)  {  dataToShow += new string(' ', depth) + obj.GetType().Name + "\n";  for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj);i++ )  {  BuildVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));  }  }  private void btnTemplate_Click(object sender, RoutedEventArgs e)  {  dataToShow = "";  ShowTemplate();  this.txtDisplayArea.Text = dataToShow;  }  //控件模板  private void ShowTemplate()  {  if (ctrlToExamine != null)  stackTemplatePanel.Children.Remove(ctrlToExamine);  try {  Assembly asm = Assembly.Load("PresentationFramework,version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35");  ctrlToExamine = (Control)asm.CreateInstance(txtFullName.Text);  ctrlToExamine.Height = 200;  ctrlToExamine.Width = 200;  ctrlToExamine.Margin = new Thickness(5);  stackTemplatePanel.Children.Add(ctrlToExamine);  //定义xml一些设置,以保持缩进  XmlWriterSettings xmlSetting = new XmlWriterSettings();  xmlSetting.Indent = true;  //创建 StringBuilder 来保存xml  StringBuilder strBuilder = new StringBuilder();  //创建基于设置的 XmlWriter  XmlWriter xWriter = XmlWriter.Create(strBuilder,xmlSetting);  //将基于ControlTemplate的XAML保存到XmlWriter对象  XamlWriter.Save(ctrlToExamine.Template,xWriter);  //文本框显示XAML  dataToShow = strBuilder.ToString();  }  catch(Exception ex) {  dataToShow = ex.Message;  }  }  }
}  

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

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

结论

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

WPF 可视化树和逻辑树相关推荐

  1. WPF,Silverlight与XAML读书笔记第六 - WPF新概念之一逻辑树与可视树

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. WPF中,XAML来呈现用户界面,其层次化的 ...

  2. 理解WPF中的视觉树和逻辑树

    理解WPF中的视觉树和逻辑树  Understanding the Visual Tree and Logical Tree in WPF 这篇文章讨论WPF中视觉树和逻辑树的细微差别.同时提供了一个 ...

  3. WPF 视觉树和逻辑树区别,以及其子节点的遍历过程。

    一.定义上区分 1.添加元素的分类,称为逻辑树.解析:用window或者是application向下定位到组成界面的基本控件元素,就是逻辑树. 2.视觉树是逻辑树的拓展版本.解析:把逻辑树上的控件元素 ...

  4. 视觉树和逻辑树的概念

    逻辑树与视觉树属于WPF的基本概念,学过WPF或者Silverlight的朋友一定会对其有所耳闻,这篇文章将来探讨逻辑树与视觉树的特质以及两者的区别 WPF Inspector工具介绍 WPF Ins ...

  5. WPF遍历视觉树与逻辑树

    xaml代码: <Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsof ...

  6. WPF 逻辑树和可视化树

    逻辑树: 逻辑树是在 WPF 框架级别定义,它主要是为了描述一个"界面对象"的构建过程,相当于对象族谱:抽象的名字和相对的位置关系.它是由 WPF 基元素是 FrameworkEl ...

  7. WPF-10 逻辑树和可视化树

    我们在WPF-03 资源之Resources结尾中介绍逻辑树和可视化树的基本概念,我们这节来介绍这两棵树 逻辑树(Logical Tree) 逻辑树是由每个控件的节点组成,本质上就是XAML文件中的U ...

  8. 逻辑树与可视树[转]

    本章内容 l    逻辑树与可视树 l    依赖属性 l    路由事件 我们即将完成本书的第一部分,在开始一些真正有趣的话题之前,回顾一下之前介绍的一些主要概念是很有用的,这也是.NET程序员们所 ...

  9. 麦肯锡著名的三大结构化工具:金字塔原理、MECE和逻辑树

    今天给大家重点介绍一下,麦肯锡著名的三大结构化工具:金字塔原理.MECE原理和逻辑树: 关于金字塔原理 对于金字塔原理这种逻辑思考工具,要想掌握,是需要花时间进行反复练习的,包括在培训的现场,是需要进 ...

最新文章

  1. 如果足够准的话,还有比体脂秤更好的身体数据采集器吗?
  2. Doug Cutting—搜索之父
  3. Linux底层网络编程--ARP,PING等
  4. SVN之如何解决从一个svn路径下载项目快速提交到其它svn路径
  5. Hashmap扩容时出现循环链表(jdk1.8把头插法换成了尾插法的原因)
  6. spark生态及各个功能
  7. MATLAB函数 zp2tf详解
  8. 最新资源《机器学习图像算法与建模优化》免费开放下载!(含代码数据)
  9. 九宫格拼图游戏的总结
  10. Mac安装Linux虚拟机
  11. 20 个短小精悍的 pandas 骚操作
  12. 外包公司究竟有没有前途?讲讲我在外包公司的真实经历
  13. linux usb检测工具,Linux下USB设备检测全教程
  14. 定义复数类Complex,重载运算符“+”,使之用于复数的加法运算
  15. ESP32超详细学习记录:wifi连接最基础方法
  16. 利用scrapy-splash爬取JS生成的动态页面
  17. [TYVJ] P1423 GF和猫咪的玩具
  18. 致敬中国杰出量化女性
  19. matplotlib可视化番外篇bar()--带误差棒的柱状图
  20. 欧姆龙NJ/NX项目实战步骤

热门文章

  1. java.lang.String_不兼容的类型:java.lang.String无法转换为Str...
  2. 4.9. 触发器(Trigger)
  3. GBase 8c亮相国内首款金融数据库性能测试工具开源发布会
  4. IntelliJ IDEA 之 设置项目编码
  5. 村田【元宇宙】要火,但不能“起火”—— 电池:半固态凝胶软包电池篇
  6. Python笔记 | 角谷猜想
  7. 逐点插入法-delaunay三角剖分
  8. 相关性系数及其python实现
  9. 21计算机考研国家线,来了!21考研国家线公布!附详细解读!
  10. Greenplum 分布键 distribute hash分布和随机分布