前面几节说了一些WPF的基础,包括XAML和布局等。在接下来的几节,我们来说说WPF的核心概念,包括逻辑树和可视树、依赖对象和依赖属性、路由事件、命令这几个部分。本节介绍下逻辑树(Logical Tree)和可视树(Visual Tree)。

逻辑树和可视树

在WPF中,用户界面是由XAML来呈现的。粗略地讲,从宏观上看,叶子为布局组件和控件所组成的树既是逻辑树,从微观上看,将逻辑树的叶子再放大可看到其内部是由可视化组件(继承自Visual类)组成的,叶子为这些可视化组件组成的树既是可视树。

逻辑树

举个例子来说明:

<Window x:Class="WpfTreeDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="100" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<TabControl>
<TabItem Header="第一页">
<TextBlock Text="This is first page" />
</TabItem>
<TabItem Header="第二页">
<TextBox Text="This is second page" />
</TabItem>
</TabControl>
<Button x:Name="btnOK" Grid.Row="1" Width="80" Height="80" Click="btnOK_Click">
<Button.Content>
<Image Source="/Images/photo.png" x:Name="imgPhoto"/>
</Button.Content>
</Button>
<ListView x:Name="lvStudents" Grid.Row="2">
<ListView.View>
<GridView>
<GridViewColumn Header="Index" DisplayMemberBinding="{Binding Index}" />
<GridViewColumn Header="Username" DisplayMemberBinding="{Binding Username}" />
<GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>

来看一下它的逻辑树:

层级感很强,这也正是XAML强大表现力的体现。如何来操作这棵树呢?最简单的方法当然是设置控件的name属性,然后在cs文件中根据name属性值来获取控件。WPF内置了一个LogicalTreeHelper类,我们可以通过它来遍历树,代码如下:

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.Navigation;
using System.Windows.Shapes;
using System.Collections;
using Microsoft.Windows.Themes;
using System.Diagnostics; namespace WpfTreeDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Student> students = new List<Student>
{
new Student{Index=,Username="Jello",Age=},
new Student{Index=,Username="Taffy",Age=}
};
this.lvStudents.ItemsSource = students;
PrintLogicalTree(, this); }
private void btnOK_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("I am a button");
}
public void PrintLogicalTree(int depth, object obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
if (!(obj is DependencyObject)) return;
foreach (var child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
{
PrintLogicalTree(depth + , child);
}
}
}
public class Student
{
public int Index { get; set; }
public string Username { get; set; }
public int Age { get; set; }
}
}

Debug运行后可以在Debug输出窗口看到界面的逻辑树。

可视树

想要观察可视树,需要将控件“打碎”来看下,这里我们以TextBox为例,用Blend来“打碎”它,看看这个控件的内部结构:

<LinearGradientBrush x:Key="TextBoxBorder" EndPoint="0,20" MappingMode="Absolute" StartPoint="0,0">
<GradientStop Color="#ABADB3" Offset="0.05"/>
<GradientStop Color="#E2E3EA" Offset="0.07"/>
<GradientStop Color="#E3E9EF" Offset="1"/>
</LinearGradientBrush>
<Style x:Key="TextBoxStyle1" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

主要是ListBoxChrome及ScrollViewer构成的,这也很好理解,将TextBox的功能拆分来看,ListBoxChrome主要可以用来输入,ScrollViewer用于当内容过多时会有滚动条。这里通过name属性一般是不能直接获取控件的,需要借助VisualTreeHelper类和Visual中的方法来获取。也举个例子:

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.Navigation;
using System.Windows.Shapes;
using System.Collections;
using Microsoft.Windows.Themes;
using System.Diagnostics; namespace WpfTreeDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Student> students = new List<Student>
{
new Student{Index=,Username="Jello",Age=},
new Student{Index=,Username="Taffy",Age=}
};
this.lvStudents.ItemsSource = students;
PrintLogicalTree(, this); }
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
PrintVisualTree(, this);
}
private void btnOK_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("I am a button");
}
public void PrintLogicalTree(int depth, object obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
if (!(obj is DependencyObject)) return;
foreach (var child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
{
PrintLogicalTree(depth + , child);
}
}
public void PrintVisualTree(int depth, DependencyObject obj)
{
Debug.WriteLine(new string(' ', depth) + obj);
for (int i = ; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
PrintVisualTree(depth + , VisualTreeHelper.GetChild(obj, i));
}
}
}
public class Student
{
public int Index { get; set; }
public string Username { get; set; }
public int Age { get; set; }
}
}

之所以在OnContentRendered中调用了一次是因为可视树直到Window完成至少一次布局后才会有节点,否则是空的,而实例化发生在布局完成之前(可以获取逻辑树),

OnContentRendered调用发生在布局完成之后(可以获取可视树)。

这里需要注意几点:

  1.并不是所有的元素(无与生俱来的呈现行为)都会出现可视树中,只有继承自Visual类或者Visual3D类的元素才会包含其中。

  2.逻辑树是静态的,而可视树是动态的(当用户切换Theme是会改变)。

  3.一般情况下,我们不需要考虑可视树,除非要进行控件重塑。

