一站式WPF--Window(一)
用戶通過Window與 Windows Presentation Foundation (WPF) 獨立應用程序進行交互。Window的主要用途是承載可視化數據并使用戶可以與數據進行交互的內容。獨立 WPF 應用程序使用 Window 類來提供它們自己的窗口。
這段是MSDN上對Window的描述,雖然翻譯的不是那么地道,也可以看出Window的兩大功能:一,承載可視化數據。二,使用戶可以與可視化數據進行交互。
在正式研究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的消息處理過程:
- 系統將消息發給隱藏的Win32-Window,在Dispatcher中GetMessage并分發到對應的窗口過程處理函數WndProc。
- WndProc里應是一個大的Switch-Case,用以處理不同的Window消息。按照消息的類別,WPF提供了不同的Manager來管理,這里的Manger并不是直接處理Window消息,并且并不是所有消息都經過WndProc再轉到Manager的。
比如說WM_KEYDOWN,Dispatcher調用GetMessage獲得消息后, 調用了ComponentDispatcher的RaiseThreadMessage方法(關于ComponentDispatcher,可以參閱Nick的文章),最終由KeyboardDevice產生Keyboard.PreviewKeyDownEvent這個路由事件(Routed Event)。 - 仍然以WM_KEYDOWN來說, InputManager找到這個Input發生的區域--Window,調用Window的RaiseEvent方法喚起Keyboard.PreviewKeyDownEvent這個路由事件。
- 路由事件沿著對象樹開始向下傳遞,方向是一去一回,由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的流程:
- Visual被添加到Visual Tree上。
- Visual Tree和Composite Engine通過Message Transport來進行通信,Message Transport包括Transport和Channel兩部分。Transport定義了傳輸的細節,Channel作用在Transport上,用來建立一個雙向的通信管道。這里,當Visual Tree被修改后,把被修改的Viusal數據通過Channel發送給Composition Engine。
- Composition Engine接收到Visual數據后,創建對應的Composition Node,并加入到Composition Tree中去。
- Composition Engine通知Render Engine開始繪制,Composition Tree中的節點是Rectangle,Ellipse等,DirectX不能識別這些數據,Render Engine要把這些數據轉化為DirectX可以識別的三角形,這個過程叫做Tessellate。
- 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.rzrgm.cn/Zhouyongh
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

浙公網安備 33010602011771號