WPF中的可视化树(VisualTree)和逻辑树(LogicalTree)

news2024/11/24 13:25:49

可视化树和逻辑树

我们先来理解一下什么是可视化树和逻辑树。

可视化树:包含最初指定的大多数元素(在XAML或.cs中)以及控件模板中的元素。

通俗点来讲,就是整个元素的构成树,从最上面的结点到最后一个结点(包括控件模板)。

逻辑树:是可视化树的一个子集,它省略了控件模板中的元素。

通俗点来讲,就是不包含控件模板的可视化树

以上的解释仅仅用于简单理解这两个概念以及区别,完整的解释请参考文末的链接,MSDN上的文档将会更准确。

下面我们创建一个Window来演示,XAML如下:

1 <Window>
3         <Grid Name="grid" Grid.Column="1">
4             <Button HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,80,0" Grid.Row="1" Content="获取可视化树" Click="GetVisualTree_Click"></Button>
5             <Button HorizontalAlignment="Right" VerticalAlignment="Center" Content="获取逻辑树" Grid.Row="1" Click="GetLogicalTree_Click"></Button>
6         </Grid>
7 </Window>

在WPF中提供了VisualTreeHelper 和 LogicalTreeHelper 两个类来对可视化树和逻辑树进行操作。

使用VisualTreeHelper来获取Grid的可视化树

 1  public void PrintVisualTree(DependencyObject parent, int level)
 2  {
 3      string typeName = parent.GetType().FullName ?? parent.GetType().Name;
 4      string name = (string)(parent.GetValue(FrameworkElement.NameProperty) ?? "");
 5      AppendText("           ".Substring(0, level * 2));
 6      AppendText($"{typeName}:",true);
 7      AppendText($" {name}");
 8      AppendText(Environment.NewLine);
 9      for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
10      {
11          DependencyObject child = VisualTreeHelper.GetChild(parent, i);
12          PrintVisualTree(child, level + 1);
13      }
14  }

输出结果如下:

可以看到Grid的视觉树不仅打印了两个Button,还打印了Button的控件模板。

Button控件的控件模板如下:

1 <Border TextBlock.Foreground="{TemplateBinding Foreground}"
2                 x:Name="Border"
3                 CornerRadius="2"
4                 BorderThickness="1">
5                   <ContentPresenter Margin="2"
6                             HorizontalAlignment="Center"
7                             VerticalAlignment="Center"
8                             RecognizesAccessKey="True" />
9 </Border>

使用LogicalTreeHelper来获取Grid的逻辑树

 1 public void PrintLogicalTree(object parent, int level)
 2 {
 3     var parentObj = parent as DependencyObject;
 4     var typeName = parent.GetType().FullName;
 5     var name = "";
 6     if (parentObj != null)
 7     {
 8         name = (string)(parentObj.GetValue(FrameworkElement.NameProperty) ?? "");
 9     }
10     else
11     {
12         name = parent.ToString();
13     }
14 
15     AppendText(GetIndentString().Substring(0, level * 2));
16     AppendText($"{typeName}:", true);
17     AppendText($" {name}");
18     AppendText(Environment.NewLine);
19 
20     if (parentObj == null)
21         return;
22 
23     var children = LogicalTreeHelper.GetChildren(parentObj);
24     foreach (object child in children)
25     {
26         PrintLogicalTree(child, level + 1);
27     }
28 }

运行结果如下:

可以看到Grid的逻辑树只显示到了Button的内容这一层。

可视化树的用途

在上面的Button模板中,我们定义了一个Border,并命名为Border,

如果我们想查找这个控件,并为之添加事件处理程序或设置一些属性,都可以通过可视化树来实现。 

首先我们定义一个遍历可视化树的方法

 1  public Visual EnumVisual(Visual myVisual,string controlName)
 2  {
 3      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
 4      {
 5          
 6          Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
 7          var nameObj = childVisual.GetValue(NameProperty);
 8 
 9          if (nameObj != null && nameObj.ToString() == controlName)
10              return childVisual;
11 
12          EnumVisual(childVisual,controlName);
13      }
14 
15      return null;
16  }

然后我们查找Border,并设置它的CornerRadius属性

 1 //查找控件模板中的名称为Border的控件
 2 var border = EnumVisual(this.btn1, "Border");
 3 if (border != null)
 4 {
 5     var borderObj = border as Border;
 6 
 7     if (borderObj != null)
 8     {
 9         borderObj.CornerRadius = new CornerRadius(10);
10     }
11 }

运行效果

