现在的位置: 首页 > 综合 > 正文

WPF的知识

2013年01月23日 ⁄ 综合 ⁄ 共 14239字 ⁄ 字号 评论关闭

闲话WPF之一(WPF的结构) 

 WPF进入我们的生活已经很多年。(写这句话让我想起来了我不做大哥好多年”。) 个人认为在UI的实践中,用户需要的是易于操作的,更加绚丽的界面。这两个应该是最基本、也是最重要的宗旨。而对于开发人员就是要用最简单的方法开发出尽可能漂亮的界面,并且效率也不能太差。(要求是不是有些过分啦!)除了在一些Web开发和特殊的应用中,很少有开发组配备单独的美工,至少目前是这样吧!根据自己目前对WPF的了解程度,感觉WPF在其中某些方面确实有超强的震撼力。 

客观上讲,Vista操作系统确实给我们带来了无可比拟的视觉效果。我自己深有体会,在近2个月的时间里每天都是在Vista下的开发,回家后看到XP系统,始终有些不爽的感觉。 

WPF可以认为是MS利用原有.NET框架的一些特色,加上DirextX的产物。从下图的WPF组件中,我们可以看出最底层仍然是一些内核API。(以下两张图片都来自互联网。)

其中红色显示的组件是WPF的核心。Milcore是一个和DirectX交互的非托管组件,非托管代码能带给我们更高效的处理,能更好的和DirextX交互。WPF的所有显示都是由Dirext完成的。milcore中一个非常重要的功能就是Composition引擎,这个引擎对效率的要求很高,它的具体作用稍后介绍。所以milcore放弃了一些CLR的特征来换取效率。而另外两个红色的组件都是建立在CLR基础之上,利用了.NET的优势。 

至于其中的User32组件有什么作用,偶目前的知道的就是在WPF的某些应用场景中为了某些兼容需要使用User32,其中就有DWM(桌面窗口管理)。DWM的内容又可以写上一大堆,感兴趣的朋友可以看SDK文档。

我们除了关心WPF的基本结构外,更重要的 是WPF提供了什么功能,请看下图:

图中的每个黄色块都是一种媒体类型。这就表示WPF可以处理几乎所有的媒体类型:位图、3D、音频、视频和文本等等。通过WPF,它集成了现在的GDI/GDI+D3D/OPENGL以及多媒体的DSHOW等等。所有的东西都是等同对象,不管的3D还是2D,或者文本。

结构图中的Animate块贯串了整个的结构,因为在WPF中我们可以对所有的可视内容进行动画操作。这是非常让人期待的功能。Animate下面我们再次看到了Composition引擎,前面提到过它是位于milcore组件中。开发过程中,我们的界面元素功能有多种,比如图片,视频等等,最后显示到窗口的内容可以认为只是一张图片(准确说是Surface)。这个引擎的作用就是合成这些图片和视频元素最后进行提交显示。

怎么感觉是废话一堆啊!我准备好了,大家的西红柿、鸡蛋不用吝啬的,尽管杂吧!

闲话WPF之二(XAML概述) 

在我开始看WPF文档开始的几天里,脑子里形成了一种错误的想法:WPF不就是XAML码?当时的感觉就是郁闷啦,我学习WPF还得弄这个东西。给人的第一感觉就是WPF很复杂。虽然对WPF的熟悉和了解还不是特别多,但现在已经知道这确实是一种错误的想法。

