zoukankan      html  css  js  c++  java
  • WPF学习(4)逻辑树和可视树

    前面几节说了一些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=1,Username="Jello",Age=22},
                    new Student{Index=2,Username="Taffy",Age=21}
                };
                this.lvStudents.ItemsSource = students;
                PrintLogicalTree(0, 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 + 1, 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=1,Username="Jello",Age=22},
                    new Student{Index=2,Username="Taffy",Age=21}
                };
                this.lvStudents.ItemsSource = students;
                PrintLogicalTree(0, this);
    
            }
            protected override void OnContentRendered(EventArgs e)
            {
                base.OnContentRendered(e);
                PrintVisualTree(0, 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 + 1, child);
                }
            }
            public void PrintVisualTree(int depth, DependencyObject obj)
            {
                Debug.WriteLine(new string(' ', depth) + obj);
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    PrintVisualTree(depth + 1, 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.一般情况下,我们不需要考虑可视树,除非要进行控件重塑。

  • 相关阅读:
    获取页面元素的xpath,验证自己写的xpath,不用工具不用插件,看完这篇保证你学会!
    Python判断IEDriverServer是否最新版本并自动更新
    Python判断软件版本号的大小
    selenium通过加载火狐Firefox配置文件FirefoxProfile,实现免登陆访问网站
    mysql查询每个学生的各科成绩,以及总分和平均分
    selenium点击(click)页面元素没有反应(报element not interactable)的一个案例
    Python查询物理主机上所有虚拟机并保存为excel,通过标记批量启动
    Python线程池下载txt
    Python自动下载最新的chromedriver
    django的url的name参数的意义
  • 原文地址:https://www.cnblogs.com/jellochen/p/3439903.html
Copyright © 2011-2022 走看看