实际上我们还可以通过FrameworkTemplate.FindName方法进行查找,使用方法如下:

1 object findObj = this.btn1.Template.FindName("Border",this.btn1);

FrameworkTemplate.FindName内部没有直接调用VisualTreeHelper,它是利用缓存查找的。

逻辑树的用途

借助逻辑树,内容模型可以方便地循环访问其可能的子对象,从而实现扩展。

FrameworkElement提供了一个FindName的方法,可以通过名称查找子对象

1 public object FindName(string name);

它内部的实现实际就是借助的逻辑树

1 internal object FindName(string name, out DependencyObject scopeOwner)
2 {
3     INameScope scope = FindScope(this, out scopeOwner);
4     if (scope != null)
5     {
6         return scope.FindName(name);
7     }
8     return null;
9 }

 1 internal static INameScope FindScope(DependencyObject d, out DependencyObject scopeOwner)
 2 {
 3     while (d != null)
 4     {
 5         INameScope scope = NameScope.NameScopeFromObject(d);
 6         if (scope != null)
 7         {
 8             scopeOwner = d;
 9             return scope;
10         }
11         DependencyObject parent = LogicalTreeHelper.GetParent(d);
12         d = (parent != null) ? parent : Helper.FindMentor(d.InheritanceContext);
13     }
14     scopeOwner = null;
15     return null;
16 }

示例代码

参考链接:

https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/trees-in-wpf?view=netframeworkdesktop-4.8

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2069164.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为:IT系统的演进与数字时代IT系统的重新定位

往期回顾&#xff1a; 企业4A架构&#xff1a;数字化转型的底层方法论&#xff08;附TOGAF资料下载&#xff09; PPT分享&#xff1a;数据治理的方法论、设计思路与方案&#xff08;干货&#xff09; 浅谈数字化转型方法论 110页PPT:xx业务流程优化&#xff08;BPR&#xff…

树状数组算法

文章目录 树状数组是什么树状数组与线段树的区别与联系树状数组讲解点修&#xff0c;区查&#xff0c;讲解及模板点查&#xff0c;区修讲解及模板 树状数组是什么 树状数组是一种数据结构&#xff0c;提供O(logn)时间内的单点修改和区间求和操作&#xff0c;比线段树有更优的常…

语音控制开关的语音识别ic芯片方案

语音控制开关是一种基于语音识别技术的设备&#xff0c;它通过内置的语音识别芯片&#xff0c;将用户的语音指令转化为电信号&#xff0c;从而实现对设备的控制。例如在智能家居设备上的应用&#xff0c;通常需要连接到家庭的Wi-Fi网络上&#xff0c;以便与智能手机或智能音箱等…

openjdk11 jvm视角查看java线程异常退出synchronized锁自动释放

##synchronized锁异常 java代码demo public class ThreadTest {public int mul 1;public static void main(String[] args) {ThreadTest threadTest new ThreadTest();Thread yym_user_thread1 new Thread() {public void run() {System.out.println("yym_user_thread…

【C++题解】1147. 求1/1+1/2+2/3+3/5+5/8+8/13+13/21……的前n项的和

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1147. 求1/11/22/33/55/88/1313/21……的前n项的和 类型&#xff1a;函数 题目描述&#xff1a; 求1/11/22/33/55/88/1313/2121/34…的前 n 项的和。 输入&#xff1a; 输入一个…

WPF—路由事件详解

WPF—路由事件 路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件。路由事件是一个CLR事件。 路由事件与一般事件的区别在于&#xff1a;路由事件是一种用于元素树的事件&#xff0c;当路由事件触发后&#xff0c;它可以向上或向…

C++ | Leetcode C++题解之第371题两整数之和

题目&#xff1a; 题解&#xff1a; class Solution { public:int getSum(int a, int b) {while (b ! 0) {unsigned int carry (unsigned int)(a & b) << 1;a a ^ b;b carry;}return a;} };

微程序控制器的设计

目录 一、微程序控制器的原理 1.模仿高级语言思想到微程序&#xff1a; 2.采用存储程序思想到CU内构造&#xff1a; 3.微程序执行过程&#xff1a; 二、微指令的设计 1.前置概念&#xff1a; 相容性微命令&#xff1a; 互斥性微命令&#xff1a; 2.微指令格式 水平…

作者推荐 |【Redis技术进阶之路】「技术提升系列」夯实基础分析探究ziplist压缩列表的点点滴滴

揭秘高效存储模型与数据结构底层实现 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 压缩列表&#xff08;ziplist&#xff09;压缩列表使用目的压缩列表结构组成压缩列表的各个组成部分三个…

