WPF全景体验
UI的历史
从视窗界面的出现开始,用户界面(UI)就进入了一个特殊的历史时期。在随后几年里,出现了很多具有历史意义的界面风格。现在,经典的Windows窗口风格已经存在了10多年,即使是深受用户喜爱的Windows XP风格也已出现五年有余。无疑大家与我一样,期待着全新视觉界面的出现。另一方面,当今计算机处理芯片飞速发展,特别是显示芯片,显示卡几乎成为了一台微型的计算机。但是,其强大的处理能力却没有得到充分的应用。目前,显示卡GPU(Graphics Processing Unit)的处理能力大多只应用在游戏和多媒体领域。为了给用户提供最强的体验,满足不断提高的用户视觉需求,Windows Vista引入了全新的图形子系统——WPF。她能充分利用显示卡的处理能力提供给用户最绚的视觉效果。
在Windows Vista之前,Windows平台的图形系统主要有:GDI、GDI+、Direct3D。其中,GDI的应用领域最为广泛。GDI图形系统已经形成了很多年。她提供2D图形处理、文本处理,以及受限的图像处理功能。虽然在一些显示卡上支持部分的GDI加速,但是其效果与当今的Direct3D相比还是很微弱。GDI+开始出现是在
图1:NT系统图形结果简图
为了兼容性,GDI和GDI+在Windows Vista中仍被支持,对她的改进也主要集中在安全性和客户相关问题上,功能性基本上没有任何的改进。
下一代图形系统——WPF
在Windows Vista中,GDI、GDI+和WPF并行存在。但是,WPF所有的提交都不依赖于GDI和GDI+,而是Direct3D。并且所有的Primitive都是通过Direct3D的本地接口实现的。请参考图2的WPF核心组件。
图2:WPF核心组件
注意图中的milcore组件,她的职责是完成与Direct3D的交互。并且出于效率考虑,milcore由非托管代码实现。我们还注意到,PresentationFramework和PresentationCore都位于通用语言运行(CLR)之上。很显然,WPF的大部分代码都是以托管形式存在的。当然,在一些显示卡不支持所需要的功能时,WPF也提供了稍微低效的软件实现,以此来支持在某些PC上运行WPF应用程序。
对于开发人员,我们最关心的WPF所提供的功能。从图3的WPF基本功能结构中我们就可以看到其丰富的功能。是的,WPF集成处理了过去的图形、多媒体、文档等功能。为她们的编程开发提供了统一的编程模型。我们不再需要去关注媒体、文档的内部处理区别,将开发人员与处理细节真正地隔离开来,这是非常让人激动的功能!
图3:WPF基本功能结构
事实上,在Windows Vista中,图形系统最关键的改进就是引入了新的显示驱动模型(Windows Display Driver Model)。前面曾提及我们有大量的显示卡资源被闲置,为了安全、高效地利用这些资源,WDDM驱动模型提供了与操作系统虚拟化内存、让多个应用程序同时运行相类似的功能。她虚拟化了显示卡的资源(主要是显示内存),提供了一个调度程序,使得多个基于Direct3D的应用程序可以共享显卡(比如WPF应用程序和基于WPF的Windows Vista桌面窗口管理器),WDDM的健壮性、稳定性也得到了显著提高。大量的驱动操作从内核(Kernel)模式移动到了用户(User)模式,这样不仅提高了安全性,还简化了显示驱动的开发过程。
图4:WPF图形系统
另外,在Windows Vista中存在两个版本Direct3D:Direct3D 9和Direct3D 10。WPF依赖于Direct3D 9,这样能更广泛的解决兼容性问题。毕竟,硬件要求越低,能支持的PD越多。另一个非常重要的原因就是为Windows Vista的服务器版本提供方便,因为服务器版本的Windows Vista对显示卡和Direct3D基本上没有任何特殊的要求,同时Windows Vista也支持Direct3D 10。Direct3D 10依赖于WDDM,只能在Windows Vista上使用。由于Windows XP没有WDDM,虽然Microsoft做了很大的努力来改善Windows XP中Direct3D 9相关的驱动,提高内容的显示质量,但是由于Windows XP中没有虚拟化显示卡资源,只能强制所有的应用程序都采用软件提交。
基于WPF的开发
那么,对于开发人员,基于WPF的应用程序开发又会是怎样呢?为了实现应用程序UI和逻辑的分离、设计人员可以更快速、高效地完成UI,微软在WPF中引入了一种全新的基于XML的、声明式编码语言——XAML。让我们先睹为快,用XAML实现一个“Hello, World”:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml "
Title=”Hello, World”>
<Label>Hello, World</Label>
</Window>
使用过XML或者某些脚本的读者应该对这种编程方式非常亲切。是的,XAML改变了我们过去的编程方式。关于XAML和WPF实在有太多的内容,限于篇幅,这里我将向大家介绍WPF中两个非常重要的概念:视觉树和逻辑树,它们对我们理解WPF编程模式有非常大的帮助。
为了更加形象地理解视觉树和逻辑树,我们看一个稍微复杂些的“Hello, World”程序:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title=”Hello, World”>
<StackPanel>
<Label>Hello, World</Label>
</StackPanel>
</Window>
在这个简单的应用程序中,Window是一个根结点,它有一个子结点StackPanel,而StackPanel有一个子结点Label,注意Label下还有一个子结点String( 也就是标签控件显示的字符串内容),它同时也是一个叶子结点。这四个对象构成了窗口的一个逻辑树结构。逻辑树结构始终存在于WPF的UI中,不管UI是用XAML编写还是用代码编写。WPF的每个方面(属性、事件、资源等等)都是依赖于逻辑树的。
图5:WPF逻辑树结构
如果将逻辑树和视觉树对比理解,逻辑树通常是指我们在UI逻辑中涉及到的界面元素。而视觉树是指系统在显示UI过程中显示的所有元素,它暴露了视觉的实现细节。
图6:WPF的视觉树
并不是所有的逻辑树结点都可以扩展为视觉树结点。只有从System.Windows.Media.Visual和System.Windows.Media.Visual3D继承的元素才能被视觉树包含。其他的元素不能包含是因为它们本身没有属于自己的提交(Rendering)行为。
在Windows Vista SDK Tools当中的XamlPad提供查看视觉树的功能。需要注意的是XamlPad目前只能查看以Page为根元素,并且去掉了SizeToContent属性的XAML文档。
我们可以看到视觉树确实比较复杂,其中还包含有很多的不可见元素,比如ContentPresenter。视觉树虽然复杂,但是在一般情况下,我们不需要过多地关注它。我们从根本上改变控件的风格、外观时,需要注意视觉树的使用,因为在这种情况下我们通常会改变控件的视觉逻辑。
WPF中还提供了遍历逻辑树和视觉树的辅助类:System.Windows.LogicalTreeHelper和System.Windows.Media.VisualTreeHelper。注意遍历的位置,逻辑树可以在类的构造函数中遍历。但是,视觉树必须在经过至少一次的布局发生之后才能形成,所以它不能再构造函数遍历。通常是在OnContentRendered进行,这个函数在布局发生后被调用。
其实,每个树结点元素本身也包含了遍历的方法。比如,Visual类包含了三个保护成员方法VisualParent、VisualChildrenCount、GetVisualChild。通过它们可以访问结点的父元素和子元素。而对于FrameworkElement,它通常定义了一个公共的Parent属性表示其逻辑父元素。特定的FrameworkElement子类用不同的方式暴露了它的逻辑子元素。比如部分子元素是Children Collection,有时Content属性,Content属性强制元素只能有一个逻辑子元素。
WPF在Windows Vista中的应用——桌面窗口管理器
桌面窗口管理器(Desktop Window Manager,DWM)是Windows Vista中的一个新组件。她建立在WPF核心图形层组件(milcore)基础之上。她利用WPF的Composition引擎进行多个窗口内容的合成。她的出现几乎改变了Windows Vista中应用程序的屏幕像素显示方式。
通过桌面窗口管理器的桌面合成,应用程序的显示不再是直接画到屏幕上,而是一个显示内存中的一个离屏Surface。然后由桌面窗口管理器将这些Surface合成显示到屏幕之上。
从用户体验的角度看,启用DWM后,提供的视觉效果有毛玻璃框架、3D窗口变换动画、窗口翻转和高分辨率支持。其中最明显的特征有:任务栏窗口的实时缩略图;Alt-Tab和Win-Tab组合键所看到的效果。
在桌面窗口管理器(
在Windows Vista中窗口的毛玻璃效果非常绚丽。在窗口的边界,我们可以看到窗口下面的内容。这其中同时具有透明和模糊的效果。但是,在实现毛玻璃时,为了不让下面的窗口内容过于清晰影响上面的窗口,DWM组还对下面的窗口实现了模糊效果。其中的实现要点有:
1、 模糊下面的内容,这是由自定义的像素Shader实现。这个Shader是一个完全运行于GPU的小程序,它可以并行处理多个像素。
2、 模糊只是针对窗口边界下的部分内容。这些内容需要从不同的缓冲中提取出来。
3、 摸索的方法类似于平均值处理:一个像素的值等于其邻居像素的平均值。
众所周知,Direct3D支持多个Surface,最后显示不同Surface时是通过Flip(翻转)实现的,DWM也是如此。这样实现的结果就是,不会再出现以前WM_PAINT消息响应带来的的Tearing效果,从而桌面变得更平滑。
现在,我们的桌面可以称得上是一个全屏幕的Direct3D应用程序。不管是老式只支持帖图加速的图形处理器,还是新型的高速图形处理器,我们都需要操作图形处理器的存储系统(显存)。这引出了两个重要的问题:
1、在窗口很多时,运行DWM需要的内存将是一个问题,它随着用户的窗口数增加而增加。
2、DWM会与其它的应用程序共享内存资源。比如DirectX应用程序、视频回放和WPF应用程序等等。
微软利用全新的Windows显示驱动模型WDDM解决这些问题。WDDM是Windows Vista及后续操作系统中的DirectX驱动模型。WDDM主要提供三项功能:
1、虚拟化显示内存。这是最关键的功能。虚拟化显示内存后,这就意味着显示内存与系统内存一样。我们知道,在系统内存中如果内存分配完毕,此时却还有新的分配要求,就会产生第二存储页面,然后由系统管理存储页面和主存储页面的算法和机制来控制内存分配。现在,主存储是显示内存,而第二存储页面是系统内存。在显示存储和系统内存都分配完后,将使用磁盘作为视频内存表面。当然,这种情况比较少见,但是这样的设计使得WDDM足够的健壮,应用程序的可靠性也得到增强。对WDDM而言,它将实现非常关键的功能:执行内存的分配、实现分配内存和真正的显示内存的控制。WDDM本身也在不断的改进中。
2、允许与GPU的交互。既然WDDM已经实现了显示内存的虚拟化,那么这就意味着WDDM具有调整应用程序的GPU命令优先级的功能。这种功能通常是由WDDM调度程序实现。因此WDDM必须能中断GPU的某些操作,并保存操作的上下文,以备在必要时恢复操作继续运行。基于这项功能,WDDM提供了两种级别的调度支持:
(1)、基本调度。它是基于DirectX9的WDDM驱动和硬件所支持的调度粒度。也就是说单独的Primitive和Shader程序不能被中断,上下文交换必须在它们完成后进行。
(2)、高级调度。它是基于DirectX10的WDDM驱动和硬件所支持的调度粒度。这种调用支持比Primitive和Shader更细粒度的中断。注意,虽然DirectX10支持高级调度,但是它并不是DirecX10所必须的。也就是说,只有部分硬件支持高级调度。桌面窗口管理器使用DirectX9,因此它是支持基本调度。
3、允许Direct3D表面可以跨进程共享。共享Direct3D表面对于重定向Direct3D应用程序非常重要。因为Vista必须要和以前的应用程序兼容,就必须支持以前用GDI、DirectX编写的应用程序。WDM必须把这些应用程序的窗口重定向到Surface,然后由WDM统一合成,最后显示单一的桌面Surface。需要注意的一点是:WDM只重定向Top-level的窗口。而对于MDI应用程序,它所有的Top-level窗口、子窗口会被合成为一个单独的Surface,然后交给DWM合成。
前进中的WPF
另外,WPF对XPS等文档的打印输出也得到了极大的改善。XPS文档本身的规范也极大地提高了其打印的质量,XPS文档的规范可以参考MSDN的资料。除了打印,Vista操作系统中对远程的改进也部分依赖于WPF,比如有远程协助、远程桌面和终端服务等等。它们的实现过程是通过发送一系列的“远程”命名到客户端,客户根据自己PC的性能和命名进行显示,这样显示的质量能得到极大的提高。WPF中所有的提交都是矢量形式的,我们可以对图像或窗口进行任意级的放缩,而图像的质量不会有任何的损耗。
在WPF中,对Direct3D进行各种封装。当然,如果你本身对Direct3D/OpenGL很熟悉,也可以直接在WPF中使用。封装后的Direct3D更容易使用,并且Web应用程序(XBAP)也可以使用Direct3D。在WPF中使用的Direct3D,没有直接用非托管代码控制所拥有的灵活性,也不能直接对硬件进行底层控制。
WPF对某些多媒体的功能支持还需要依赖之前的技术,比如DirectShow。当我们进行音频视频的捕捉或者其它任务时,只能直接用DirectShow实现,然后再用HwndHost嵌入到WPF内容当中。
利用类似的内容嵌套技术,我们可以在WPF应用程序中显示自定义格式的内容。通过提供自定义的DirectShow CODEC,然后用Media元素实现和WPF内容毫无限制的集成。
虽然WPF被设计作为下一代的图形系统,但当前版本的WPF对一些Win32功能还没有很好的支持,比如WMF/EMF文件,单个像素宽度线条等等。对于这些需求还需要使用GDI/GDI+来实现。
结束语
本文对下一代图形系统WPF进行了全面的介绍,包括与NT系统中图形系统的对比、WPF编程的核心概念以及WPF在Windows Vista中的应用。希望通过对这些内容的介绍,让读者对WPF有一个全面的感性认识。而且,我们有理由相信:随着Windows Vista的普及,WPF的应用将更加广泛,过去痛苦的UI编程将不再出现。让我们一起来体验吧!
Desktop Window Manager,DWM)中,我们的每个窗口都用一个Surface表示,都可以视为是3D的网格。虽然每个窗口仍是一个矩形,但它们都位于一个3D空间之中。窗口操作(比如最大化,还原等等)的实现已经发生了变化,它们都是对网格进行3D变换实现的,这与以往有了很大的区别。