Charles Petzold先生曾有一篇文章介绍了WPFXAML的一些关系(The Two APIs)。文章中说明了WPF为什么很复杂:因为WPF有两套API,一套用于普通的编码访问(比如C#VB.NET等其中.NET支持的语言。而另外一套就是基于XMLAPI,被称为XAMLExtensible Application Markup Language)。

XAML实现UI代码和应用程序逻辑代码的分离。在.NET 3.0Windows Vista中,XAMLWPF一起建立整个的UI。由于XAML是基于XML的,所以每个XAML代码都肯定是一个完整的XML文件。XAML继承了XML所有的定义和规则。XAML与其他XML扩展不同之处就是他所表示的意义。每个XAML元素是一个.NET CLR类。基于XML使得我们非常容易扩展和操作XAML。利用XAMLWPF这种关系,开发人员可以单独的设计漂亮的UI,也许真正的美工会更多的出现。我们可以把程序逻辑写在单独的文件或者是内联嵌入到XML文件。 

XAML中使用得最多的XML功能应该有三个:命名空间、属性和子元素。 

先看一个简单的XAML的例子:

<Window x:Class="FirstXAML.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="FirstXAML" Height="200" Width="300"

   >

  <Canvas>

  </Canvas>

</Window>

其中的xmlns就是XML中的名字空间,在W3Cxmlns是如下定义的:

XML namespaces provide a simple method for qualifying element and attribute names used in Extensible Markup Language documents by associating them with namespaces identified by URI references.

简单地说就是xmlns提供了一种方法把URI引用的名字空间定义为当前XML文件的元素和属性的默认命名空间。这里表示当前这个XML文档,也就是我们的XAML文件,它的默认的命名空间就是http://schemas.microsoft.com/winfx/2006/xaml/presentation

然后是属性和子元素,XML对属性的表示除了可以用Property外,还可以用子元素,在XAML中也是如此,看一个简单的例子:

<Button Width="6">

   <Button.Background>White</Button.Background>

</Button>

例子当中就使用了属性和子元素两种方式来指定属性。其中的Width是直接用属性表示,Background属性是用子元素表示。在多数时候,但不是所有,你可以自由选择这两种表示方式之一。

XAML被编译为BAMLBinary Application Markup Language)文件。通常,BAML文件比XAML更小,编译后的BAML都是Pre-tokenized的,这样在运行时能更快速的加载、分析XAML等等。这些BAML文件被以资源的形式嵌入到Assembly当中。同时生成相应的代码(文件名称是**.g.cs或者**.g.vb),这些代码根据XAML元素分别生成命名的 Attribute字段。以及加载BAML的构造函数。

 

最后,关于XAML的优点,我附上一点翻译过来的条款,可能更直观:

XAML除了有标记语言、XML的优点外,还有如下一些优点:

   XAML设计UI更简单

   XAML比其他的UI设计技术所需编码更少。

   XAML设计的UI方便转移、方便在其他环境提交。比如在WebWindows Client

   XAML设计动态UI非常容易

   XAMLUI设计人员带来新的革命,现在所有的设计人员不再需要.NET开发的知识同样可以设计UI。在不远的将来,终端用户可以看到更漂亮的UI

闲话WPF之三(XAML的名字空间) 

前一篇文章中,指出xmlns的作用是设置XML文件的命名空间。类似的,xmlns:x的作用也是指定命名空间。这里为什么是x而不是其他的,我们可以简单的理解为其只是MS的一个命名而已,没有任何特殊的意义,当然,为了避免和它的冲突,我们定义自己的命名空间的时候不能是x。

而另一个x:Class的作用就是支持当前Window所对应的类,前面已经说过每个XAML元素都是一个CLR类型,这里的x:Class是Window的一个属性,属性的内容指出当前的窗口类是FirstXAML名字空间下的Windows1。为什么需要类,而不全部用XAML实现?XAML的主要作用还是编写UI部分,我们仍然需要用代码对程序逻辑进行更深层次的控制。

好了,这是两个最基本的名字空间。同样地,名字空间也可以自定义,并且这个自定义会给我们带来很大的方便。我们定义如下的一个类:

namespace DataBind4Image
{
   public class GroupData
   {
      //具体的细节忽略
    }
}

如果想在XAML文件中使用这个GroupData类对象,我们就可以通过自定义的名字空间引入这个类:

xmlns:local="clr-namespace:DataBind4Image"

这里的后缀local是一个标识,你可以设置为任何你喜欢的唯一标识。通过这个引入定义我们就可以在XAML文件中用local来标识DataBind4Image当中的任何类。访问GroupData类时只需要加上local就可以识别了:<local:DrawingGroupData/>

利用名字空间,除了可以引入我们定义的当前工程的类,还可以引入任何的Assembly。直接看例子是最简单的:

<Window x:Class="WindowsApplication1.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:sys="clr-namespace:System;assembly=System"
  >
  <ListBox>
    <sys:String>One</sys:String>
  </ListBox>
</Window>

例子当中引入.NET的System Assembly,通过它我们就可以直接使用System的任何类。利用这种类似的方式,我们可以在XAML中使用几乎所有的DOTNET框架类。

最后说明一下在XAML中inline嵌入程序逻辑处理代码的情况。利用<CDATA[…]]>关键字引入处理代码。这种情况在实际当中不太合适,我们不应该采用UI和逻辑混合的方式。详细的解释可以参数Windows SDK文档。

