作者:周永恒 
出处:http://www.cnblogs.com/Zhouyongh

在正式研究Window的功能之前,先来看一下,什么是Window?

什么是Window

  Window是Windows操作系统的核心,从表现上来说,Windows就是由许许多多的Window组成的,那么具体什么叫Window呢?

  通常意义上讲,我们所谓的Window是最外面的Window,也就是有着关闭、最小化的主Window。在Window编程中,调用CreateWindow来创建Window,通过设置dwStyle来指定样式,比如设置LBS_OWNERDRAWFIXED可以创建ListBox,设置BS_PUSHBUTTON可以创建Button等。CreateWindow的返回值就是窗口的句柄,从这个意义上来讲,在Win32世界中,万物皆Window,只是表现形式不同,那么WPF的Window对应什么呢?

WPF的Window

  WPF中的Window继承于ContentControl,内部可以承载一个Content,当然,借助于ItemsControl或Panel,Content也可以向下添加多个对象。这些对象都是WPF中的对象,也就是要承载的可视化数据。那么用户与可视化数据间的交互是怎么完成的呢?

  无论使用GDI绘制,或者使用DirectX绘制,在操作系统来看,Window都是一块持有句柄的有效区域。所有对该区域的操作,都会通过句柄来发送到Window对应的消息处理函数。也就是说,对外来看,WPF的Window依然是传统Win32的Window,对内它又把消息转化为Routed Event或者Command等来处理。关于这层处理和消息转化,要深入WPF的Window来谈起。

深入WPF的Window

  作为外界和可视化数据之间的桥梁,Window具有对内和对外两层作用。先说对内,Window内部可能会存在Button,ListBox等等控件,这些控件组成了一个对象树。树的子节点可能很多,但顶点只有一个,这个对象树是WPF的核心,Routed Event和Routed Command等都是依附于它的。抛开具体的对象树不说,我们要关注的是它的这种“众”字型的结构。如果你把这颗可视化数据组成的对象树想象成一个人的话,那么它的顶点就是它的头,我们对手臂和腿的操作只要对头喊话就可以了。换言之,对于WPF的Window

,它对内最关心的就是找到对象树的头(RootVisual),然后通过头把操作传递下去。

  从对外来看,操作系统关注的是注册Window的风格以及Rect。比如鼠标按键被按下时,按键消息被发送到系统的消息队列中,系统通过扫描所有注册窗口的Rect判断按键发生在哪个窗口中,再在适当的时机把按键消息从系统消息队列转移到创建窗口线程的消息队列中等待窗口处理。对于WPF的Window来说,同步这个Rect很重要,Window的UI是WPF的,但内部有个隐藏的使用CreateWindow创建的Win32-Window,当用户设置win.Width=60方法时要同步内部Window的Rect,反过来接收到WM_SIZE时也需要调用RootVisual去执行WPF的Measure、Arrange流程。

  用一个草图来表示Window的消息处理过程:

  1. 系统将消息发给隐藏的Win32-Window,在Dispatcher中GetMessage并分发到对应的窗口过程处理函数WndProc。
  2. WndProc里应是一个大的Switch-Case,用以处理不同的Window消息。按照消息的类别,WPF提供了不同的Manager来管理,这里的Manger并不是直接处理Window消息,并且并不是所有消息都经过WndProc再转到Manager的。
    比如说WM_KEYDOWN,Dispatcher调用GetMessage获得消息后, 调用了ComponentDispatcher的RaiseThreadMessage方法(关于ComponentDispatcher,可以参阅Nick的文章),最终由KeyboardDevice产生Keyboard.PreviewKeyDownEvent这个路由事件(Routed Event)。
  3. 仍然以WM_KEYDOWN来说, InputManager找到这个Input发生的区域--Window,调用Window的RaiseEvent方法唤起Keyboard.PreviewKeyDownEvent这个路由事件。
  4. 路由事件沿着对象树开始向下传递,方向是一去一回,由PreviewKeyDown到KeyDown。在这个传递过程中,相应的路由事件也被唤起,比如说如果此时焦点在Button上,当传递到Button时还会唤起Button的ClickEvent事件等。

  这些Manager,其中像ContentLayoutManager,本身是Internal的,仅仅是在Measure和Arrange的内部使用,这里只是表示消息经由分类后最终由这些Manager来管理。这个过程比较有意思的是Input,简单的来谈一谈它。

