WPF 的结构

 这个话题介绍了WPF的类层次结构图,它覆盖了WPF的大部分主要子系统,并且描述了它们之间的相互作用。它还详细的介绍了WPF的设计者们所做的选择。

这个话题包含了如下几个部分:

System.Object

System.Threading.DispatcherObject

System.Windows.DependencyObject

System.Windows.Media.Visual

System.Windows.UIElement

System.Windows.FrameworkElement

System.Windows.Controls.Control

Summary

Related Topics

System.Object

主要的WPF程序模型是通过托管代码暴露的。在早期的设计阶段,在系统的托管组件和非托管组件之间的区别有过激烈的争论。CLR提供了大量的特性使得开发更加多产和健壮(包括内存管理,错误处理公共类型系统等),但是他们的代价很大。

WPF的主要组件如下图所示。图的红色部分(PresentationFramework, PresentationCore, 和milcore)是WPF的主要代码部分。在他们之中,只有milcore是非托管的。Milcore是非托管的代码写的,目的是为了和DirectX紧密集成。所有的WPF是通过DirectX 引擎来显示的,能够有效的用于硬件和软件显示。WPF也需要精细的控制内存和执行。Milcore的引擎性能表现得非常敏感,并且需要放弃许多CLR的优势来获得性能。

 

WPF托管和非托管之间的交流在后面的话题里会讨论到。托管编程的其他部分将在下面描述。

System.Threading.DispatcherObject

WPF的大部分对象继承自在DispatcherObject, 为处理并行和线程提供了基本的结构。WPF是通过调度器基于消息系统实现的。这与Win 32的消息泵很接近;实际上,WPF调度器使用了User32的消息用于执行交叉线程调用。

这里有两个核心的概念用于理解并且讨论WPF的并行-调度器(dispatcher)和线程关联(thread affinity)。

在WPF的设计阶段,目标是成为一个单线程执行程序,除了一个非线程的“affinitized”模型。当组件使用执行线程的标识符来存储状态类型时,会调用线程关联。通常的形式是使用线程局部存储(TLS)去存储状态。线程关联要求在操作系统里,每个执行的逻辑线程被一个物理线程所拥有,他可以成为内存密集型。在最后,WPF的线程模型与线程关联的正存在的User32单线程执行线程模型同步。这样做的主要原因是互操作性-系统如OLE2.0,剪贴板,IE都要求单线程(STA)执行。

你有了对象和STA线程,你需要一个方法用于线程之间的交流,并且验证你正在一个正确的线程上。调度器的作用就在于此。调度器是一个基本的消息调度系统,具有多个优先级队列。消息包含了原始的输入通知(鼠标移动),框架函数(布局),或者用户命令(执行这个方法)。通过从DispatcherObject派生,你可以创造一个CLR对象具有STA行为,并且在创建时给出一个指向调度器的指针。

System.Windows.DependencyObject

在建立WPF时使用了一个主要的架构理念是属性比方法和事件的优先级高。声明的属性允许你更容易的指定意图,而不是行为。同样也支持模型驱动,数据驱动和用于显示用户接口内容的系统。这个理念旨在创建更多的属性让你可以去绑定,以用于更好的控制应用程序的行为。

为了拥有更多被属性驱动的系统,需要一个比CLR更丰富的属性系统。这种丰富性的简单例子就是改变通知。为了是你能够双向绑定,你需要两边的绑定都支持改变通知。为了有绑定属性的行为,当属性值改变的时候,你需要被通知。在微软的.NET Framework里有一个接口,INotifyPropertyChange,允许一个对象去公布改变的状态,无论如何这是可选的。

WPF提供了一个很丰富的属性系统, 从DependencyObject继承的类型。这个属性系统真的是一个“依赖”属性系统,当依赖属性改变时,在属性表达式和自动重新验证属性值之间跟踪了依赖关系。例如,假设你有一个继承的属性(FontSize),如果在继承了该值的父控件上的属性值改变,系统将会自动的更新。

WPF的属性系统基础是一个属性表达式的概念。在WPF的第一个发布版中,属性表达式系统被关闭了,并且所有的表达式作为framework的一部分。表达式是为什么属性系统没有数据绑定,形式或者击沉硬编码,而是在框架内提供了一个后来层。

属性系统同样为属性值提供了疏存储(sparse storage)。因为对象可以有很多个属性(甚至是上百个),而且大部分属性都有默认值(通过样式设置)而不是每个对象的实例需要定义所有的属性。

属性系统最终新特性的理念是附加属性。WPF元素建立在组成和组件重用的原则上。在通常的情况下,一些容器元素(例如Grid布局元素)需要附加数据到每个子元素以控制他们的行为(例如行或列)。不是关联每个元素的所有属性,而是任何对象可以为任何其他对象提供属性值。这与JavaScript的”expando”特性相似。

