两耳不闻窗外事,一心只读圣贤书

愚昧的编程狂热分子……

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

WPF入门系列——WPF体系结构(下)

延伸……

WPF的体系结构分层图:

System.Object

这是万物归宗……

 

 

System.Threading.DispatcherObject

作为大多数的WPF顶层的抽象基类,该类提供了并发和同步的基本构造,其作用不容忽视,我们一般称它为“调度对象”。DispatcherObject只有两个方法和一个属性,分别是:CheckAccess方法、VerifyAccess方法和Dispatcher属性。如前所说,整个WPF其实只有两个主线程,一个呈现线程,一个UI线程。只要WPF一运行,UI就会开出一个UI线程,这个线程负责将所有的“工作”排列,然后逐一去完成它。从而可以减免多线程的复杂度(如果你有看过线程处理模型,你会明白WPF在多线程中的处理是多么轻松的事儿)。CheckAccessVerifyAccess都是用来检查当前线程可否访问当前对象,比如一个ButtonButton派生于DispatcherObject)在创建的时候便会向当前线程进行关联,当鼠标移入Button,那么该Button便会发送一条消息至UI线程,说“我被移入”,然后UI线程便会执行这个工作项(如在Window 7系统中,当鼠标移入按钮时,按钮就会有一个渐变颜色的过程,十分靓丽)。这两个方法的区别在于,前者是检查,Check会返回一个bool值,而Verify是验证,一旦验证失败将会引发异常InvalidOperationException

对于Dispatcher属性,在MSDN上,这段解释,已经足够了:

WPF 中,只有创建 DispatcherObject 的线程才能访问该对象。例如,一个从主 UI 线程派生的后台线程不能更新在该 UI 线程上创建的 Button 的内容(Aloner注:比如此时正在执行别的线程任务,像什么聊天程序啊等)。为了使该后台线程能够访问 Button Content 属性,该后台线程必须将此工作委托给与该 UI 线程关联的 Dispatcher。这可以通过使用 Invoke BeginInvoke 来完成。Invoke 是同步操作,而 BeginInvoke 是异步操作。该操作将按指定的 DispatcherPriorityAloner注:线程优先级 添加到 Dispatcher 的事件队列中。

Invoke 是同步操作;因此,直到回调返回之后才会将控制权返回给调用对象。

BeginInvoke 是异步操作;因此,调用之后控制权会立即返回给调用对象。

BeginInvoke 返回一个 DispatcherOperation 对象,当委托位于事件队列中时,该对象可用于与委托进行交互。

BeginInvoke 返回的 DispatcherOperation 对象可以采用多种方式与指定的委托进行交互,例如:

· 当在事件队列中挂起执行时,更改委托的 DispatcherPriority

· 从事件队列中移除委托。

· 等待委托返回。

· 获取委托执行之后返回的值。

如果按同一个 DispatcherPriority 调用多个 BeginInvoke,将按调用发生的顺序执行它们(这就是,为什么在线程处理模型中,递归是重新向UI线程添加一个空闲时段执行的工作项)。

如果对某个已关闭的 Dispatcher 调用 BeginInvoke,则返回的 DispatcherOperation 的状态属性将设置为 Aborted

 

总结来讲,DispatcherObject对象给UI控件提供了一个可以向当前工作UI线程发布一个新的工作项,这样做主要目的是防止出现画面重绘工作突然被别的线程占用(比如一个动画效果一卡一卡的),从而导致画面怪异之类的,使其拥有更流畅的界面,如同MSDN所说,一点点延迟的迹象都会令用户不满。再者,这样可以减免多线程的繁琐步骤。

 

 

System.Windows.DependencyObject

 

可以这么说,所有的UI控件几乎都是派生于DependencyObject[依赖]属性系统对象)。该类对许多的派生类都启用了WPF的属性系统服务。

那么,什么是属性系统呢?在MSDN有这么一句话:

 

生成 WPF 时使用的主要体系结构原理之一是首选属性而不是方法或事件。属性是声明性的,使您更方便地指定意图而不是操作。它还支持模型驱动或数据驱动的系统,以显示用户界面内容。这种理念的预期效果是创建您可以绑定到的更多属性,从而更好地控制应用程序的行为

 

WPF中,一个主要思想是:属性优于方法和事件。它的基础是表达式,提供对属性的稀疏保存。其实,这个【属性系统】并不是什么太新颖的东西(他独特的地方在于表达式),在HTMLXML已经实现了,如在ASP.NET中:

ASP.NET

 

<asp:Label ID="Label1" runat="server" Text="Label" BackColor="Red" TabIndex="2" />

 

 

