1. 程式人生 > >WPF的消息機制(二)- WPF內部的5個窗口之隱藏消息窗口

WPF的消息機制(二)- WPF內部的5個窗口之隱藏消息窗口

release 直接 3.0 函數 行為 交互 sdn temp spl

原文:WPF的消息機制(二)- WPF內部的5個窗口之隱藏消息窗口

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/powertoolsteam/article/details/6109036

?

目錄

WPF的消息機制(一)-讓應用程序動起來

WPF的消息機制(二)-WPF內部的5個窗口

(1)隱藏消息窗口

(2)處理激活和關閉的消息的窗口和系統資源通知窗口

(3)用於用戶交互的可見窗口

(4)用於UI窗口繪制的可見窗口

WPF的消息機制(三)-WPF輸入事件的來源

WPF的消息機制(四)-WPF中UI的更新

?

WPF內部的5個窗口

對於Windows系統來說,它是一個消息系統,消息系統的核心就是窗口。對於WPF來說也是如此。那麽WPF內部為什麽需要窗口,又存在哪些窗口呢?

在上一篇,我們頻繁的提及“線程”,“Dispatcher”其實,運行WPF應用程序所在的線程就是WPF所謂的UI線程,在Application.Run之後,調用Dispatcher.Run時會檢查當前線程是否已經存在了一個Dispatcher對象,如果沒有就構造一個,在這裏,一個線程對應一個Dispatcher。因此,WPF的對象在獲取this.Dispatcher屬性時,不同對象取的都是同一個Dispatcher實例。另外,前面提到的“消息循環”,“消息隊列”等都是Win32應用程序的概念,我們知道,提起這些概念,必然會跟Win32的“窗口”,“Handle”,“WndProc”之類的概念離不開,那麽WPF裏面究竟有沒有“窗體”,“Handle”,“WndProc”呢?

我想說的是:有,還不止一個,只不過沒有暴露出來,外面不需要關心這些。

通常情況下,一個WPF應用程序在運行起來的時候,後臺會創建5個Win32的窗口,幫助WPF系統來處理操作系統以及應用程序內部的消息。在這5個窗口中,只有一個是可見的,可以處理輸入事件與用戶交互,其他4個窗口都是不可見的,幫助WPF處理來自其他方面的消息。接下來我會來介紹究竟這5個Win32的窗口如何幫助WPF處理消息,我會根據每個窗口創建的順序來介紹。

?

隱藏消息窗口

創建時機:在Application的構造函數調用基類DispatcherObject的構造函數的時候,會創建一個Dispatcher對象,在Dispatcher的私有構造函數當中。

用途:實現WPF線程模型的異步調用。

談到異步調用,相信許多人都不陌生。WinForm下,我們通常為了使一些花費較多時間的方法調用不影響UI的響應,會將這個操作分為很多步,然後使用BeginInvoke調用每一步,這樣UI響應就不會被阻塞。BeginInvoke的本質是往消息隊列當中PostMessage,而不是直接調用,與此同時,UI行為(MouseMove)導致系統也往消息隊列當中PostMessage更新UI,但由於彼此花費的時間很短,就感覺兩個消息是被同時處理似的,界面就不會覺得被阻塞了。WPF同樣面臨這樣的問題,他是如何解決的呢?在這裏Window 1#起著至關重要的作用。通過下面一副圖我們來看看Window 1#在做什麽事情?

技術分享圖片

WPF也是通過BeginInvoke來解決的,而Wpf的BeginInvoke是在Dispatcher上面暴露了,因為整個消息系統都是Dispatcher在協調。從上面圖可以看出Dispatcher在調用BeginInvoke之後所經歷的流程,最終是什麽時候Foo()被真正執行的。

第一步,就是將調用的Delegate和優先級包裝成一個DispatcherOperation放入Dispatcher維護的優先級隊列當中,這個Queue是按DispatcherPriority排序的,總是高優先級的DispatcherOperation先被處理。關於優先級相關知識可以參考MSDN對WPF線程模型的解釋。

第二步,往當前線程的消息隊列當中Post一個名為MsgProcessQueue的Message。這個消息是WPF自己定義的,見Dispatcher的靜態構造函數當中的

_msgProcessQueue = UnsafeNativeMethods.RegisterWindowMessage("DispatcherProcessQueue");

這個消息被Post到消息隊列之前,還要設置MSG.Handle,這個Handle就是Window 1#的Handle。指定Handle是為了在消息循環Dispatch消息的時候,指定哪個窗口的WndProc(窗口過程)處理這個消息。在這裏所有BeginInvoke引起的消息都是Window1#的窗口過程來處理的。

第三步,消息循環讀取消息。

第四步,系統根據獲取消息的Handle,發現跟Window1#的Handle相同,那麽這個消息派發到Window1#的窗口過程,讓其處理。

第五步,在窗口過程中,優先級隊列當中取一個DispatcherOperation。

第六步,執行DispatcherOperation.Invoke方法,Invoke方法的核心就是調用DispatcherOperation構造時傳入的Delegate,也就是Dispatcher.BeginInvoke傳入的Delegate。最終這個Foo()方法就被執行了。

通過上面的六步過程,一次Dispatcher.BeginInvoke就被處理完成。而這個過程需要消息不斷的流動,就必須加入消息隊列,最後還要特定的窗口過程處理,而核心的東西就是這個隱藏的Window1#,他在WPF當中只負責處理異步調用,其他的消息他不關心,剩余的4個窗口在處理。這個Window1#在WPF當中被包了一層殼子,如果感興趣,你可以去查看類型MessageOnlyHwndWrapper。

WPF的消息機制(二)- WPF內部的5個窗口之隱藏消息窗口