Input

  路由事件是WPF处理Input的核心,简略的说就是有一去一回从PreivewKeyDown到KeyDown这个过程,PreviewKeyDown的方向是从父到子,KeyDown的方向是从子到父。这个处理的过程不是本篇文章要谈的,重点是如何把一个简单的WM_KEYDOWN消息转化为PreivewKeyDown和KeyDown这两个路由事件。

  从图中可以看出,InputManager负责处理Input,一个Input,可能来自不同的设备--Mouse,Keyboard等等。InputManager要关注的地方有二:一,这个Input会转化成什么路由事件。二,这个Input作用在哪个UIElement上。第一个转化是由InputDevice来做的,这个InputDevice,具体有MouseDevice、KeyboardDevice等等。它会根据Window消息来生成对应的路由事件,然后把这些信息报告给InputManager。InputManager再根据这些信息找到作用的UIElement,然后唤起路由事件。

  说过了Input,重点来看Presentation,所谓Windows Presentation Foundation,显示一定是它的重点。

Presentation

  在前面中,介绍到了需要被显示的可视化数据,在WPF中是以对象树(确切说是Visual Tree)来组织的。那么它又是如何被画出来的呢?从对象树到真正Render之间又发生了什么呢?

  图例是WPF的架构图,其中重要的两个是PresentationCore和MilCore。在PresentationCore中,定义了Visual类,这个是WPF显示的核心,所有可以被显示的对象都直接或间接继承自Visual。当然,这里的Visual Tree就指Visual组成的树。Milcore(MIL -- Media Integration Layer),非托管代码,负责WPF和DirectX之间的通信,它主要由两部分组成:一,Composition Engine。二,Render Engine。前者负责创建Composition Tree,后者负责把Composition Tree转换成DirectX可以识别的Triangle并通知DirectX进行Render。

  简单说一下Render的流程:

  1. Visual被添加到Visual Tree上。
  2. Visual Tree和Composite Engine通过Message Transport来进行通信,Message Transport包括Transport和Channel两部分。Transport定义了传输的细节,Channel作用在Transport上,用来建立一个双向的通信管道。这里,当Visual Tree被修改后,把被修改的Viusal数据通过Channel发送给Composition Engine。
  3. Composition Engine接收到Visual数据后,创建对应的Composition Node,并加入到Composition Tree中去。
  4. Composition Engine通知Render Engine开始绘制,Composition Tree中的节点是Rectangle,Ellipse等,DirectX不能识别这些数据,Render Engine要把这些数据转化为DirectX可以识别的三角形,这个过程叫做Tessellate。
  5. Render Engine通知DirectX开始绘制(Render),DirectX在经过驱动(WDDM或者XPDM)通知显卡开始绘制像素到屏幕。

  在第一篇文章中,介绍了WPF的线程模型,WPF中线程一分为二,有UI线程和Render线程。UI线程是托管代码,管理Visual  Tree,用于处理输入,事件等。Render线程是非托管代码,在MIL中,仅用于绘制,把从UI线程传入的Visual数据转化并添加到Composition Tree进行绘制。在这个过程中,Render线程是被动的,它等待着UI线程向它传输数据并下达命令,也会把操作的结果(绘制完成,错误)等通过Channel报告给UI线程。

  这里要说说Viusal数据,也就是如何把Visual转化为Composition Node,在Avalon世界中,UCE(Unified Composition Engine)负责处理这层转化。当然,对UCE来说,它是不能识别WPF对象的,这种不能识别,就是说直接拿一个WPF的Line,它是不知道如何转化为相应Composition Node的,必须要WPF对象进行自描述,告诉UCE它对应什么Composition类型。UCE提供了IResource接口,这个接口定义了可以通过Channel传递到UCE的一系列方法。WPF的Visual实现了这一接口,Visual子类重写了其中的AddRefOnChannel方法并注明了其对应的Composition类型,比如说LineGeometry设置了它的类型是DUCE.Resource.Type_LINEGEOMETRY。UCE通过这些信息,就可以把传递过来的Visual数据转化为相应的Composition Node了。

  这里说到了UCE,每个WPF进程都有自己的UCE,并且在Avalon(Window Vista/Window 7)中,负责绘制桌面的DWM(Desktop Window Manager)也有它的UCE(也叫DUCE)。为了提供透明效果,桌面上的显示需要进行混合,DWM也是使用Composition Tree来管理窗口的,用两幅图来描述一下UCE的处理过程:

  最终,DWM经过混合后得到了桌面最后的透明效果。

  当然,整个过程不必细究,在WPF编程中也很少需要从UCE这个角度来考虑问题,只是帮助朋友们捋清一下思路,更好的理解WPF。讲过了这些底层的处理,把思路回归到Window上来,来看看Window是如何对这些进行整合的。

Inside Window

  前面提到,Window内部有一个隐藏的Win32-Window,用于接收消息,在WPF中,使用HwndSource来封装这个隐藏Window。那么从Visual Tree到Window之间又发生了什么呢?

  从Visual Tree来看,像提线木偶一样,控制它的头(顶点)就可以随意玩弄它。WPF提供了CompositionTarget以及PresentationSource来完成这些内部的处理,关于具体的流程,那么,就下篇吧。 ^_^

作者:周永恒 
出处:http://www.cnblogs.com/Zhouyongh

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