这个ASP.NETLabel控件,这些属性最初都是字符串,但事实上,TableIndex属性是一个int类型,而BackColor是一个Color类型,还有诸如“Font-Bold”,这个值只能是truefalse,但在用属性描述元素时,都会经过一个“转换器”,如将string的“Red”转换为“Colors.Red”枚举之类的。

 

XAML的属性系统主要功能是计算属性的值,并提供有关值已更改的通知。参与属性系统的另外一个关键类是DependencyPropertyDependencyProperty可实现在属性系统中注册依赖项属性,并提供有关每个依赖项属性的标识和信息,而DependencyObject作为一个基类则使对象能够使用依赖项属性DependencyObject 包括以下服务和特征:

l  依赖项属性宿主支持。通过调用 Register 方法并将该方法的返回值作为公共静态字段存储在类中,即可注册依赖项属性

l  附加属性宿主支持。通过调用 RegisterAttached 方法并将该方法的返回值作为公共静态只读字段存储在类中,即可注册附加属性。(还有一些附加成员要求;请注意,这代表附加属性的 WPF 特定实现。有关详细信息,请参见附加属性概述。) 然后,可以在从 DependencyObject 派生的任何类上设置附加属性。

l  存在于 DependencyObject 上的任何依赖项属性值的 getset clear 实用工具方法。

l  依赖项属性或附加属性的元数据、强制值支持、属性更改通知以及重写回调。同时,DependencyObject 类简化了依赖项属性的每所有者属性元数据。

l  派生自 ContentElementFreezable Visual 的类的公共基类。(另一个基元素类 UIElement 具有一个包括 Visual 的类层次结构。)

 

有关于依赖属性和附加属性有将会在后面的文章陆续发表。DependencyObject提供一套针对属性的处理,而该属性,不单单是一些简单的数据类型,甚至可以是自定义的数据类型,这个时候属性系统的作用就显示出来了。

 

 

 

System.Windows.Media.Visual

Visual 类是每个 FrameworkElement 对象要从其派生的基本抽象类。它还可以用作在 WPF 中编写新控件的入口点。在 Win32 应用程序模型中,可以采用多种方式将它视为等效于窗口句柄 (HWND)

Visual 对象是一个核心 WPF 对象,其主要作用是提供呈现支持。用户界面控件(例如 Button TextBox)派生自 Visual 类,并使用 Visual 定义的属性来保存它们所呈现的数据。Visual 对象可对下列功能提供支持:

·           输出显示:为可视对象呈现持久的序列化绘图内容。

·           转换:对可视对象执行转换。

·           剪辑:为可视对象提供剪辑区域支持。

·           命中测试:确定指定的坐标(点)或几何图形是否包含在可视对象的边界内。

·           边界框计算:确定可视对象的边框。

 

 

 

System.Windows.UIElement

UIElement 定义核心子系统,包括 LayoutInput 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 的特定布局的实现)之间的明确划分是希望的结果。目标就是提供在堆栈中处于较低位置的可扩展性点,这将允许外部开发人员可以在需要时创建自己的框架。

 


 

 

System.Windows.FrameworkElement

可以按两种不同的方式来看待 FrameworkElement。它对在 WPF 的较低层中的子系统引入一组策略和自定义项。它还引入了一组新的子系统。

FrameworkElement 引入的主要策略是关于应用程序布局。FrameworkElement UIElement 引入的基本布局协定之上生成,并增加了布局插槽的概念,使布局制作者可以方便地拥有一组面向属性的一致的布局语义。HorizontalAlignmentVerticalAlignmentMinWidth Margin 等属性使得从 FrameworkElement 派生的所有组件在布局容器内具有一致的行为。

利用 FrameworkElementWPF 的核心层中具有的许多功能可以更方便地进行 API 公开。例如,FrameworkElement 通过 BeginStoryboard 方法提供对动画的直接访问。Storyboard 提供一种针对一组属性为多个动画编写脚本的方式。

FrameworkElement 引入的两个最关键的内容是数据绑定和样式。

经使用 Windows 窗体或 ASP.NET 创建应用程序用户界面 (UI) 的用户应当对 WPF 中的数据绑定子系统较为熟悉。在上述每个系统中,可通过一种简单的方式来表达您希望将给定元素中的一个或多个属性绑定到一个数据片断。WPF 对属性绑定、变换和列表绑定提供全面支持。

WPF 中数据绑定的最值得关注的功能之一是引入了数据模板。利用数据模板,您可以声明性地指定某个数据片断的可视化方式。您可以将问题换个方向,让数据来确定将要创建的显示内容,而无需创建可绑定到数据的自定义用户界面。

