`

理解WPF中的视觉树和逻辑树(转载)

 
阅读更多

 

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

 

理解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会假定这些元素也在可视化树中,来保持事件路由的一致性,但这只是个幻觉。VisualTreeHelperContentElement对象不起作用,因为ContentElement不是继承自Visual或者Visual3D. 下面是Framework中所有承继自ContentElement的类从Reflector中可以看到:

 


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

逻辑树

逻辑树表示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;
	
	else
		return null;


	return result;
}

 

 

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

研究工具

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

打开工具:

 

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

 

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

 

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

 


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

结论

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

 

分享到:
评论

相关推荐

    wpf datagrid实现树结构.rar

    总的来说,这个项目演示了如何通过自定义模板和数据绑定在WPF的DataGrid中实现树形结构,对于理解和应用此类功能非常有帮助。开发者需要了解MVVM设计模式、数据绑定、HierarchicalDataTemplate、以及如何处理UI事件...

    了解WPF中的可视树和逻辑树

    可视树是WPF中表示UI元素物理布局和渲染层次的结构。它由直接或间接继承自`System.Windows.Media.Visual` 类的所有对象组成,包括`UIElement` 和 `FrameworkElement` 的实例。可视树关注元素的视觉呈现,如绘制、...

    wpf_combobox_tree下拉树形控件

    1. **控件模板**:在WPF中,我们可以使用ControlTemplate来定义控件的外观。对于树形ComboBox,我们需要创建一个模板,包含一个TextBox(显示当前选择)和一个Popup(作为下拉树形列表)。这个模板需要使用...

    WPF入门实例——WPF目录树实例

    通过这个实例,初学者可以了解到如何在WPF中使用`TreeView`控件来构建交互式的目录树。不仅可以学习到数据绑定、数据模板以及如何处理文件系统的操作,还可以熟悉XAML和C#代码之间的交互。这是一个很好的起点,对于...

    WPF TreeGrid树形表格

    在WPF中创建TreeGrid,首先需要选择一个合适的控件。常见的选择是使用DataGrid控件结合TreeView控件来实现。或者,可以使用第三方库,如Syncfusion、Telerik等提供的扩展控件,它们提供了内置的TreeGrid功能。 **3....

    WPF_多选树控件.zip

    在Windows Presentation Foundation (WPF) 中,开发人员经常需要创建具有多选功能的树形控件...通过查看和学习这个示例,开发者可以快速理解和掌握如何在WPF中实现多选树控件,从而在自己的项目中复用或扩展这些技术。

    WPF的动态树源码

    在本文中,我们将深入探讨WPF中的数据绑定以及如何构建动态树。 1. **数据绑定基础**: WPF的数据绑定机制允许UI元素(如TreeView)与应用程序的数据模型进行连接,自动同步它们的状态。它遵循一个简单的声明式...

    调试wpf-wpf可视化树+辅助类

    在开发WPF应用时,遇到问题时,理解和利用WPF可视化树以及辅助类进行调试至关重要。本文将详细探讨这两个概念及其在调试过程中的应用。 **一、WPF可视化树** 1. **定义**:WPF可视化树是UI元素的一种层次结构表示...

    WPF多选树作为模版.zip

    在WPF中,树形视图控件TreeView是用于展示层次结构数据的强大工具。通过使用ItemTemplate,我们可以自定义每个节点(TreeNode)的显示样式。在多选树中,每个节点不仅包含子节点,还可以被选中或取消选中。为了实现...

    WPF 下拉树菜单

    由于项目需要一个树形下拉菜单,在网上找了半天,找到一个sliverlight的,结果转成wpf又出错,放弃了,无赖只好自己整一个WPF的,思路是CheckBox+Popup完成了需求,基础实现,各位可以根据需求扩展成自己的想要的...

    基于wpf的桌面下雪圣诞树程序源码

    在WPF中,控件可以绑定到数据源,这样可以动态地更新界面。例如,雪花的数量和它们的属性(如大小、颜色、速度)可能存储在一个集合中,并绑定到UI上,以便于实时变化。 6. 事件处理 用户与应用程序的交互通常...

    wpf实现DataGrid列表控件实现树形结构,并且展开控件ToggleButton可以放在任意一列,也可以冻住多列不可拖动

    5. **MVVM模式**: 在WPF中,推荐使用Model-View-ViewModel(MVVM)设计模式来分离视图和业务逻辑。在本例中,ViewModel将负责处理数据的加载、折叠/展开状态的维护,以及与View之间的通信。视图则负责展示数据和交互...

    自定义WPF TreeSelect、MultiTreeSelect ,树形选择框,多选树形选择框

    在WPF中,我们可以通过继承或组合现有控件来实现这个功能。一般而言,我们可以使用ComboBox显示顶级节点,并在点击时展开一个TreeView,展示其子节点。用户可以选择一个节点作为当前选定值。 对于`MultiTreeSelect`...

    wpf 三态树

    wpf 三态树设计实例和效果截图 wpf 三态树设计实例

    使用WPF做的一个树形的省市县级联菜单

    在WPF中,树形控件通常使用`TreeView`来实现。`TreeView`控件可以展示数据的层次结构,非常适合用来创建省市县级联菜单。这种菜单允许用户逐步选择他们所在的地理位置,从省级开始,然后选择市,最后到县,这样的...

    treegrid.zip_treeGrid wpf_treegrid_treegrid wpf c#_wpf 树状表格_wpf树

    TreeGrid是WPF中的一个高级控件,它结合了树视图(TreeView)和数据网格(DataGrid)的功能,使得我们可以展示层次结构的数据,并且能够对这些数据进行编辑、排序和筛选。我们将基于“treegrid.zip”这个压缩包中的...

    WPF 下拉框树状通用控件

    在Windows Presentation Foundation(WPF)框架中,下拉框树状控件是一种常见的UI元素,它结合了下拉框和树视图的功能,允许用户在有限的空间内展示层级结构的数据。这种控件在多种场景下都非常有用,比如在选择组织...

    WPF TreeGrid

    在WPF中,TreeGrid通常是通过结合使用TreeView和DataGrid来实现的。TreeView提供了一个可折叠/展开的节点结构,而DataGrid则用于展示每个节点下的详细数据。通过数据绑定和模板定义,我们可以定制TreeGrid的外观和...

    wpf实现的checkbox层级联动树形菜单

    首先,要构建这个树形菜单,我们需要了解WPF中的TreeView控件。TreeView控件允许我们展示层次化的数据,每个节点可以包含子节点,非常适合构建树形结构。在我们的案例中,每个节点将包含一个Checkbox,以便用户可以...

    WPF控件模板可视化树查看器

    在WPF中,每个控件都有一个默认的模板,这个模板定义了控件的视觉表现。例如,Button控件的默认模板包括一个边框、背景、以及点击时的动画效果。控件模板通过`ControlTemplate`类来定义,可以使用XAML语言进行编写。...

Global site tag (gtag.js) - Google Analytics