WPF Unleashed Chapter 3:Important New Concepts in WPF ---Logical and Visual Trees 翻译

  本书的第一部分已经介绍完了,在开始接触wpf真正有意思的部分之前,我将介绍一些.NET程序员不太熟悉的概念。这些概念是WPF所特有的,是学习WPF的拦路虎,只有掌握之后,您才能更好地掌握本书后面介绍的知识。 

     其中的一些概念是WPF特有的(例如逻辑树和视觉树);还有一些是在原有个概念的一些扩充(例如属性(property)和事件)。学习完这些概念后,我们提供了一个应用这些概念的例子:一个"About dialog"
   
     逻辑树和视觉树 

     XAML天然的层次性使得它非常适合用于展现用户界面。WPF的用户界面是由一个"对象树"来构成的.这个树就是我们所说的逻辑树。 

    List 3.1中的代码定义了"About dialog",它使用Window元素作为逻辑树的根。Window控件内部包含一个子元素:StackPanel控件(第六章"Layout with Panels"将介绍),StackPanel内部还包含一个内部有两个Button控件的StackPanel。

   LISTING 3.1 A Simple About Dialog in XAML


<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
Title=”About WPF Unleashed” SizeToContent=”WidthAndHeight”
Background=”OrangeRed”> 
    <StackPanel> 
        <Label FontWeight=”Bold” FontSize=”20” Foreground=”White”> 
            WPF Unleashed (Version 3.0) 
        </Label> 
        <Label>© 2006 SAMS Publishing</Label> 
        <Label>Installed Chapters:</Label> 
        <ListBox> 
            <ListBoxItem>Chapter 1</ListBoxItem> 
            <ListBoxItem>Chapter 2</ListBoxItem> 
        </ListBox> 
        <StackPanel Orientation=”Horizontal” HorizontalAlignment=”Center”> 
            <Button MinWidth=”75” Margin=”10”>Help</Button> 
            <Button MinWidth=”75” Margin=”10”>OK</Button> 
        </StackPanel> 
        <StatusBar>You have successfully registered this product.</StatusBar> 
    </StackPanel>
</Window>

  图3.1是List 3.1中代码运行后的结果(你可以将里面的代码复制到XamlPad),图3.2是这个窗体所对应的逻辑树。
 
   FIGURE 3.1 The rendered dialog from Listing 3.1.

   需要注意的是:逻辑树并不只存在于使用XAML构建的对象中,使用程序代码构建的对象同样存在逻辑树。
  
FIGURE 3.2 The logical tree for Listing 3.1.
   从图中可以看出,逻辑树的概念还是比较简单的。那我们为何还要这么关注逻辑树这个概念呢?这是因为逻辑树会在会多方面影响到WPF。如属性,事件,资源等。举个例子来说,属性的值会沿着逻辑树传递到子元素上;事件的触发也是沿着逻辑树传递的。这些例子我们会在后面讨论 

    和逻辑树类似的是视觉树。逻辑树中包含很多节点,这些节点对我们来说是个黑盒(black box)。视觉树是逻辑树的一种扩展,可以将逻辑树中的节点分解成核心的视觉组件,这样视觉树可以将这些黑盒的内部实现暴露给我们。举例来说,虽然一个的ListBox在逻辑上是一个单独的控件,但是从视觉树的角度上看,ListBox是由很多wpf的原始元素组成:一个Border,两个ScrollBars...  

   需要说明的是:并不是逻辑树中的每个节点都是视觉树,符合条件的只有那些继承子 System.WindowsMedia.Visual或者System.Windows.Media.Visual3D的元素。其他的元素并不包括在内,因为那些元素(包括简单的字符串内容)没有继承自身的呈现行为。 

      图3.3是List 3.1中代码在Windows Vista的Aero主题下运行时的视觉树。图中的一些组件是在运行时看不到的,例如ListBox控件的那两个ScrollBar,Label控件的Border.从图中我们可以看出Button,Label,ListBoxItem这些控件都是由相同的一些元素组成。当然,Button控件使用的是ButtonChorme元素而不是Border元素。(这些控件之所以看上去不同是因为属性的默认值不同。例如Button的Margin属性的默认值为10,10,10,10,而Label控件的Margio值都为0)
   
FIGURE 3.3 The visual tree for Listing 3.1, with logical tree nodes emphasized.

      视觉树是相当复杂的,它是WPF底层架构中非常本质的部分,可以让我们对WPF的构成有深入的了解. 幸运的是,我们没必要对其过多关注,除非你要彻底地改变一个控件的样式(第10章“Styles, Templates, Skins, and Themes”会提到)或者做一些底层的绘图(第11章2D Graphics会提到)

WARNING

避免编写和特定视觉树紧密结合的代码,虽然视觉树不会因为添加删除元素的操作而改变,但当控件应用不同的主题时,同一个元素的视觉树是可能不同的


    我们使用System.Windows.LogicalTreeHelperSystem.Windows.Media.VisualTreeHelper类可以很容易分割逻辑树和视觉树。List 3.2中的代码是List 3.1的XAML代码的后台代码。调试程序时会以深度优先的方式展现窗体的逻辑树和视觉树。
   
   LISTING 3.2 Walking and Printing the Logical and Visual Trees


using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
public partial class AboutDialog : Window


    public AboutDialog() 
        { 
            InitializeComponent(); 
            PrintLogicalTree(0, this); 
        } 

    protected override void OnContentRendered(EventArgs e) 
        { 
            base.OnContentRendered(e); 
            PrintVisualTree(0, this); 
        } 

    void PrintLogicalTree(int depth, object obj) 
        { 
            // Print the object with preceding spaces that represent its depth 
            Debug.WriteLine(new string(‘ ‘, depth) + obj); 
            // Sometimes leaf nodes aren’t DependencyObjects (e.g. strings) 
            if (!(obj is DependencyObject)) return; 
                // Recursive call for each logical child 
            foreach (object child in LogicalTreeHelper.GetChildren( obj as DependencyObject))
                PrintLogicalTree(depth + 1, child); 
        } 

    void PrintVisualTree(int depth, DependencyObject obj) 
        { 
            // Print the object with preceding spaces that represent its depth 
            Debug.WriteLine(new string(‘ ‘, depth) + obj); 
            // Recursive call for each visual child 
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) 
                PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i)); 
        }
}


 
       将窗体本身传递给方法后(PrintVisualTree和PrintLogicalTree),得到的是和图3.2和图3.3一样的结果,只不过是文本形式的。我们可以在Window的构造函调用PrintLogicalTree方法,对其逻辑树进行遍历。但是视觉树则需要整个窗体呈现后才能够遍历。所以我们才将PrintVisualTree方法放OnContentRendered方法内调用,而不是像PrintLogicalTree那样放在构造函数中。

        遍历任意一棵树时都可以使用树中元素的实例方法。例如,Visual类有三个保护成员(VisualParent,VisualChildrenCount和GetVisualChild),用于得到Visual对象的父和子。FrameworkElement是许多控件的基类。例如Button,Label都是它的子类。FrameworkElement定义了一个公共的属性:Parent。用于呈现逻辑上的父对象。而FrameworkElement的各个子类所暴露的逻辑子对象也各有不同。例如有的类的子类可以是集合,而有的类则暴露一个内容属性(Content property),强迫该元素只能包含一个逻辑子对象

       
        下一节将介绍Dependency Property,它是WPF非常重要的特性...
posted on 2007-06-21 13:55  stswordman  阅读(3017)  评论(2编辑  收藏  举报