主题提供 Windows Presentation Foundation (WPF) 类层次结构,涵盖了 WPF 的大部分主要子系统,并描述它们是如何交互的。node
WPF 主要编程模型是经过托管代码公开的。在 WPF 的早期设计阶段,曾有过大量关于如何界定系统的托管组件和非托管组件的争论。CLR 提供一系列的功能,能够令开发效率更高而且更加可靠(包括内存管理、错误处理和常规类型系统等),但这是须要付出代价的。算法
下图说明了 WPF 的主要组件。关系图的红色部分(PresentationFramework、PresentationCore 和 milcore)是 WPF 的主要代码部分。在这些组件中,只有一个是非托管组件 – milcore。milcore 是以非托管代码编写的,目的是实现与 DirectX 的紧密集成。WPF 中的全部显示都是经过 DirectX 引擎完成的,所以硬件和软件呈现都很高效。WPF 还要求对内存和执行进行精细控制。milcore 中的组合引擎受性能影响关系大,须要放弃 CLR 的许多优势来提升性能。express

本主题的后面部分将讨论 WPF 的托管和非托管部分之间的通讯。下面介绍托管编程模型的其他部分。编程
WPF 中的大多数对象是从 DispatcherObject 派生的,这提供了用于处理并发和线程的基本构造。WPF 基于调度程序实现的消息系统。其工做方式与常见的 Win32 消息泵很是相似;事实上,WPF 调度程序使用 User32 消息执行跨线程调用。windows
要讨论 WPF 中的并发,首先必须真正理解两个核心概念 – 调度程序和线程关联。缓存
在 WPF 的设计阶段,目标趋向于单一线程的执行,但这不是一种与线程“关联的”模型。当一个组件使用执行线程的标识来存储某种类型的状态时,将发生线程关联。最多见的形式是使用线程本地存储 (TLS) 来存储状态。线程关联要求执行的每一个逻辑线程仅由操做系统中的一个物理线程所拥有,这将占用大量内存。最后,WPF 的线程处理模型保持与具备线程关联的单一线程执行的现有 User32 线程处理模型同步。主要缘由是互操做性 – 相似于 OLE 2.0 的系统、剪贴板和 Internet Explorer 均须要单一线程关联 (STA) 执行。数据结构
假设您具备带有 STA 线程的对象,则须要一种方式来在线程之间通讯,并验证您是否位于正确的线程上。调度程序的做用就在于此。调度程序是一个基本的消息调度系统,具备多个按优先级排列的队列。消息的示例包括原始输入通知(鼠标移动)、框架函数(布局)或用户命令(执行此方法)。经过从 DispatcherObject 派生,您能够建立一个具备 STA 行为的 CLR 对象,并在建立时得到一个指向调度程序的指针。并发
生成 WPF 时使用的主要体系结构原理之一是首选属性而不是方法或事件。属性是声明性的,使您更方便地指定意图而不是操做。它还支持模型驱动或数据驱动的系统,以显示用户界面内容。这种理念的预期效果是建立您能够绑定到的更多属性,从而更好地控制应用程序的行为。app
为了更加充分地利用由属性驱动的系统,须要一个比 CLR 提供的内容更丰富的属性系统。此丰富性的一个简单示例就是更改通知。为了实现双向绑定,您须要绑定的双方支持更改通知。为了使行为与属性值相关联,您须要在属性值更改时获得通知。Microsoft .NET Framework 具备一个 INotifyPropertyChange 接口,对象经过该接口能够发布更改通知,但该接口是可选的。框架
WPF 提供一个丰富的属性系统,该属性系统是从 DependencyObject 类型派生的。该属性系统实际是一个“依赖”属性系统,由于它会跟踪属性表达式之间的依赖关系,并在依赖关系更改时自动从新验证属性值。例如,若是您具备一个会继承的属性(如 FontSize),当继承该值的元素的父级发生属性更改时,会自动更新系统。
WPF 属性系统的基础是属性表达式的概念。在 WPF 的初版中,属性表达式系统是关闭的,表达式都是做为框架的一部分提供的。表达式导致属性系统不具备硬编码的数据绑定、样式调整或继承,而是由框架内后面的层来提供这些功能。
属性系统还提供属性值的稀疏存储。由于对象能够有数十个(若是达不到上百个)属性,而且大部分值处于其默认状态(被继承、由样式设置等),因此并不是对象的每一个实例都须要具备在该对象上定义的每一个属性的彻底权重。
属性系统的最后一个新功能是附加属性的概念。WPF 元素是基于组合和组件重用的原则生成的。某些包含元素(如 Grid 布局元素)一般须要子元素上的其余数据才能控制其行为(如行/列信息)。任何对象均可觉得任何其余对象提供属性定义,而不是要将全部这些属性与每一个元素相关联。这与 JavaScript 中的“expando”功能类似。
定义一个系统后,下一步是将像素绘制到屏幕上。Visual 类用于生成可视化对象的树,每一个对象能够选择性地包含绘制指令以及有关如何呈现这些指令(剪辑、变换等)的元数据。Visual 旨在极致轻量且灵活,所以大部分功能均未公开 API,而且很是依赖受保护的回调函数。
Visual 其实是 WPF 组合系统的入口点。Visual 是托管 API 和非托管 milcore 这两个子系统之间的链接点。
WPF 经过遍历由 milcore 管理的非托管数据结构来显示数据。这些结构(称为组合节点)表明层次结构显示树,其中每一个节点都有呈现指令。只能经过消息传递协议来访问此树(下图右侧所示)。
当对 WPF 编程时,您将建立 Visual 元素及派生的类型,它们经过此消息传递协议在内部与此组合树进行通讯。WPF 中的每一个 Visual 能够不建立组合节点,也能够建立一个或多个组合节点。