样式实际上是轻量级的数据绑定。使用样式,您可以将共享定义的一组属性绑定到元素的一个或多个实例。通过显式引用(通过设置 Style 属性)或通过将样式与元素的 CLR 类型隐式关联,便可以将样式应用到元素。

FrameworkElement WPF 框架级元素类与 WPF 核心级 UIElement 表示服务集之间的连接点。有关这些概念的更多信息,请参见 WPF 体系结构

FrameworkElement 扩展了 UIElement 并添加了下列功能:

·           布局系统定义:FrameworkElement 为已定义为 UIElement 中的虚拟成员的某些方法提供特定 WPF 框架级实现。最值得注意的是,FrameworkElement 会密封某些 WPF 核心级布局重写,而改为提供派生类应重写的 WPF 框架级等效布局重写。例如,FrameworkElement 密封 ArrangeCore 但提供 ArrangeOverride。这些更改表明:在 WPF 框架级别上,存在可以呈现任何 FrameworkElement 派生类的完整布局系统。在 WPF 核心级别上,存在能够基于布局解决方案构建常规 WPF 的某些成员,但并未定义布局系统的实际引擎。有关更多信息,请参见布局系统

·           逻辑树:常规 WPF 编程模型通常用元素树表示。在 FrameworkElement 级别上,不仅支持将元素树表示为逻辑树,同时还支持在标记中定义此树。但请注意,FrameworkElement 特意未定义内容模型,并将这一责任留给了派生类。有关更多信息,请参见 WPF 中的树

·           对象生存期事件:通常,了解元素何时初始化(调用构造函数)或元素何时首次加载到逻辑树非常有用。FrameworkElement 定义了多个与对象生存期有关的事件,这些事件为涉及元素的代码隐藏操作(如添加更多子元素)提供了有用的挂钩。有关更多信息,请参见对象生存期事件

·           数据绑定和动态资源引用的支持:数据绑定和资源的属性级支持由 DependencyProperty 类实现并体现在属性系统中,但对存储为 Expression(作为数据绑定和动态资源基础的编程结构)的成员值进行解析的功能则由 FrameworkElement 来实现。有关更多信息,请参见数据绑定概述资源概述

·           样式:FrameworkElement 可定义 Style 属性。不过,FrameworkElement 还不能定义模板支持,也不支持修饰器。这些功能可由控件类(如 Control ContentControl)引入。

·           更多动画支持:在 WPF 核心级别上已定义了一些动画支持,而 FrameworkElement 通过实现 BeginStoryboard 和相关成员对此进行了扩展。

正如在类层次结构中看到的那样,许多 WPF 类都是从 FrameworkElement 派生而来的,其方式可以是直接派生,也可以是通过中间基类(如 Panel Control)派生。

如果要将 FrameworkElement 用作基类,需要先检查现有派生类。FrameworkElement 虽然提供了对一些基本方案的支持,但如果要构建用于在可扩展应用程序标记语言 (XAML) 中创建用户界面 (UI) 的块,还缺少一些元素所需的功能。例如,FrameworkElement 未定义任何真正的内容模型;作为一个基类,FrameworkElement 未定义可创建 XAML 子元素的属性。尤其是,您可能需要查看 Control ContentControl

 

http://i.msdn.microsoft.com/Global/Images/clear.gif

 

System.Windows.Controls.Control

 

控件的最重要的功能是模板化。如果您将 WPF 的组合系统视为一个保留模式呈现系统,则控件可通过模板化以一种参数化的声明性方式描述其呈现。ControlTemplate 实际上不过是一个用于创建一组子元素的脚本,同时绑定到由控件提供的属性。

Control 提供一组常用属性,如 ForegroundBackgroundPadding 等,模板创作者可以使用这些常用属性来自定义控件的显示。控件的实现提供了数据模型和交互模型。交互模型定义了一组命令(如窗口的关闭),以及到输入笔势的绑定(如单击窗口上角的红叉)。数据模型提供了一组属性,用于自定义交互模型或自定义显示(由模板确定)。

数据模型(属性)、交互模型(命令和事件)及显示模型(模板)之间的划分,使用户可以对控件的外观和行为进行完全自定义。

最常见的控件数据模型是内容模型。如果查看 Button 之类的控件,您会看到它具有一个类型为 Object 的名为“Content”的属性。在 Windows 窗体和 ASP.NET 中,此属性通常为一个字符串不过,这会限制您可以在按钮中输入的内容类型。按钮的内容可以是简单的字符串、复杂的数据对象或整个元素树。如果是数据对象,可以使用数据模板构造显示内容。


转载请注明:http://www.cnblogs.com/aloner

posted on 2009-07-21 17:48  Aloner  阅读(1124)  评论(0)    收藏  举报