System.Windows.Media.visual

随着系统的定义,接下来是在屏幕上进行像素绘制。Visual类提供了visual对象的构造树,每一个都可选的包含了如何呈现这些指令的绘图指令和元素据。Visual设计为非常轻巧灵活,所以大部分特征没有公共的API接口,主要依赖于回掉函数。

Visual真的是WPF组件系统的入口点。Visual是一个介于两个子系统:托管API和非托管milcore的连接点。

 WPF呈现的数据由milcore转化为非托管的数据结构。这些结构,称之为组件节点,在每个节点里显示了一个层次的表现树。这些树,正如下图的右图所示,只能通过消息协议获得。

当WPF运行时,你创造的Visual元素及其衍生类型,在组件树内通过消息协议进行内部交流。每个WPF的Visual可能创造一个,无或者多个组件节点。

这里有一个重要的结构细节需要注意---整个Visual树和绘制指令会被cacah。在图形WPF使用了一个保留的呈现系统。这使得系统能够快速的刷新而不需要组件系统重新调用用户代码。这有利于阻止应用程序外观的不响应。

另一个重要的细节是图上没有明显的显示系统是如何进行组合的。

在User32和 GDI, 系统是一个即时模式的剪辑系统。当一个组件需要被呈现的时候,系统基于剪切边界,组件是不允许接触到像素的。然后组件在盒子里绘制像素。这个系统在有内存限制的系统工作的非常好,因为当一些改变时,只会影响到接触的组件-同一个像素不会有两个组件。

WPF使用了一个“绘制算法”的绘制模型。这意味着不是剪辑每个组件,而是每个组件是从后面向前面绘制。这允许了每个组件基于前面的组件来显示。这个模型的优势是你可以有复杂的,部分透明的形状随着今天的图形硬件,这个模型是相对较快的。

正如前面所提到的,WPF的理念是一个更加声明性的,“属性中心”程序模型。在一个Visual系统,这显示在如下几个有兴趣的地方。

第一,              如果你思考了保留模式的图形系统,这真是一个逐渐从画线/画线的模型,成为了一个数据导向的模型-新线/新线。这种改变使得数据驱动能够在使用属性时绘制指令渲染更复杂的操作。从Drawing衍生的类型有效的渲染了模型。

第二,              如果你计算动画系统,你将看到大部分完整描述。而不需要程序员去计算下一个位置,下一种颜色。你可以通过动画对象的属性表达动画。开发者和设计者都可以表达这些动画的意图,并且系统会决定一种最有效的方法来完成它。

System.Windows.UIElement

UIElement定义了核心子系统用于布局,输入和事件。

布局是WPF的核心。在许多的系统里要么是一个固定的布局模型(HTML支持三种布局模型:flow, absolute, table)或者没有模型用于布局(User32用于支持绝对位置)。WPF开始就假设了开发人员和设计人员想要一个灵活的,容易扩展的布局模型,能够被属性值驱动,而不是必要的逻辑。在UIElement级别,介绍了用于布局的基本---一个Measure和Arrange传递的两阶段模型。

Measure允许一个组件去决定它将要多大的尺寸。这是从Arrange里分离出来的阶段,因为有许多的环境,例如一个父元素将会要求子元素去measure很多次去决定最优的位置。实际上是父元素要求子元素去测量声明另一个WPF的关键原理---内容尺寸。WPF里所有的control都有能力去为内容划分自然的尺寸。这使得定位更加容易,并且允许元素在任何尺寸进行动态的布局。在Arrange阶段允许父元素为每个子元素进行定位。

大部分时间经常花在谈论WPF的输出端-可视化和相关的对象。然而在输入端也有一个巨大的创新。可能最根本性的改变是用于WPF的输入模型是利用输入事件路由到系统的一致性模型。

在内核模式设备驱动下,输入源作为一个信号,并且通过一个涉及到Windows kernel和User32的复杂进程路由到正确的进程和线程。一旦输入的User32的消息路由到WPF,他转换为WPF的原始输入信息,并且发送到调度器。WPF允许原始的输入事件转化为多个真实的事件。使“MouseEnter”这样的功能在低级别的操作系统里保证交付。

每个输入事件转化为至少两个事件-一个“preview”事件和一个真实的事件。在WPF里所有的事件有一个通过元素树进行路由的概念。事件被称为”bubble”,如果他们从目标向上传递到根,如果重根开始传送到目标则称为“tunnel”。输入preview事件隧道,能够使树里的元素有机会过滤或者响应事件。常规事件(非-preview)事件冲目标冒泡到根部。