一站式WPF--Window(一)相关推荐

  1. WPF Window 窗口获得焦点和失去焦点事件

    Window 窗口获得焦点和失去焦点事件(窗口变为背景窗口.窗口切换等都引起窗口焦点失去) Activated获得焦点事件 和 Deactivated失去焦点的事件:     Activated: 获 ...

  2. wpf window 不执行show 就不能load执行_Numpy反序列化命令执行漏洞分析(CVE-2019-6446)附0day...

    1.介绍 NumPy 是 Python 机器学习库中之一,主要对于多为数组执行计算.NumPy 提供大量的 函数和操作,能够帮助程序员便利进行数值计算.在 NumPy 1.16.0 版本之前存在反序列 ...

  3. wpf window 不执行show 就不能load执行_关于机器学习中的Scikit-Learn,你不知道的10个实用功能...

    Scikit-learn是使用最广泛的Python机器学习库之一. 它具有标准化和简单的界面,用于预处理数据以及模型训练,优化和评估. 该项目最初由David Cournapeau开发的Google ...

  4. WPF window窗体的关闭事件Closing 和Closed

    当窗口关闭时,它会引发两个事件:Closing 和 Closed. Closing 在窗口关闭之前引发,它提供一种机制,可以通过这种机制来阻止窗口关闭. 系统会向Closing 事件处理程序传递一个 ...

  5. wpf异形按钮_WPF Window异形窗口演示

    我们先通过简单的效果展示,切换展示不同图片: 我们先定义图片资源文件,我们可以在window资源中定义,下面的在app.xaml文件来定义: xmlns="http://schemas.mi ...

  6. WPF自定义控件——顶级控件

    作为一个WPF程序员,我最希望看到的是WPF的应用,或者更确切的说是绚丽的应用,虽然限于自身的实力还不能拿出成绩来,但看到别人的作品时,心里还是有很大的宽慰--WPF是可以做出更加动人地产品的,只要你 ...

  7. Wpf消息循环之消息传递

    几天遇见一个问题需要检查某个wpf程序是否已经运行,如果没有运行则启动传递参数,如果已运行则需要直接传递消息.在没有运行 情况下传递参数很简单,我们只需要Process cmd窗口启动并传递参数,在程 ...

  8. 分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日)

    分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日) 本周Silverlight学习资源更新 Silverlight 定位 niejunhua [学习 ...

  9. WPF第一章(XAML前台标记语言(Chapter02代码讲解))

    XAML前台标记语言(Chapter2代码讲解)     很不好意思,工作有点忙,博客停了两天.相对于一门语言的学习,理论知识和实践必不可少,大多数时间我们要用,对于代码也是,一边不行可以看两遍,实在 ...

  10. WPF与Win32互操作

    一.WPF如何使用HWND 当您创建WPF Window时,WPF会创建顶级HWND,并使用HwndSource将Window及其WPF内容放入HWND中.应用程序中其余的WPF内容共享此单个HWND ...

最新文章

  1. java 位掩码_Java位掩码控制权限与()或(|)非(~)、的介绍
  2. DEDE-Function ereg_replace() is deprecated in ..line 2
  3. 【作死】更新macOS Mojave后Vagrant无法使用
  4. Unity4.6新UI系统初探(uGUI)
  5. Draw Circle 沿着圆运动~~
  6. Vue.js实现tab切换效果
  7. C++的int初始化
  8. 1010 Lehmer Code (35 分)(思路+详解+树状数组的学习+逆序对+map+vector) 超级详细 Come baby!!!
  9. python科学编程入门书_Python数据科学零基础一本通
  10. @Size注解无法使用
  11. C#组件系列——又一款Excel处理神器Spire.XLS,你值得拥有
  12. 搜狗搜索php,搜狗搜索公众号内容提取
  13. (十一) ELK快速入门
  14. Thompson Sampling(汤普森采样)
  15. 银河麒麟服务器系统使用的一些问题和解决方案
  16. 掘金总点赞量前 5000 排行发布 | 掘金总关注量前 5000 排行
  17. Java的热门应用有哪些
  18. 天津落户——历程简记
  19. 我的武林秘籍设计模式之策略模式
  20. matlab求解二维矩阵并画图,Matlab教程2_ 绘图 _ 二维(2)

热门文章

  1. oracle中oltp场景,Oracle-OLAP和OLTP解读
  2. 利用条形码生成器应用程序在Word 2013中轻松制作条形码的方法
  3. packetTracer作业
  4. 10-25 顺序表 23-5-11
  5. CTFHub | PHPINFO
  6. Mac OS电源管理大解析
  7. 通过设置让火狐浏览器自动清缓存,不需要自己每次手动清除了
  8. 谁拉大了中国制造的贫富差距?
  9. IDF实验室之牛刀小试啥?
  10. xcode 报Remote object proxy returned error: Error Domain=NSCocoaErrorDomain Code=4099 The connec...