WPF学习(4)逻辑树和可视树的更多相关文章

  1. WPF路由事件一:逻辑树和可视树

    一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. <Window x:Class=&quo ...

  2. wpf 逻辑树与可视化树

    XAML天生就是用来呈现用户界面的,这是由于它具有层次化的特性.在WPF中,用户界面由一个对象树构建而成,这棵树叫作逻辑树.逻辑树的概念很直观,但是为什么要关注它呢?因为几乎WPF的每一方面(属性.事 ...

  3. WPF学习(6)路由事件

    做过.net开发的朋友对于事件应该都不陌生.追溯历史,事件(Event)首先应用在Com和VB上,它是对在MFC中使用的烦琐的消息机制的一个封装,然后.net又继承了这种事件驱动机制,这种事件也叫.n ...

  4. WPF学习(5)依赖属性

    今天我们来学习WPF一个比较重要的概念:依赖属性.这里推荐大家看看周永恒大哥的文章,讲的确实很不错.我理解的没那么深入,只能发表一下自己的浅见.提到依赖属性,不得不说我们经常使用的传统的.net属性, ...

  5. 【WPF学习】第五十八章 理解逻辑树和可视化树

    在前面章节中,花费大量时间分析了窗口的内容模型——换句话说,研究了如何在其他元素中嵌套元素,进而构建完整的窗口. 例如,考虑下图中显示的一个非常简单的窗口,该窗口包含两个按钮.为创建该按钮,在窗口中嵌 ...

  6. WPF中的逻辑树和可视化树

    WPF中的逻辑树是指XAML元素级别的嵌套关系,逻辑树中的节点对应着XAML中的元素. 为了方便地自定义控件模板,WPF在逻辑树的基础上进一步细化,形成了一个“可视化树(Visual Tree)”,树 ...

  7. WPF 中的逻辑树(Logical Tree)与可视化元素树(Visual Tree)

    一.前言 ​ WPF 中有两种"树":逻辑树(Logical Tree)和可视化元素树(Visual Tree). Logical Tree 最显著的特点就是它完全由布局组件和控件 ...

  8. WP8.1程序开发,可视树VisualTreeHelper类的使用

    对于可视树的使用,很久之前就接触了, 一方面当时知识太浅根本看不懂,就放下没看了: 另一方面,也没用到,就没往这方面努力研究学习: 现在好了,遇到问题了,正好涉及到VisualTreeHelper的使 ...

  9. AI逻辑实现-取舍行为树还是状态机

    AI逻辑实现-选择行为树还是状态机? 关注AI的朋友可能会看过赖勇浩翻译的<有限状态机时代终结的10大理由> ,里面谈到了状态机的诸多弊端.同时在ppt(附上下载地址)中述说了行为树的诸多 ...

随机推荐

  1. IOS开发应用

    IOS开发应用 我的第一个IOS开发应用 1. 需求描述 2. 开发环境介绍 3. 创建一个工程 4. 工程配置介绍 5. 目录结构介绍 6. 界面设置 7. 关联输入输出 8. 关联事件代码 9.  ...

  2. Linux 命令 快捷命令综合

    FTP开机启动 启动要让FTP每次开机自动启动,运行命令:  chkconfig --level 35 vsftpd on linux 关机 1.halt linux 注销 1.logout linu ...

  3. 学习NodeJS第一天:node.js介绍

    Node.JS 前辈 C 程序猿 Ryan Dahl(http://four.livejournal.com/)工程,根据 Google 著名的开源 JavaScript 发动机 V8 对于二次开发 ...

  4. 小米2S TWRP 3.0.2-0 最新版Recovery

    主界面 使用了我最新修改的内核 下载地址: 链接: http://pan.baidu.com/s/1i5xwddb 密码: 7dyb 验证信息: md5sum: dca410f33020eb87986 ...

  5. Zabbix的数据表结构

    看到Zabbix的数据表结构吧,就知道数据量大了 性能问题很让人担忧,不过基于Zabbix数据库导出报表,或自动跑报表的时候,就必须去了解一下zabbix的数据表结构了,得知道XX放在哪才能找到XX, ...

  6. DevExpress控件中LayoutControl的使用

    原文:DevExpress控件中LayoutControl的使用 C#开发中,软件布局设计,主要用TableLayoutPanel能很好地支持缩放功能,对自身的Label.TextBox等控件支持的很 ...

  7. MVC json

    1. .net MVC中Controller 在mvc中所有的controller类都必须使用"Controller"后缀来命名 并且对Action也有一定的要求: 必须是一个pu ...

  8. 如何自动以管理员身份运行.NET程序?

    原文:如何自动以管理员身份运行.NET程序? windows 7和vista提高的系统的安全性,同时需要明确指定“以管理员身份运行”才可赋予被运行软件比较高级的权限,比如访问注册表等.否则,当以普通身 ...

  9. LNMP 免安装包

    LNMP(Linux-Nginx-Mysql-PHP)可爱的黄金搭档,不过配置并不轻易,而我平常用于测试环境又经常用到,所以打包了这么一个免安装的LNMP包,内置常用库和模块,以及基本的优化设置,这样 ...

  10. jQuery来源学习笔记:整体结构

    1.1.由于调用一个匿名函数: (function( window, undefined ) { // jquery code })(window); 这是一个自调用匿名函数,第一个括号内是一个匿名函 ...