Tunnel和bubble阶段的分离实现了很多特性,例如快捷键在一个复合的世界里一致的工作。在User32你将通过一个包含所有快捷键的单个全局表来实现快捷键去支持(Ctrl+N映射到new)。你应用程序的dispatcher将会调用TranslateAccelerator将输入信息传递到User32,并且决定是否有任何匹配的注册快捷键。在WPF里这将不能实现,因为系统是完全“组合”的任何元素都可以使用快捷键。输入的两阶段模型使得组件实现他们自己的“TranslateAccelerator”。

进一步说,UIElement同样引进了命令绑定的概念。WPF的命令系统允许开发员定义命令结束点的功能---一些继承了ICommand接口的东西。命令绑定使得元素定义一个基于输入快捷键(Ctrl+N)和命令(New)的映射。输入快捷键和命令定义都是可扩展的。在使用时,可以连接在一起。这使得它更加容易的允许一个最终用户在应用程序里去自定义他们想要的快捷键绑定。

在主题的这一点上,WPF的核心特性 – 特性实现了PresentationCore的组装,已经成为了焦点。当编译WPF的时候,foundational pieces(例如Measure 和Arrange的布局) 和 framework pieces的清晰分离是一个理想的结果。它的目标是提供一个栈低处的扩展点,允许外部的开发员去创建他们自己的框架。

System.Windows.FrameworkElement

FrameworkElement可以从两个不同的方向来看。在WPF的底层子系统引入了一系列的政策以及自定义。它同样引进了一系列的新子系统。

FrameworkElement的首要政策是围绕应用层。FrameworkElement建立在UIElement引进的基本布局契约,并且增加了布局“槽”的概念,使得它更加布局者更加容易的属性驱动布局语法的一致性。属性,例如HorizontalAlignment, VerticalAlignment, MinWidth, Margin给出的所有组件继承至布局容器FrameworkElement一致性的行为。

FrameworkElement同样给WPF核心层的许多特性提供了一个更容易的API。例如,FrameworkElement提供了直接的动画的直接接口通过BeginStoryboard方法。Storyboard提供了一种方法,多个动画脚本针对一组属性。

两个最重要的事情是FrameworkElement引入了数据绑定和样式。

WPF的数据绑定子系统与大家而言应该是相对熟悉的,它已经应用到WinForm和Asp.net里用于创建应用程序接口。在每个系统中,有一个简单的方法表达你想从一个被绑定元素给定的元素里想要一个或多个属性。WPF完全支持属性绑定,转换和list绑定。

WPF里很有趣的一个数据绑定特征是数据模版。数据模版允许你声明指定数据应该怎样可视化。你可以以数据为中心,来决定应该创建什么来显示,而不是创建自定义的接口来绑定到数据。

样式是数据绑定的轻巧形式。使用样式你可以从一个元素的一个或多个实例定义绑定属性集。应用到元素上的样式可以通过显式引用或者隐式关联CLR元素类型。

System.Windows.Controls.Control

Control最显著的特征是模版。如果你认为WPF的保留系统是一种呈现系统的保留模式,模板允许控件它参数化,声明性的呈现。一个ControlTemplate仅仅是一个用于创建子元素的脚本,绑定到Control提供的属性。

Control提供了一系列死板的属性,Foreground, Background, Padding,仅举几例,模版作者可以自定义控件的显示,为了控件实现提供数据模型和交互模型。交互模型定义了一些列的命令(例如关掉window)和绑定到快捷键(例如点击窗口上方红色的X)。数据模型提供了一系列的属性值即可自定义交互模型,也可自定义的显示(通过模板决定)。

数据模型(属性)和交互模型(命令和事件)的分离,和显示的模型(模板)完成了空间的外观和行为的自定义化。

控件的数据模型有一个共同的方面是内容模型。如果你看到了一个控件,例如Button, 你将看到它有一个Object类型的属性“Content”,在Winform和asp.net,这个属性是一个简单的字符串,一个复杂的数据对象,或一个完整的元素树。在一个数据对象的情况下,数据模板用于构造一种显示。

总结

WPF致力于允许我们创建动态的,数据驱动的表现系统。系统的每一部分设计为利用创建对象的属性集来驱动行为。数据绑定是系统的基本部分,并且与每一层集成在一起。

传统应用程序创建了一个显示层,然后与某些数据绑定。在WPF,空间的任何东西,显示的任何方面,都是由一些数据绑定类型生成的。Button里面的文字是通过创建一个组成的控件显示的。

当你开始开发WPF基础应用时,感觉应该是很熟悉的。你可以设置属性,使用对象和数据绑定的方式与WinForm以及ASP.NET一致。为了更进一步调查WPF的结构,你将找到可能性去创造更丰富的应用,利用数据作为应用程序的驱动核心。

posted @ 2013-06-18 15:17  龙之云  阅读(1006)  评论(0编辑  收藏  举报