1、第七章 使用者介面與圖形子系統與Microsoft XP作業系統不同, Windows CE將Win32 API的使用者介面(User32)和圖形設備介面(GDI32)合併成一個新的模組gwes.exe,稱為GWE 子系統。GWE 是一個縮寫詞,其中G代表Graphics(圖形),W代表Window Manager(視窗管理器),E代表Event Manager(事件管理器)。 GWE子系統是使用者、應用程式和作業系統之間的圖形使用者介面。GWE支援組成Windows CE圖形使用者介面的所有視窗、對話方塊、控制項、功能表和資源,使使用者能夠通過執行功能表命令、單擊按鈕等操作來控制應用程式。G
2、WE還以點陣圖、游標、文字以及圖示等形式為使用者提供資訊。即使不具備圖形使用者介面的基於Windows CE的平臺也使用了GWE的基本視窗和訊息功能,這些功能提供了在使用者、應用程式和作業系統之間進行通訊的方法。本章主要分析GWE子系統的體系結構以及相關的實作程式碼,主要涉及的根源程式位於Windows CE .NET原始程式碼樹中的CEROOTPrivateWinceosCoreosGWE目錄下。需要說明的是,受Microsoft Shared Source License的限制, GWE子系統中只有GDI部分公開了少量的原始程式碼,User 部分的原始程式碼均未公佈,因此本章內容主要著重於
3、GWE子系統體系結構的分析。7.1 GWE概述對應於桌面Windows作業系統中的 User32,Windows CE GWE子系統中的USER部分包含了使用者輸入系統(User Input System)、事件管理器(Event Manager)和視窗管理器(Window Manager)三個組件。其中使用者輸入系統接收來自鍵盤、滑鼠和手寫筆等設備的訊息,事件管理器管理訊息和訊息佇列,而視窗管理器將訊息回應發送到對應的視窗以實作特定的顯示。GWE子系統中的GDI(Graphics Device Interface,圖形設備介面)部分依靠二維圖形包中的API 函式將使用者的繪圖操作通過直線、曲
4、線、填充區域、點陣圖和文字等GDI基本操作來實作,此外GDI還支援點陣字體和True Type字體。基於Windows CE的程式設計要通過訊息迴圈。訊息迴圈是在Windows 應用程式中的一種迴圈,它負責接收系統傳送過來的訊息,並且把它們發送到相對應的視窗,直到系統表明所有的訊息都發送完畢,訊息迴圈才結束。它包含在WinMain函式中。處理訊息迴圈的函式是WndProc 。下面是GWE 的一些特殊功能:GWE即時追蹤執行系統的工作情況,所以如果沒有在三分鐘內給設備一些指令的話, GWE將關閉這個設備。GWE還添加了儲存空間不足時的提示和解決方案。雖然在技術實作上儲存空間不夠的提示和解決沒有必
5、要一定成為GWE的一部分,但是在這裏加上一些程式碼實作這種功能是最方便的。因為GWE和輸入相關。如果儲存空間不足,將出現記憶體不足的對話方塊。同時會彈出一個視窗,給出正在執行的程式碼,讓使用者選擇關掉其中的一個或幾個。Windows CE添加儲存空間不足時的解決程式是基於執行設備的考慮。因為執行Windows CE的設備一般沒有硬碟,同時也沒有足夠的記憶體,一旦在執行時超過了記憶體的容量,將沒有硬碟保存,所以必須在有可能超過儲存容量的地方結束一些程式以節省空間。當然這在執行桌面Windows作業系統的設備上不常見到。在使用者輸入系統的設計上, Windows CE也儘量減少執行緒數。它將鍵盤設
6、備和觸控設備的處理直接交給了GWE,並且將這些設備所傳送的訊息直接放在全域 (Global)設備佇列中。GWE在設計之初有以下的一些目標: 在小螢幕設備上執行比較穩定: GWE設計的時候提供了足夠的函式以便使用者寫出的程式碼在小螢幕上能夠執行的很好。 和 Win32的相容性:和Win32 API的相容性是GWE設計時最重要的目標,因為這可以讓那些會Windows程式設計的人能夠在 Windows CE上程式設計。 支援廣泛的顏色位元深度和色彩模式,如1、2、4、8、16、24和32位顏色。這些在桌面Windows作業系統中是沒有提供的。 加上了調節低電壓和對電源的管理的功能設計:這使得系統在判
7、斷使用者沒有使用設備後關掉設備。在上述目標之中,Windows CE最基本的設計目標是和Win32 API相容,它的大部分程式碼也沒有重新寫。和桌面Windows 作業系統一樣,Windows CE的視窗管理器也包括對話方塊管理器、Splash類別和控制項等。具體的結構如圖 7.1所示。Windows CE中的非使用者輸入區和桌面Windows作業系統中的有一些區別。它和視窗管理器結合在一起。在Windows CE中,一個獨立的功能表列和工具列將佔據太多的空間,所以Windows CE將功能表列和工具列結合成一個新的控制項,稱為命令列。以前的功能表被放在非使用者區而工具列放在使用者區,現在功能
8、表和別的控制項一樣被放在命令列中,並且由命令列實作功能表控制項的功能。對話方塊管理器位於視窗管理器的上層。當實作一個對話方塊管理器時,同時會有一個訊息方塊產生。訊息方塊位於對話方塊管理器的上層,並且所有的控制項(比如編輯框、列表框、下拉式列示方塊等)都位於對話方塊管理器的上層。功能表與以前有一些不同,現在也作為控制項處理。GWE還包括記憶體不足(OOM,out of memory)對話方塊和記憶體不足控制碼。記憶體不足對話方塊在使用者記憶體空間不夠時彈出,並將正在執行的程式列出來,讓使用者選擇關閉其中的一個或幾個。同樣,記憶體不足控制碼也是由於儲存空間比較小才被加到Windows CE系統中去
9、的。圖7.1 Windows CE圖形子系統的結構7.2 使用者輸入系統GWE的 USER部分包括訊息佇列、事件管理器和使用者輸入系統三個核心元件。其中最重要的是使用者輸入系統,它負責接收從鍵盤、滑鼠以及觸控板發出的訊息。使用者輸入系統將使用者從鍵盤、滑鼠等設備輸入的訊息通過訊息傳送機制送到相對應的視窗中。USER利用產生視窗和訊息傳送這兩部分功能來實作使用者輸入系統。使用者輸入系統的結構如圖7.2所示,主要組件包括: Msgque:訊息佇列,這是任何需要訊息傳遞的地方所必須的部分,因為要實作使用者輸入就必須能夠把輸入的資訊傳給所需要的視窗。 Wmbase:這部分元件的作用是建立視窗,為視窗提
10、供視窗處理函式WndProc ,並且給它發送訊息。 Winmgr:視窗管理器,它負責把繪圖操作的結果在螢幕上顯示出來。下面分兩部分對以上內容進行詳細闡述,其中將視窗的產生和管理合併成輸入系統。圖7.2 GWE 的USER部分的主要結構7.2.1 訊息佇列訊息佇列有兩個功能:它不僅負責接收訊息並將訊息發送到相對應的視窗,而且它還負責保存輸入狀態資訊,比如游標的大小、提示符閃爍率等。在訊息傳送時,有兩個最基本的函式:SendMessage和PostMessage。其中SendMessage函式採用的是同步訊息傳送機制:發送者發出訊息,接收者接收訊息,而發送者則等待訊息被處理完成。訊息佇列和執行緒存
11、在一一對應的關係。通過深入瞭解API函式可以發現,當把訊息傳送到對應的視窗時,每一個視窗對應一個執行緒,SendMessage函式先將訊息發送到相對應的視窗,在後臺可以發現和這個視窗聯繫的執行緒在同步的回應。如果呼叫SendMessage函式的執行緒和視窗所在的執行緒是同一個執行緒,這次呼叫就會退化為呼叫WndProc函式的一個子程式(因為WndProc是這個視窗的預設處理函式)。函式PostMessage的工作和SendMessage有所不同。PostMessage採用的是非同步訊息傳送機制:它僅僅將訊息封裝以後送進訊息佇列中,訊息的發送者繼續執行,並不管訊息在什麼時候被處理。一段時間後,訊
12、息從訊息佇列中被取出,送到相對應的地方等待處理。所以,每一個視窗都和一個與特定執行緒相關的訊息佇列聯繫在一起,視窗成為訊息傳送的目的地。執行緒、訊息佇列和視窗以及視窗處理函式緊密聯繫在一起,它們之間的關係是一個視窗擁有它自己的執行緒、自己的訊息佇列和相對應的視窗處理函式。在WinMain( )函式中經常看見這樣的訊息迴圈:while (GetMessage(DispatchMessage(當一個執行緒呼叫GetMessage函式時,相對應執行緒的訊息佇列會發生下述變化。在訊息處理過程中最簡單的部分是已發送的訊息(將訊息放到別的執行緒的訊息佇列中,但是對本執行緒來講是發送訊息)。在執行程式碼過程
13、中,訊息佇列中的一部分指標指向即將被回應的訊息佇列,當函式GetMessage被呼叫時,它查看將被回應的訊息佇列。如果所需要的訊息在佇列中,函式GetMessage將這個訊息送到特定的地方然後返回。接著主迴圈將呼叫函式DispatchMessage。函式DispatchMessage首先查看送到的訊息所包含的資訊,找到和這條訊息相關的視窗,以及和視窗相關的處理程式,將訊息和相對應的參數發送給處理程式,讓相對應的處理程式做出回應。對訊息的處理就是這樣,將訊息從佇列中取出,送到相對應的位置等待,當輪到處理這條訊息時,找到相對應的視窗和視窗處理程式,呼叫處理程式做出訊息回應。從訊息發送方面看,呼叫函
14、式PostMessage 作用就是將訊息封裝以後送到相對應的訊息佇列中去,不管訊息是通過和函式PostMessage 同一個執行緒處理還是不同的執行緒處理,結果都一樣,就是訊息被從佇列中取出然後送到了相對應的視窗處理函式等待回應。同樣,如果訊息處理的執行緒正確執行,將訊息從佇列中取出然後發送出去,從外面看不出有什麼變化,如圖7.3所示。圖7.3 PostMessage的訊息處理流程函式SendMessage 和函式PostMessage相比有一點區別,它和訊息處理是同步的,所以當發現函式SendMessage 返回時,這說明訊息已經被處理完了。訊息有兩種途徑被發送到處理程式,這主要取決於訊息發
15、送到的視窗是在函式SendMessage的執行緒還是別的執行緒。最簡單的情況是將訊息傳送到和函式SendMessage在同一個執行緒的視窗。函式SendMessage發現產生視窗的執行緒和自己所在的執行緒是同一個執行緒,就直接呼叫視窗處理程式(而不是將它放到訊息佇列中),然後返回,這裏的訊息處理僅僅是呼叫一個子函式。SendMessage 的處理流程如圖 7.4所示。圖7.4 SendMessage 的同執行緒訊息處理流程複雜一點的情況是將訊息傳送到屬於另外一個執行緒的視窗。此時,訊息佇列有一個子佇列負責處理傳送到特定視窗和執行緒的訊息。函式SendMessage發現這個訊息將要傳送到另一個執
16、行緒的訊息佇列,就將它封裝然後放到這個執行緒的訊息佇列中去,然後等待。所以現在就有一個正在呼叫的執行緒等待一個內部Win32事件物件的回應。這時,執行緒的等待並不影響它接收訊息,執行緒可以接收訊息並且對它做出處理。這裏的關鍵點是當訊息傳送到屬於別的執行緒的視窗時,實際上是視窗所屬的主執行緒在執行程式碼。發送訊息的執行緒呼叫函式SendMessage ,但是是別的執行緒的WndProc函式在接收訊息。如圖7.5所示。說的更詳細一點,當送出訊息的執行緒被掛起(Hook)等待回應時,它還必須即時檢查訊息是否被送回來。接收到送出執行緒送出訊息的視窗可以立即回發一個訊息,所以當執行緒被掛起等待回應時,它
17、依然可以接收送過來的訊息。在發送訊息的區別上,除了函式SendMessage發出訊息後必須等待訊息被處理而函式PostMessage不需要外,函式 SendMessage還被函式GetMessage所操作。應用程式的主迴圈從不檢查函式GetMessage的呼叫是否返回。函式GetMessage執行時僅僅按優先順序處理訊息,並將訊息分別發送到相對應的地方。表7.1列舉了一些訊息的等級。圖7.5 SendMessage 的異執行緒訊息處理流程表7.1 訊息的級別級別 訊息類型1 發送訊息呼叫SendMessage發送的訊息,有最高級的優先順序2 發送訊息呼叫PostMessage 發送的訊息,有次
18、高級的優先順序3 WM_QUIT訊息4 WM_PAINT訊息5 WM_TIMER訊息所以,如果一個視窗或者一個執行緒被掛起,最常見的原因是一些執行緒在等待Win32事件物件的回應而不是在自己的訊息迴圈中。同時如果這個執行緒被掛起,其中發送訊息的程式一樣被掛起等待回應的返回。那樣將會呼叫MsgWaitForMultipleObjects,它是一個API 函式,作用是等待一個Win32 事件物件,同時如果有訊息送來將立即處理。這種方式將是最好的混合模式,因為它既可以等待Win32事件物件,還能處理訊息。如果不用混合模式,那麼必須讓執行緒掛起等待Win32事件物件或者讓執行緒僅僅處理訊息。下面是本小
19、節最重要的幾點: 函式 SendMessage和訊息處理是同步的。 函式 PostMessage不等待訊息被處理,和訊息處理是不同步的。 所有的訊息,從呼叫函式GetMessage進行分配到發送到後臺,會被不同的視窗程式處理。 當用訊息傳送機制時,執行緒產生一個視窗,並且相對應的視窗處理程式會執行相對應的程式碼來處理訊息。不會有一個執行緒呼叫函式SendMessage並且這個執行緒還執行另外一個視窗的處理程式。不必要在視窗處理程式中自己將訊息進行序列化,因為一旦訊息通過SendMessage傳送,它會自動被訊息佇列序列化。7.2.2 輸入管理輸入管理由一整套子系統來完成,在Windows CE
20、中,該子系統負責處理前臺視窗、活動視窗和焦點視窗。每一個執行緒有一個特定的視窗稱為活動視窗。這是被特定執行緒擁有和啟動的最高等級的視窗。活動視窗和它的子視窗可以是焦點視窗(具有輸入焦點的視窗)。焦點視窗能夠接收來自鍵盤的訊息。系統中一個特定的執行緒或者訊息佇列稱為前臺執行緒,前臺執行緒中的活動視窗是前臺視窗。這三個視窗是相互關聯的,設定輸入焦點可以改變活動視窗。同樣,改變活動視窗也可以改變輸入焦點。設定前臺視窗或者活動視窗可以改變視窗的座標原點位置,同樣,改變視窗的位置可以改變前臺視窗。所以,三個視窗有類似“石頭、剪、布”之間的關係,它們之間的任何一個改變,其他的都會受到影響,如圖7.6所示。
21、圖7.6 三個視窗相互關聯活動視窗和焦點視窗的資訊都保存在訊息佇列的結構中,所以它們都有一個基本的執行緒。當呼叫函式SetActiveWindow 時,一個執行緒將把和它同一個執行緒的視窗啟動。函式SetFocus將改變其所在執行緒中任意視窗的輸入焦點(這個視窗可以是優先順序最高的視窗也可以是一個子視窗)。當輸入焦點改變或者別的視窗被啟動時,WM_SETFOCUS,WM_KILLFOCUS會被發送。因為在輸入系統中有一個執行緒負責輸入事件,所以上面所說的工作在系統這個大的框架下面完成,而不是在更低一級的函式中處理。這個執行緒會通過觀察它的訊息佇列的情況即時追蹤前臺執行緒的執行情況。它從系統中產
22、生一個執行緒作為前臺執行緒,當使用者按下某個鍵,鍵盤的輸入會被傳送到前臺執行緒的輸入焦點處。在舊的Windows版本中,可以呼叫 SetActiveWindow函式來使不同的視窗啟動。但是呼叫這個函式並不是像前面所說的那樣工作。Windows CE在輸入方面和桌面Windows 作業系統一樣,由一個基本執行緒管理。現在可以呼叫函式SetForegroundWindow 來負責改變輸入系統中前臺視窗的資訊,鍵盤輸入也可以順利地發送出去。使用者可能會疑惑,因為他執行的應用程式可能並不在前臺執行緒中。其實,他的應用程式已經內部呼叫了SetActiveWindow 函式和SetFocus函式,但是使用
23、者並沒有看見應用程式所屬的視窗被啟動。整個系統好像沒有變化。但是函式GetActiveWindow會表明上面所說的視窗是活動視窗。函式GetFocus 說明那個應用程式的視窗已經獲得了輸入焦點。視窗還沒有放到前臺的原因是它所在的執行緒還沒有成為前臺執行緒。所以,這就是函式SetForegroundWindow(設置前臺視窗)被加上的原因。它會告訴系統“這就是你所要的接收使用者輸入的前臺視窗”。這個呼叫會產生一系列的反應:系統希望的視窗將被置於前臺,它可以從內部將另一個執行緒啟動,將輸入焦點改變從而使合適的視窗接收到鍵盤輸入。類似地,如果現在的執行緒是前臺執行緒,並且將別的具有最高等級的視窗置於
24、頂層(比如呼叫函式SetWindowPos 將它的位置改變),那麼這個視窗將變成前臺視窗。並且如果你將輸入焦點從一個視窗轉向另一個視窗,活動視窗也隨之改變。這中間沒有改變的是具有輸入焦點的視窗總是活動視窗或者是活動視窗的子視窗,當然,它也可能為空。因此總體的結構是這樣的:在所有儲存結構中,一個處理程序有一個埠。GWE就是其中一個處理程序。GWE內部是一個執行緒等待著輸入事件。鍵盤或者別的觸控式設備將一個事件放入主輸入佇列,主輸入佇列相當於系統輸入佇列,但是它沒有桌面Windows作業系統中系統佇列描述的那麼細緻。執行緒在那兒得到相對應的事件。如果得到的事件是一個觸控輸入事件,事件管理器會呼叫視
25、窗管理器來找出是哪一個視窗被點選,接著使用函式PostMessage 將訊息封裝,然後放到所屬視窗合適的訊息佇列中去。如果事件是一個鍵盤輸入事件,事件管理器會將與資訊相關的視窗和訊息佇列交給前臺執行緒處理後返回。相對應的資訊會表現為函式PostMessage所發送的訊息。所以,這種發送的機制會同步執行,獲得所有的輸入情況並且將它發送到相對應的執行緒。當別的執行緒執行時,它們將相對應的訊息從訊息佇列中取出並且處理它。如圖7.7所示。圖7.7 觸控輸入事件和鍵盤輸入事件的處理7.3 圖形設備介面在別的圖形包(Package)中,可以使用所有的畫筆、畫刷和字體等來完成每一個繪圖操作。在Win32 G
26、DI(圖形設備介面)中,設備描述表(Device Context,DC)描述了圖形的輸出模版。通過將使用的繪圖工具(畫筆、畫刷等)物件選入設備描述表中來完成對繪圖工具的選擇。設備描述表是所有繪圖工具的集合。繪圖操作使用所有被選入設備描述表的工具物件。當通過呼叫相對應的API函式來使用畫筆和畫刷時,畫筆和畫刷必須轉化成和介面目標一致的形式。所以,如果你呼叫的API函式和介面與畫筆都有關時,必須明白畫筆和介面要一致。在Windows CE中,將畫筆選入設備描述表,同時和畫筆一致的介面物件也被選入了設備描述表中。畫筆只需要實作一次,但是能畫出一百萬條線和矩形。對所有的GDI 圖形物件來說,實作是很模
27、糊的概念,並且它對不同的物件也代表了不同的操作。你想用想像中的顏色的畫筆和畫刷,也許這種顏色在系統中並不存在,但是系統會儘量選擇一個最接近的顏色。從某種意義上說這就是一種實作。字體的實作是這樣的:指定一種理想化的字體並且將它選入設備描述表,它和現實中的某種物理字體是匹配的。調色板也是這樣實作的,通過函式RealizePalette將一個理想中的調色板選入物理設備中。當將某種式樣的畫刷選入設備描述表中(它可以是一個點陣圖或者更大規格),必須建立一個和所需要的點陣圖格式相匹配的畫刷。這種概念上的實作和別的可以選擇的圖形物件的實作是一樣的。一般情況下,當繪圖時,僅僅是選擇相對應的資源並且把它們拷貝到
28、對應的地方。但是別的邏輯操作會把它們連接起來。映射模式的操作是選擇邏輯操作的方法。當用畫筆在介面上繪圖時,可以使用的一種可能的映射模式是將和畫筆規格具有同樣像素的線條複製到指定的區域。當然,也可以通過一些別的操作組合來實作別的。資源有16種組合方式,這些組合方式產生的基本區域被記作第二類映射模式。還有第三類映射模式,它將組合出3個像素寬度的區域作為畫刷的寬度。7.3.1 基本GDI物件在Windows CE的圖形設備介面(GDI)中,所有的東西都是一個C + +物件。基礎類別是一個被稱作GDIOBJ的類別,其定義如程式碼7.1所示。程式碼7.1 GDIOBJ類別/ /摘自CEROOTPriva
29、teWinceosCoreosGWEMGDIincGDIOBJbase.hppclass GDIOBJpublic:static HTABLE* m_pHTable; / 控制碼表INT 16 m_nCount; / 引用計數UINT 16 m_nIndex; / 控制碼表索引GDIOBJ ( void ) ; GDIOBJ ( void ) ;ULONG Increment(void);ULONG Decrement(void);void R e m o v e F r o m H a n d l e T a b l e ( void ) ;BOOL IsStockObject(void);
30、virtual BOOL DeleteObject(void);virtual int GetObject(int CntBytesBuffer, void* pObject) = 0;virtual DWORD GetObjectType(void ) = 0;virtual GDIOBJ* SelectObject(DC*) = 0; ;由GDIOBJ類別的定義可知所有的GDI物件都擁有一個16位的引用計數m_nCount。但是GDI對象沒有元件物件模型(COM)物件那麼強的引用計數,當 COM物件的引用計數為0時,物件可以自我刪除。對於GDI 物件來說,由程式師負責刪除資源:當引用計數為
31、0 時,應該呼叫DeleteObject函式。基本GDI 物件定義了一些抽象函式,規定了實際的GDI物件需要完成的任務,例如刪除物件自身(DeleteObject)、將物件自身選入設備描述表( SelectObject)等。控制碼表(m_pHTable)是物件控制碼的一個列表,它保存著交給使用者程式處理的控制碼。m_nIndex 是一個16位元的索引,指向控制碼表。應用程式在失效的控制碼上有很多問題。比如,一個應用程式呼叫函式CreatePen建立一個畫筆物件,同時得到物件相對應的控制碼(HPEN)。其後,程式刪除了這個畫筆物件,建立一個畫刷對象。但是,畫筆控制碼依然存在著。當應用程式呼叫函式
32、SelectObject將畫刷控制碼選入設備描述表中時,畫筆控制碼才會轉化成一個畫刷控制碼。有時候程式師以為選擇的是一個畫筆,而實際上選擇的卻是畫刷,應用程式可能將這一切搞得很混亂。這是Win32 API存在的問題,一個多態性的問題。因此,當選擇一個控制碼時,必須判斷準確這個控制碼是什麼類型的控制碼。當應用程式退出而沒有釋放所佔用的記憶體空間時,作業系統負責將多餘物件所占的記憶體空間釋放。比如一個應用程式建立了一個畫筆的控制碼(HPEN)並且在程式結束之前忘記把它刪除。程式GWEs.exe接收到應用程式終止的訊息以後,從控制碼表裏面可以查明哪些處理程序建立了GDI物件。從而,系統就可以在控制碼
33、表中尋找已經結束處理程序的物件,然後刪除它們。所以,儘管應用程式沒有將多餘的空間釋放,但是作業系統可以確保不讓多餘的物件佔據記憶體空間。當然,正確的做法是讓應用程式要盡力使它在退出時不會留下一些佔據記憶體空間的無用資料。在編譯應用程式的偵錯版本時,系統會指出錯誤資訊,告訴應用程式哪些物件控制碼應該被釋放。7.3.2 圖形基本操作Windows CE GDI一個非常重要的結構特點是它不直接接觸像素。所有的資訊都將送至裝置驅動程式,並由裝置驅動程式最終完成像素點的輸出。在桌面Windows作業系統中,裝置驅動程式將實作不了的操作交還給GDI,其GDI 可以直接進行像素點的輸出。但是Windows
34、CE GDI沒有這種能力。為了降低記憶體佔用,系統必須讓裝置驅動程式能夠支援所有和像素有關的資訊。Windows CE GDI(繪圖部分)的基本操作有:矩形、折線、多邊形、橢圓和圓角矩形。而裝置驅動程式只知道如何畫線和填充小區域,問題在於如何將GDI繪圖的基本操作分解為一些線和小區域。為解決這個問題,首先應該弄清楚Windows CE GDI的畫筆(用來繪製直線的工具)和畫刷(用來添充區域內部的工具)。 畫筆BLACK-PEN 和WHITE-PEN分別用黑色和白色繪製1個像素寬的直線,這些畫筆和NULL-PEN(空畫筆)都是 GDI的普通畫筆。GetStockObject 函式可用來選擇這些普
35、通畫筆。CreatePen和CreatePenIndirect 函式用來設計與普通畫筆不同屬性的畫筆。它們允許使用者定義畫筆的線寬度、顏色和畫筆類型。表7.2列出了畫筆類型。表7.2 畫筆類型畫筆 類型PS_SOLID 畫實線PS_DASH 畫點劃線PS_NULL 不畫線 畫刷普通畫刷和普通畫筆一樣,可通過SelectObject函式選擇。CreateDIBPatternBrushpt 函式能夠用來設計任何尺寸、顏色和模式的畫刷。它可以設置某種單一顏色或者混合色。畫筆和畫刷的定義如程式碼7.2所示。程式碼7.2 畫筆和畫刷的定義/ /摘自CEROOTPrivateWinceosCoreosGW
36、EMGDIincGDIOBJ.hstruct PEN : public GDIOBJLOGPEN m_LogPen;PEN(CONST LOGPEN *);virtual int GetObject(int,PVOID);virtual DWORD GetObjectType();virtual GDIOBJ *SelectObject(DC *);#if DEBUGvirtual int Dump (void);#endif ;struct BRUSH : public GDIOBJLOGBRUSH m_LogBrush;MBITMAP *m_pBitmap; / bitmap contai
37、ning patternBRUSH( );BRUSH();BOOL Create(CONST LOGBRUSH *);virtual int GetObject(int,PVOID);virtual DWORD GetObjectType();virtual GDIOBJ *SelectObject(DC *);#if DEBUGvirtual int Dump (void);#endif ;有了畫筆和畫刷的概念,下面看看如何具體將GDI繪圖的基本元素分解為一些線和小區域。函式Setpixel和Getpixel處理的物件像素本身就是一個小區域(就是通常的點),可以看作一個1像素* 1像素的矩形
38、。函式Rectangle的功能是畫出及填充一個矩形。由於矩形只是一系列區域的組合,實作起來非常容易。呼叫裝置驅動程式時,對於矩形區域內部使用一個畫刷工具,而對於矩形外邊緣,使用4個小區域。首先使用選入設備描述表中的畫刷填充矩形區域內部,然後使用選入設備描述表中的畫筆描繪矩形外邊緣。如果只想填充矩形區域內部,則可以在設備描述表中選入NULL-PEN 後呼叫函式 Rectangle,這樣會畫出沒有邊框的矩形。函式Polyline的功能是畫出一個連續的折線,能夠逼近各種形狀。如果畫筆的寬度多於一個像素,則將節點間連線轉變為對區域的填充,GDI 將每一段折線變成一個填充區域,然後將這些區域送至裝置驅動
39、程式。函式Polygon的功能是畫出及填充一個多邊形。它假定所有節點組成的是一個封閉圖形,如果不是這樣,Windows CE會自動將其封閉。和函式Rectangle一樣,函式Polygon將進行多邊形區域內部的填充和多邊形外邊緣的描繪。同樣如果畫筆的寬度多於一個像素,將會呼叫一個新的Polygon函式,產生一個在原多邊形外側的帶狀多邊形。函式Ellipse 的功能是畫出及填充一個橢圓。它和RoundRect是惟獨兩個能夠處理曲線物件的函式。可以對這些曲線進行貝塞爾級數展開從而很好的對曲線的每一區域進行近似。然後通過貝塞爾近似將其轉變為一系列直線段,這樣裝置驅動程式便可以對其進行處理了。函式Ro
40、undRect 的功能是畫出及填充一個有圓角的矩形,其實就是將橢圓用兩組分別平行於其長短軸的直線組切割後剩下的部分。因此它的實作只須將函式Ellipse進行一下簡單地擴充。桌面Windows作業系統預設具有幾何寬度的畫筆,並可以將線的末端設置為方角形或斜角形。Windows CE中沒有支援這種幾何型的畫筆,它只支援最簡單的畫筆,和一般畫筆比較相近。其實在Windows CE中本可以增加一些圖形基本元素,例如粗畫筆、粗線橢圓或者一些特殊形狀工具。但是這樣無非是用空間換取了時間,這違背了Windows CE設計的基本原則,是不可取的。Windows CE所需要的只是一些最基本的元素,而對於複雜的圖
41、形,寧可通過增加時間複雜度的方法來實作。7.3.3 調色板可能有許多人都會問一個問題,“Windows CE的調色板是什麼? ”。回答是Windows CE中根本就沒有自己的調色板。裝置驅動程式在啟動時會提供一個首選調色板並說明它是否可以變動。某些設備會擁有一個硬編碼調色板,在這些設備上,它就是Windows CE的調色板。雖然沒有一個通用的系統調色板,但是當需要它的時候,會有一個裝置驅動程式提供一個調色板。系統開發人員推薦原始設備製造商(OEM )將半色調調色板(Halftone Palette)植入設備中並將其載入為原始編碼。可以將你的資源建立在半色調調色板上,同時在執行的時候不能存在色彩
42、轉換。如果問程式開發者們他們覺得在臺式機系統的GDI中最頭疼的部分是什麼,他們一定會說是調色板的模式。在MSDN上有一篇著名的關於調色板的文章,叫作“ The Palette ManagerHow and Why It Does What It Does”,此文對調色板進行了詳細闡述。對此有興趣的讀者可以認真閱讀一下此文章,相信一定會受益非淺。在臺式機系統中,調色板管理器合併了多個應用程式的請求從而使各方面都得到盡可能的滿足。當試圖在臺式機系統上建立一個調色板時,可以在每個PALETTEENTRY類別的物件處插入一些標記參數peFlags。它告訴調色板管理器你打算對這個調色板設置的入口參數,例
43、如不允許映像、不允許別的使用者使用這個調色板入口、將對這個調色板入口進行大面積修改等。開始10個和最後10個顏色叫作系統顏色(Windows colors),他們很難修改。即便向Windows發出一個請求,臺式機系統也不會允許你修改第一個和最後一個入口參數,它們分別代表黑和白。但在Windows CE中,系統開發人員忽略了所有這些。只須將一個調色板選入設備描述表,然後在設備上顏色就會在邏輯調色板和物理調色板之間進行一一映射。所有Windows CE的調色板都是一致性調色板。Windows CE沒有一個調色板管理器。系統開發人員將Windows CE的這種調色板設計比做調色板的公民自由模式。你可
44、以對系統調色板做你願意做的任何事。系統開發人員信任你會是個好的調色板公民。這裏系統沒有任何限制。如果希望選擇一個256個入口參數的調色板並且都為紅色,那就這樣去做。你的顯示器將變成紅色。任務列、系統對話方塊一切都會變成紅色。在設計中,系統開發人員進行了一些簡化工作從而使其能很好地支援Windows CE中的調色板。同時系統開發人員很有希望能建立起一個可以被人們所理解和認同的模型。他們在簡化系統以及保留合理功能這一方面做的很好。Windows CE的調色板可以讓我們做一些在臺式機系統中做不到的事情。例如在臺式機系統中,如果你將一個設備描述表中的區域映射到另一個設備描述表中,不會得到正確的顏色。而
45、在Windows CE 中沒有這種現象,你可以得到最好的色彩轉換。這是因為Windows CE中的函式StretchBlt在實作色彩轉換上比臺式機系統中的函式 BitBlt要精巧的多。如果必須進行色彩轉換,系統實際上會將色彩轉換的物件存入圖像高速緩衝記憶體。色彩轉換是一個非常複雜的問題。設想現在要進行從一個某種8位元色彩模式圖像到另一個不同的8位元色彩模式圖像的轉換。由於兩個調色板並不匹配,你需要在目標處找到一個與資訊源處第一個入口參數最接近的入口,然後重複這種操作。要知道這是一個256色到256色的轉換問題,它會相當耗費資源。而將其運算結果存入圖像高速緩衝記憶體也會佔據很大的空間。實際上程式
46、開發者們在實際應用時並沒有使用這麼多不同的調色板。調色板越多,資訊源與目標的組合就越多,這對圖像高速緩衝記憶體無疑是一個重大的考驗。如果是這樣,你一定無法忍受程式執行所耗費的足夠多的時間。即便色彩轉換全部在高速緩衝記憶體裏完成,如果你很在乎系統的性能,也應該在同一模式和同一調色板下進行資訊源和目標的色彩轉換。因此無論是否在高速緩衝記憶體裏,在同一模式和調色板下的色彩轉換都會大大提高速度。習慣了臺式機系統調色板的人們可能會在使用Windows CE調色板的時候遇到麻煩。他們可能會認為調色板管理器還在幫助自己完成一些基本的工作,然而實際上它已經不存在了。如果你在Windows的最上層打開了一個視窗
47、(在它的下面還有很多其他的視窗),而這時你想改變調色板,於是呼叫函式RealizePalette。在臺式機系統上,執行著的調色板管理器會幫你把整個背景整理的井井有條,但是在Windows CE上,背景上的東西看起來很糟糕,這是因為他們都被映射到了一個不同的調色板而且沒有一個調色板管理器。因此我們要說的是在Windows CE上不要去碰調色板。如果一定要改變系統調色板,要確定打開的視窗已經被最大化,它已經覆蓋住了所有下層的其他視窗。否則的話它們會看上去很糟。當然對於程式開發者們他們可以做任何他們想做的事情。但是如果他們想改變調色板的話,應該建議他們不要試圖改變所謂的系統顏色(Windows co
48、lors),也就是前面提過的開始10個和最後10個顏色。對於遊戲程式和某些應用程式,擁有完整的調色板是十分有意義的,但是程式開發者們必須足夠努力工作才能做到這一點。然而在調色板模式問題上還是有一些小的問題。如果通過呼叫函式CreatDIBSection建立一個點陣圖,你需要操作一個色彩表,它是一個附加在這個點陣圖上的調色板,定義了點陣圖上像素的顏色與其值的對應關係。但是其他建立點陣圖的API函式並不接受色彩表。在這個意義上色彩處理會有一點混亂。如果通過呼叫函式CreateBitmap建立一個非8位元的點陣圖,這個點陣圖會和這個位深度的預設色彩表關聯起來。例如,對於1位元點陣圖,當然會採用黑白兩
49、色的預設色彩表。對於2位元點陣圖,H/PC為系統開發人員提供了先例,即預設為黑、深灰、淺灰和白四色色彩表。對於4位元點陣圖,系統開發人員選擇了臺式機系統上的16位元EGA調色板。對於比8位元高的點陣圖,如16、24和32位元點陣圖,系統開發人員使用了臺式機系統上的RGB預設色彩表。這樣便設置了除8位元外所有位深度的點陣圖預設色彩表。為了允許動態調色板工作,系統開發人員對8位元點陣圖做了一些不同的設計。對於8位元點陣圖,函式CreateBitmap返回一個沒有色彩表與之關聯的點陣圖,如果這樣顏色是如何定義的呢?“ 17”這個像素值又是代表什麼顏色呢?這時候被選入設備描述表中的調色板就開始起作用了。如果有一個沒有色彩表的8位元點陣圖,那麼這個點陣圖的顏色定義便由在設備描述表中選擇的調色板決定。這就使諸如動態調色板之類的功能成為可能:你可以建立一個點陣圖,選擇一個調色板,將其送到顯示器,然後再將另一個調色板選入設備描述表並不斷改變這個調色板,如此進行重複操作。這或許是在調色板模式問題上的一個更為混亂的問題。當你將一個沒有色彩表的8位元點陣圖傳給一個2位元設備時,系統會試圖將這個8位元點陣圖和設備描述表中4個入口參數的色彩表關聯起來,於是錯誤發生了。人們可能經常抱怨將一個本以為是