<![CDATA[
    void Clicked(object sender, RoutedEventArgs e)
    {
        button1.Content = "Hello World";
    }
  ]]></x:Code>

前面提到过每个XAML元素表示一个.NET CLR类。多数的XAML元素都是从System.Windows.UIElement, System.Windows.FrameworkElement, System.Windows.FrameworkContentElement和System.Windows.ContentElement继承。没有任何的XAML元素与.NET CLR的抽象类对应。但是很多元素都有一个抽象类的派生类对应。

 

通常有如下四种通用的XAML元素:

Root元素:Windows和Page是最常用的根元素。这些元素位于XAML文件的根元素,并包含其他元素。

Panel元素:帮助布置UI位置。常用的是StackPanel, DockPanel, Grid和Canvas。

Control元素:定义XAML文件的控件类型。允许添加控件并自定义。
Document元素:帮助实现文档提交。主要分为InlineBlock元素组,帮助设计的外观类似文档。一些有名的Inline元素有BoldLineBreak, ItalicBlock元素有Paragraph, List, Block, FigureTable

XAML元素的属性与.NET类对象的属性类似,XAML的面向对象特征使得它的行为与之前的HTML类似。每个属性(实际上是类属性)继承了父元素的属性或者重载(如果重新设置了属性)。

闲话WPF之四(WPFWin32 

说明:这里的Win32特指Vista操作系统之前的所有图形系统:GDI、GDI+、Direct3D。

 

GDI是当今应用程序的主流图形库,GDI图形系统已经形成了很多年。它提供了2D图形和文本功能,以及受限的图像处理功能。虽然在一些图形卡上支持部分GDI的加速,但是与当今主流的Direct3D加速相比还是很弱小。GDI+开始出现是在2001年,它引入了2D图形的反走样,浮点数坐标,渐变以及单个象素的Alpha支持,还支持多种图像格式。但是,GDI+没有任何的加速功能(全部是用软件实现)。

 

当前版本的WPF中,对一些Win32功能还没有很好的支持,比如WMF/EMF文件,单个象素宽度的线条等等。对于这些需求还需要使用GDI/GDI+来实现。

 

在Windows Vista中,GDI和GDI+仍然支持,它们与WPF并行存在,但是基本上没有任何功能性的改进。对GDI和GDI+的改进主要集中在安全性和客户相关问题上。WPF的所有提交都不依赖于GDI和GDI+,而是Direct3D。并且所有的Primitive都是通过Direct3D的本地接口实现的。还记得我前面随笔中提到过的Milcore吗?它就是和Direct3D交互的非托管代码组件。由于WPF的大部分代码都是以托管代码的形式存在的,所以WPF中有很多托管、非托管的交互。当然,在一些图形卡不支持WPF所需要的功能时,WPF也提供了稍微低效的软件实现,以此来支持在某些PC上运行WPF应用程序。

 

在Windows Vista中,Direct3D的关键改进就是引入了新的显示驱动模型。VDDM驱动模型虚拟化了显卡上的资源(主要是显示内存),提供了一个调度程序,因此多个基于Direct3D的应用程序可以共享显卡(比如WPF应用程序和基于WPF的Windows Vista桌面窗口管理)。VDDM的健壮性、稳定性也得到了提高,大量的驱动操作从内核(Kernel)模式移动到了用户(User)模式,这样提高了安全性,也简化了显示驱动的开发过程。

 

在Windows Vista中存在两个版本的Direct3D:Direct3D 9和Direct3D 10。WPF依赖于Direct3D 9,这样能更广泛的解决兼容性问题。另外一个非常重要的原因就是为Vista的服务器版本提高方便,因为服务器版本的Vista对显卡和Direct3D基本上没有任何的要求。同时WPF也支持Direct3D 10。Direct3D 10依赖与VDDM,只能在Windows Vista上使用。由于Windows XP没有VDDM,虽然Microsoft做了很大的努力来改善XP中Direct3D 9相关驱动,提高内容的显示质量,但是由于XP中没有对显卡资源的虚拟化,强制所有的应用程序都用软件提交。

WPF对某些多媒体的功能支持还需要依赖老的技术,比如DirectShow。当我们进行音频视频的捕捉或者其它任务时,只能直接用DirectShow实现,然后再用HwndHost嵌入到WPF内容当中。

 

利用类似的技术,我们可以在WPF应用程序中显示自定义格式的内容。通过提供自定义的DirectShow CODEC,然后用Media元素实现和WPF内容毫无限制的集成。

 

另外,WPF对XPS等文档的打印输出也得到了极大的改善。XPS文档本身的规范也极大的提高了其打印的质量,XPS文档的规范可以参考MSDN的资料。除了打印,Vista操作系统中对远程的改进也部分依赖于WPF,比如有远程协助、远程桌面和终端服务等等。它们的实现过程是通过发送一系列的“远程”命名到客户端,客户根据自己PC的性能和命名进行显示,这样显示的质量能得到极大的提高。

 

在WPF中,对Direct3D进行各种封装。当然,如果你本身对Direct3D/OpenGL很熟悉,也可以直接在WPF中使用。封装后的Direct3D更容易使用。并且在Web应用程序(XBAP)也可以使用Direct3D。在WPF中使用的Direct3D,没有直接用非托管代码控制所拥有的灵活性,也不能直接对硬件进行底层控制。

 

WPF中所有的提交都是矢量形式的,我们可以对图像或窗口进行任意级的放缩,而图像的质量不会有任何的损耗。

闲话WPF之五(XAML中的类型转换) 

 在前面关于XAMLPost当中,简单说明了XAML如果引入自定义名称空间。还提到过XAML基本上也是一种对象初始化语言。XAML编译器根据XAML创建对象然后设置对象的值。比如:

<Button Width=”100”/>

很明显,我们设置Button的宽度属性值为100。但是,这个“100”的字符串为什么可以表示宽度数值呢?在XAML中的所有属性值都是用文本字符串来描述,而它们的目标值可以是double等等。WPF如何将这些字符串转换为目标类型?答案是类型转换器(TypeConverter)。WPF之所以知道使用Double类型是因为在FrameworkElement类中的WidthProperty字段被标记了一个TypeConverterAttribute,这样就可以知道在类型转换时使用何种类型转换器。TypeConverter是一种转换类型的标准方法。.NET运行时已经为标准的内建类型提供了相应的TypeConverter。所以我们可以用字符串值指定元素的属性。

然而并不是所有的属性都标记了一个TypeConverterAttribute。这种情况下,WPF将根据属性值的目标类型,比如Brush,来判断使用的类型转换器。虽然属性本身没有指定TypeConverterAttribute,但是目标类型Brush自己标记了一个TypeConverterAttribute来指定它的类型转换器:BrushConverter。所以在转换这种属性时将自动使用目标值类型的BrushConverter将文本字符串类型的属性值转换为Brush类型。

类型转换器对开发人员有什么作用呢?通过它我们可以实现自定义的类型转换。下面一个例子演示了如何从Color类型转换为SolidColorBrush

[ValueConversion(typeof(Color), typeof(SolidColorBrush))]
public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Color color = (Color)value;
        return new SolidColorBrush(color);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

然后我们可以在资源中定义一个ColorBrushConverter 实例(src是一个自定义命名空间,引入了ColorBrushConverter 类所在的Assembly)。

<Application.Resources>
  <src:ColorBrushConverter x:Key="ColorToBrush"/>
</Application.Resources>

最后使用这个自定义的类型转换器:

<DataTemplate DataType="{x:Type Color}">
  <Rectangle Height="25" Width="25" Fill="{Binding Converter={StaticResource ColorToBrush}}"/>
</DataTemplate>

其实WPF所使用的这种类型转换从.Net Framework1.0已经开始并广泛应用。有兴趣的朋友可以参考MSDN的介绍:通用类型转换(Generalized Type Conversion

闲话WPF之六(XAML的标记兼容性(Markup Compaibility)) 

继续XAML的话题,在前一个Post当中简单介绍了XAML的类型转换器(TypeConverters)。这次介绍一些XAML标记兼容性(Markup Compatibility)的相关内容。

利用XAML标记兼容性实现更加强大的注释功能

写过XAML的朋友应该都知道:在XAML中可以通过<!--****-->标记来实现注释。但是,利用XAML标记兼容性,还提供了其它更加强大的注释功能。
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006
    xmlns:c="Comment"
    mc:Ignorable="c">
   <Canvas>
    <Button c:Width="100" Height="50">Hello</Button>
   </Canvas>
</Window>

看见了Width前面的c前缀吗?它的作用就是注释掉Width属性。是不是感觉比标记注释的方法简单。而且这个c前面不但可以应用在属性上,也可以直接应用在实例上,如下:

<Window
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006
    xmlns:c="Comment"
    mc:Ignorable="c">
 <Canvas>
    <c:Button Width="100" Height="50">Hello</c:Button>
 </Canvas>
</Window>

上面的代码就全部注释掉了Button实例。当然,这种方法不建议在最后的发布XAML文档中出现。只适合在XAML文档的开发过程中使用。

XAML标记的向后兼容性

XAML支持XAML文档的向前和向后兼容性。为了帮助说明XAML标记的向后兼容性,我们看一个自定义的View类,其中定义了一个Color类型的颜色属性Color_Prop

public class CLYLView
{
        Color _color;        
       public Color Color_Prop { get { return _color; } set { _color = value; } }
}

很简单,在XAML中,我们可以如下使用这个CLYLView类:

<CLYLView Color=”Red” xmlns=”… assembly-V1-uri…”>

注意其中的xmlns=”… assembly-V1-uri…”,这就是一个所谓的XmlnsCompatibleWith属性。通过它我们指定了包含CLYLView的特定Assembly

现在,我们向V2版本的CLYLView添加了一个Content属性。如下所示:

public class CLYLView 
{
        Color _color;
       Content _content;
       public Color Color_Prop { get { return _color; } set { _color = value; } }
       public Content Content_Prop { get { return _content; } set { _content = value; } }

}

现在我们可以这样使用V2版本的CLYLView实例:

<CLYLView Color=”Red” Content=”Unknown” xmlns=”... assembly-v2-uri…”/>

但是,我们仍然希望在V2版本的CLYLView支持V1版本。满足这种需求,我们可以用XmlnsCompatableWith声明一个新的Assembly与老的Assembly兼容。XAML加载器看到了XmlnsCompatableWith属性,就会把默认地把所有对V1的引用处理为V2的引用。

向后兼容最大的一个好处就是:当我们只有新版的Assembly时,所有对老版Assembly的引用仍然是可读的,不会出现任何的错误。 

闲话WPF之七(XAML的向前兼容性) 

前一个Post当中,我们简单介绍了XAML的向后兼容性,以及利用标记兼容性实现注释的功能。现在,我们接着讨论XAML的向前兼容性问题。 

同样地,我们用一个简单的例子来帮助说明XAML的向前兼容性。假设有一个自定义的CLYLButton,实现了一个Light属性。在V1版本它的默认属性值是Blue(蓝光)。在V2版本中支持属性值Green(绿光)。假设我们在程序中利用Light属性实现了绿光效果。但是,如果恰好目标机器上的V2版本意外地被替换为了V1版本。此时,程序的行为应该怎么样呢?崩溃,不,我们希望它在没有V2的情况下能利用V1版本的默认值实现蓝光效果。如何实现且看XAML标记的向前兼容性。向前兼容性表示通过标记兼容性名字空间的Ignorable属性标识元素、属性和类,使它们可以动态的支持向前版本。 

<CLYLButton V2:Light="Green" 
      xmlns="...assembly-v1-uri..."
      xmlns:V2="...assembly-V2-uri..."
      xmlns:mc=http://schemas.micrsoft.com/winfx/2006/markup-compatibility
      mc:Ignorable="V2" />

这就利用了标记兼容性名字空间的Ignorable属性。mc:Ignorable=”V2”表示所有用V2前缀关联的名字空间中元素或者属性都是可以忽略的。如果现在只有V1版本的CLYLButton,上面的代码就被XAML加载器解释为:

<CLYLButton Light=”Blue” xmlns=”… assembly-V1-uri …”/>

如果现在有V2版本的CLYLButton,上面的代码将被XAML加载器解释为:

<CLYLButton Light=”Green” xmlns=”… assembly-V2-uri …”/>

XMAL标记兼容性除了可应用在属性上,还可以应用在元素之上。仍然通过例子进行说明,定义如下的一个类:

[ContentProperty("Buttons")]
    public class CElement {
        List<CLYLButton> _buttons = new List<CLYLButton>();
        public List<CLYLButton> Buttons { get { return _buttons; } 
}

关于ContentProperty的用法可以参考MSDN文档ContentPropertyAttribute Class

同样,我们可以如下编写XAML代码,使其可以同时兼容两个版本的CElement

<CElement mc:Ignorable="V2"
      xmln="...assembly-v1-uri..."
      xmlns:V2="...assembly-V2-uri..."
      xmlns:mc="http://schemas.micrsoft.com/winfx/2006/markup-compatibility">
      <CLYLButton Light="Blue" />
      <V2:CLYLButton Light="Green"/>
</CElement>

这样,如果加载器有V2版本,则Green属性值生效。如果没有则被忽略。类似地,我们还可以完全自动地处理名字空间的类:

<CElement mc:Ignorable="v2"
      xmln="...assembly-v1-uri..."
      xmlns:V2="...assembly-v2-uri..."
      xmlns:mc="http://schemas.micrsoft.com/winfx/2006/markup-compatibility">
      <V2:Favor/>
</CElement>

加载时,如果没有V2版本存在,Favor类实例同样将被忽略。

Markup Compatibility中,除了有前面介绍的CommentIgnorable属性修饰外,另一个有趣的就是AlternateContent。利用AlternateContent,我们能方便的实现可选内容。比如,我们的程序使用了V2版本AssemblyCLYLButton类,但是,如果没有找到这个Assembly,那么它对应的内容自动用另一个指定版本V1替换,而不是兼容性体现的忽略。看下面的例子:

<CElement mc:Ignorable="v2"
      xmln="...assembly-v1-uri..."
      xmlns:v2="...assembly-v2-uri..."
      xmlns:mc="http://schemas.micrsoft.com/winfx/2006/markup-compatibility">
      <mc:AlternateContent>
            <mc:Choice Requires="V2">
                <CLYLButton Light="Green" Shape="Dog" />
                <V2:Favor/>
            </mc:Choice>
            <mc:Fallback>
                <CLYLButton Light="Blue"/>
            </mc:Fallback>
       </mc:AlternateContent>
</CElement>

这一段XAML代码在有V1版本的Assembly时将被视为:

<CElement xmln="...assembly-v1-uri...">
        <CLYLButton Light="Blue"/>
</CElement>

如果有V2版本的Assembly,编译的结果如下:

<CElement xmln="...assembly-v1-uri...">
        <CLYLButton Light="Green"/>
       <Favor/>
</CElement>

闲话WPF之八(WPF的逻辑树和视觉树) 

这部分的内容来自于即将出版的新书《WPF Unleashed》的第三章样章。关于什么是逻辑树,我们先看下面的一个伪XAML代码的例子: 

<Window ......>
     <StackPanel>
      <Label>LabelText</Lable>
     </StackPanel>
</Window>

在这样一个简单UI中,Window是一个根结点,它有一个子结点StackPanel。而StackPanel有一个子结点Label。注意Label下还有一个子结点stringLabelText),它同时也是一个叶子结点。这就构成了窗口的一个逻辑树。逻辑树始终存在于WPFUI中,不管UI是用XAML编写还是用代码编写。WPF的每个方面(属性、事件、资源等等)都是依赖于逻辑树的。 

视觉树基本上是逻辑树的一种扩展。逻辑树的每个结点都被分解为它们的核心视觉组件。逻辑树的结点对我们而言基本是一个黑盒。而视觉树不同,它暴露了视觉的实现细节。下面是Visual Tree结构就表示了上面四行XAML代码的视觉树结构:

并不是所有的逻辑树结点都可以扩展为视觉树结点。只有从System.Windows.Media.VisualSystem.Windows.Media.Visual3D继承的元素才能被视觉树包含。其他的元素不能包含是因为它们本身没有自己的提交(Rendering)行为。

Windows Vista SDK Tools当中的XamlPad提供查看Visual Tree的功能。需要注意的是XamlPad目前只能查看以Page为根元素,并且去掉了SizeToContent属性的XAML文档。如下图所示:

注意图中工具栏特别标记的地方。我们可以看到Visual Tree确实比较复杂,其中还包含有很多的不可见元素,比如ContentPresenterVisual Tree虽然复杂,但是在一般情况下,我们不需要过多地关注它。我们在从根本上改变控件的风格、外观时,需要注意Visual Tree的使用,因为在这种情况下我们通常会改变控件的视觉逻辑。

WPF中还提供了遍历逻辑树和视觉树的辅助类:System.Windows.LogicalTreeHelperSystem.Windows.Media.VisualTreeHelper。注意遍历的位置,逻辑树可以在类的构造函数中遍历。但是,视觉树必须在经过至少一次的布局后才能形成。所以它不能在构造函数遍历。通常是在OnContentRendered进行,这个函数为在布局发生后被调用。

其实每个Tree结点元素本身也包含了遍历的方法。比如,Visual类包含了三个保护成员方法VisualParentVisualChildrenCountGetVisualChild

抱歉!评论已关闭.