flv格式分析与解复用

介绍 FLV(Flash Video)是Adobe公司推出的⼀种流媒体格式&#xff0c;由于其封装后的⾳视频⽂件体积⼩、 封装简单等特点&#xff0c;⾮常适合于互联⽹上使⽤。⽬前主流的视频⽹站基本都⽀持FLV。采⽤FLV 格式封装的⽂件后缀为.flv。 FLV封装格式是由⼀个⽂件头(file header)和 …

0基础深度学习项目13:基于TensorFolw实现天气识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 目录 一、创建环境二、前期准备2.1 设置GPU2.2 导入数据2.3 数据预处理2.3.1 加载数据2.3.2 查看图像的标签 2.4 数据可视化 三、构建简单的CNN网络&#xff0…

NVDLA专题12:具体模块介绍——RUBIK

概述 RUBIK类似于BDMA&#xff0c;它无需任何数据计算对数据映射格式进行转换。RUBIK有3种工作模式&#xff0c;分别是: 合并(Contract)数据立方体将特征数据立方体分割为多平面&#xff08;multi-planar&#xff09;格式将多平面(multi-planar)格式合并到数据立方体 由于该…

第三十八篇-TeslaP40-SenseVoice部署,速速杠杠的

环境 系统&#xff1a;CentOS-7 CPU: 14C28T 内存&#xff1a;32G 显卡&#xff1a;Tesla P40 24G 驱动: 535 CUDA: 12.2创建环境 conda create -n sv python3.11 -y conda activate sv克隆 git clone https://github.com/FunAudioLLM/SenseVoice.git cd SenseVoice配置镜像…

React antd Table表格动态合并单元格

注意&#xff1a; ① 采用的是React antDsign 4.x版本 ② 需重新处理data数据 实现效果 代码实现 import React from react; import { Table } from antd;const data [{key: 0,name: 张三,age: 22,sex: 男,},{key: 1,name: 李四,age: 42,sex: 男,},{key: 2,name: 小丽,age: …

CAN的协议层介绍

一&#xff0c;CAN帧种类介绍 1. 数据帧&#xff08;Data Frame&#xff09;&#xff1a;数据帧是CAN总线上用于传输用户数据的帧&#xff0c;包括必要的帧头、标识符、控制位、数据长度代码、数据域、CRC校验码和应答域等部分&#xff0c;是CAN通信中最基本和最重要的帧类型。…

Android Room DataBase

Room数据库是在Sqlite的基础上&#xff0c;进行了封装和优化。这让我们可以摆脱&#xff0c;繁琐的数据库操作 在module的gradle里面&#xff0c;加入: dependencies {annotationProcessor "androidx.room:room-compiler:2.3.0"implementation androidx.room:room-…

Selenium自动化测试 常见API的使用

本篇文章内容是关于 Selenium 自动化测试工具的常见 API 的使用 Selenium版本&#xff1a;4.23.1 编程语言&#xff1a;Java JDK22 编译器&#xff1a;IDEA 2024.2.0.2 浏览器版本&#xff1a;谷歌浏览器128.0.6613.36&#xff08;正式版本&#xff09; &#xff08;64 位&…

【Hexo】hexo-butterfly主题添加装备展示页面

本文首发于 ❄️慕雪的寒舍 在翻开往的时候看到了一位老哥的博客里面正好有这个教程&#xff0c;整了一下发现效果还不错&#xff01; Hexo的Butterfly魔改教程&#xff1a;我的装备&#xff0c;分享你在用的设备 | 张洪HeoHexo博客添加自定义css和js文件 | Leonus 注&#x…

Python个人收入影响因素模型构建:回归、决策树、梯度提升、岭回归|数据分享...

全文链接&#xff1a;https://tecdat.cn/?p37423 分析师&#xff1a;Greata Xie “你的命运早在出生那一刻起便被决定了。”这样无力的话语&#xff0c;无数次在年轻人的脑海中回响&#xff0c;尤其是在那些因地域差异而面临教育资源匮乏的年轻人中更为普遍。在中国&#xff0…

NRC-SIM:基于Node-RED的多级多核缓存模拟器

整理自&#xff1a; 《NRC-SIM: A NODE-RED Based Multi-Level, Many-Core Cache Simulator》&#xff0c;由 Ezequiel Trevio 撰写&#xff0c;作为他在德克萨斯大学里奥格兰德河谷分校攻读电气工程硕士学位的部分成果。以下是论文的详细主要内容&#xff1a; 摘要(Abstract…