请注意一个很是重要的体系结构细节 – 可视对象和绘制指令的整个树都要进行缓存。在图形方面,WPF 使用一个保留的呈现系统。这可使系统以一个高刷新率重绘系统,而且不会发生组合系统阻止对用户代码的回调。这有助于防止出现应用程序无响应的状况。
关系图中不十分引人注意的另外一个重要细节是系统实际上如何执行组合。
在 User32 和 GDI 中,系统是在一个即时模式剪辑系统上工做。当须要呈现一个组件时,系统会创建一个剪辑边界,不容许组件接触该边界以外的像素,而后会要求此组件在该框中绘制像素。此系统在内存受限的系统上工做良好,由于当某些内容更改时,只须要处理受影响的组件便可 – 不会有两个组件对一个像素的颜色更改起做用。
WPF 使用“绘画器的算法”绘制模型。这意味着并非剪辑每一个组件,而是要求从显示内容的背面至正面来呈现每一个组件。这容许每一个组件在先前的组件的显示内容上绘制。此模型的优势是您能够生成部分透明的复杂形状。配合当今的新式图形硬件,此模型相对较快(建立 User32/GDI 时则否则)。
如上面所述,WPF 的一个核心原理是转移到一个更具声明性且“以属性为中心”的编程模型。在可视化系统中,这会表现为须要关注的两种状况。
首先,若是您考虑保留的模式图形系统,则其实是从命令性 DrawLine/DrawLine 类型模型移动到面向数据的模型 new Line()/new Line()。经过这一贯数据驱动的呈现移动,能够在使用属性表达的绘制指令上进行复杂的操做。从 Drawing 派生的类型其实是用于呈现的对象模型。
第二,若是评估动画系统,您将看到它几乎是彻底声明性的。无须要求开发人员计算下一位置或下一颜色,您能够将动画表示为动画对象上的一组属性。因而,这些动画能够表示开发人员或设计人员的意图(在 5 秒内将此按钮从一个位置移动到另外一个位置),系统就能够肯定完成此任务的最有效方式。
UIElement 定义核心子系统,包括 Layout、Input 和 Event。
Layout 是 WPF 中的一个核心概念。在许多系统中,可能有一组固定的布局模型(HTML 支持三种布局模型:流、绝对和表),也可能没有布局模型(User32 实际仅支持绝对定位)。WPF 先假设开发人员和设计人员但愿有一个灵活的可扩展布局模型,该模型多是由属性值而不是命令性逻辑驱动的。在 UIElement 级别,会引入布局的基本协定 - 具备 Measure 和 Arrange 处理过程的两阶段模型。
Measure 容许组件肯定它要采用的大小。此阶段独立于 Arrange,由于在许多情形下,父元素会要求子元素测量若干次以肯定其最佳位置和大小。父元素要求子元素测量这一事实体现了 WPF 的另外一关键原则 – 内容大小。WPF 中的全部控件支持调整到内容原始大小的功能。这使本地化更加容易,并容许在调整大小时对元素进行动态布局。Arrange 阶段容许父元素定位并肯定每一个子元素的最终大小。
一般会花费大量的时间来讨论 WPF 的输出端(Visual 及其相关对象)。然而,在输入端也有许多创新。WPF 输入模型的最基本更改也许是一致模型,输入事件经过系统借助此模型进行路由。
输入是做为内核模式设备驱动程序上的信号发出的,并经过涉及 Windows 内核和 User32 的复杂进程路由到正确的进程和线程。与输入相对应的 User32 消息路由到 WPF 后,就会转换为 WPF 原始输入消息,并发送到调度程序。WPF 容许将原始输入事件转换为多个实际事件,容许在保证传递到位的状况下在较低的系统级别实现相似“MouseEnter”的功能。
每一个输入事件至少会转换为两个事件 – “预览”事件和实际事件。WPF 中的全部事件都具备经过元素树路由的概念。若是事件从目标向上遍历树直到根,则被称为“冒泡”,若是从根开始向下遍历到目标,它们被称为“隧道”。输入预览事件隧道,使树中的任何元素都有机会筛选事件或对事件采起操做。而后,常规(非预览)事件将从目标向上冒泡到根。
分割隧道和冒泡阶段使快捷键等功能在复合世界中表现一致。在 User32 中,您能够经过使用一个全局表来实现快捷键,该表中包含您但愿支持的全部快捷键(Ctrl+N 映射为“新建”)。在应用程序的调度程序中,您能够调用 TranslateAccelerator,它会探查 User32 中的输入消息,并肯定是否有任何消息与已注册的快捷键匹配。在 WPF 中,上述内容不会起做用,由于系统是彻底“可组合”的 – 任何元素均可以处理和使用任何快捷键。将这个两阶段模型用于输入,将容许组件实现其本身的 "TranslateAccelerator"。
为了进一步深化此功能,UIElement 还引入了 CommandBindings 的概念。WPF 命令系统容许开发人员以命令终结点(一种用于实现 ICommand 的功能)的方式定义功能。命令绑定使元素能够定义输入笔势 (Ctrl+N) 和命令(“新建”)之间的映射。输入笔势和命令定义都是可扩展的,而且能够在使用时联系到一块儿。这使得一些操做(例如,容许最终用户自定义其要在应用程序内使用的键绑定)显得可有可无。
至此,本主题已重点讨论了 WPF 的“核心”功能 - PresentationCore 程序集中实现的功能。当生成 WPF 时,基础部分(例如带有 Measure 和 Arrange 的布局的协定)和框架部分(例如 Grid 的特定布局的实现)之间的明确划分是但愿的结果。目标就是提供在堆栈中处于较低位置的可扩展性点,这将容许外部开发人员能够在须要时建立本身的框架。
能够按两种不一样的方式来看待 FrameworkElement。它对在 WPF 的较低层中的子系统引入一组策略和自定义项。它还引入了一组新的子系统。
FrameworkElement 引入的主要策略是关于应用程序布局。FrameworkElement 基于 UIElement 引入的基本布局构建而成,并增长了布局“插槽”的概念,使布局创做者能够更方便地拥有一组一致、属性驱动的布局语义。HorizontalAlignment、VerticalAlignment、MinWidth 和 Margin 等属性使得从 FrameworkElement 派生的全部组件在布局容器内具备一致的行为。
利用 FrameworkElement,WPF 的核心层中具备的许多功能能够更方便地进行 API 公开。例如,FrameworkElement 经过 BeginStoryboard 方法提供对动画的直接访问。Storyboard 提供一种针对一组属性为多个动画编写脚本的方式。
FrameworkElement 引入的两个最关键的内容是数据绑定和样式。
曾经使用 Windows Forms或 ASP.NET 建立应用程序user interface (UI) 的用户应当对 WPF 中的数据绑定子系统较为熟悉。在上述每一个系统中,可经过一种简单的方式来表达但愿将给定元素中的一个或多个属性绑定到一个数据片断。WPF 全面支持属性绑定、转换和列表绑定。
WPF 中数据绑定的最值得关注的功能之一是引入了数据模板。利用数据模板,您能够声明性地指定某个数据片段的可视化方式。您能够将问题换个方向,让数据来肯定将要建立的显示内容,而无需建立可绑定到数据的自定义用户界面。
样式其实是轻量级的数据绑定。使用样式,您能够将共享定义的一组属性绑定到元素的一个或多个实例。经过显式引用(经过设置 Style 属性)或经过将样式与元素的 CLR 类型隐式关联,即可以将样式应用到元素。
控件的最重要的功能是模板化。若是将 WPF 的组合系统视为一个保留模式呈现系统,则控件可经过模板化以一种参数化的声明性方式描述其呈现。ControlTemplate 实际上不过是一个用于建立一组子元素的脚本,同时绑定到由控件提供的属性。
Control 提供一组经常使用属性,如 Foreground、Background、Padding 等,模板创做者可使用这些经常使用属性来自定义控件的显示。控件的实现提供了数据模型和交互模型。交互模型定义了一组命令(如窗口的“关闭”),以及到输入笔势的绑定(如单击窗口上角的红叉)。数据模型提供了一组属性,用于自定义交互模型或自定义显示(由模板肯定)。
数据模型(属性)、交互模型(命令和事件)及显示模型(模板)之间的划分,使用户能够对控件的外观和行为进行彻底自定义。
最多见的控件数据模型是内容模型。若是查看 Button 之类的控件,您会看到它具备一个类型为 Object 的名为“Content”的属性。在 Windows Forms和 ASP.NET 中,此属性一般为一个字符串 – 不过,这会限制您能够在按钮中输入的内容类型。按钮的内容能够是简单的字符串、复杂的数据对象或整个元素树。若是是数据对象,可使用数据模板构造显示内容。
传统的应用程序建立一个显示内容,而后绑定到某些数据。在 WPF 中,关于控件的全部内容、显示内容的全部方面都是由某种类型的数据绑定生成的。经过在按钮内部建立复合控件并将其显示绑定到按钮的内容属性,就会显示按钮内的文本。
当开始开发基于 WPF 的应用程序时,您应对其感到很是熟悉。在其中设置属性、使用对象和数据绑定的方式与您使用 Windows Forms或 ASP.NET 是极其类似的。若是对 WPF 体系结构有更深的了解,您就可以建立更丰富的应用程序,这些应用程序在根本上会将数据视为应用程序的核心驱动力。