?? 4.輸出文字.txt
字號:
輸出文字
智能中國——游戲組 整理編譯
--------------------------------------------------------------------------------
在前一章,您看到了一個簡單的Windows 98程序,它在窗口中央,或者更準確地說,在顯示區域中央顯示一行文字。正如我們學到的,顯示區域是整個應用程序窗口中未被標題列、窗口邊框,以及可選的菜單列、工具列、狀態列和滾動條占據的部分。簡而言之,顯示區域是窗口中可以由程序任意書寫和傳遞視覺信息的部分。
對于程序的顯示區域,您幾乎可以為所欲為,只不過您不能假定窗口大小是某一特定尺寸,或者在程序執行時其大小會保持不變。如果您不熟悉圖形窗口環境的程序設計,這些限制可能會使您感到驚訝:不能再假設屏幕上的一行文字一定有80個字符了。您的程序必須與其它Windows程序共享視訊顯示器。Windows使用者控制程序窗口在屏幕上顯示的方式。盡管可以建立固定大小的窗口(這對于計算器之類的應用是合理的),但在大多數情況下,使用者應該能夠改變應用程序窗口的大小。您的程序必須能夠接受指定給它的大小,并且合理地利用這一空間。
這有兩種可能的情況。一種可能是,程序只有僅能顯示「hello」的顯示區域;還有另一種可能,即程序在一個大屏幕、高分辨率的系統上執行,其顯示區域大得足以顯示兩整頁文字。靈活地處理這兩種極端是Windows程序設計的要點之一。
這一章,我們將講述程序在顯示區域顯示信息的方式,但比上一章說明的顯示方式更加復雜。當程序在顯示區域顯示文字或圖形時,它經常要「繪制」它的顯示區域。本章著重講述繪制的方法。
盡管Windows為顯示圖形提供了強大的圖形設備接口(GDI)函數,但在這一章中,我只介紹簡單文字行的顯示。我也將忽略Windows能夠使用的不同字體外形及字體大小,僅使用Windows的內定系統字體。這看起來似乎是一種限制,其實不然,本章涉及和解決的問題適用于所有Windows程序設計。在混合顯示文字和圖形時,Windows內定字體的字符大小通常決定了圖形的尺寸。
本章表面上是討論繪圖的方法,實際上是討論與設備無關的程序設計基礎。Windows程序只能對顯示區域大小甚至字符的大小做很少的假定,相反地,必須使用Windows提供的功能來取得關于程序執行環境的信息。
繪制和更新
在文字模式環境下,程序可以在顯示器的任意部分輸出,程序輸出到屏幕上的內容會停留在原處,不會神秘地消失。因此,程序可以丟掉重新生成屏幕顯示時所需的信息。
在Windows中,只能在窗口的顯示區域繪制文字和圖形,而且不能確保在顯示區域內顯示的內容會一直保留到程序下一次有意地改寫它時還保留在那里。例如,使用者可能會在屏幕上移動另一個程序的窗口,這樣就可能覆蓋您的應用程序窗口的一部分。Windows不會保存您的窗口中被其它程序覆蓋的區域,當程序移開后,Windows會要求您的程序更新顯示區域的這個部分。
Windows是一個消息驅動系統。它通過把消息投入應用程序消息隊列中或者把消息發送給合適的窗口消息處理程序,將發生的各種事件通知給應用程序。Windows通過發送WM_PAINT消息通知窗口消息處理程序,窗口的部分顯示區域需要繪制。
WM_PAINT消息
大多數Windows程序在WinMain中進入消息循環之前的初始化期間都要呼叫函數UpdateWindow。Windows利用這個機會給窗口消息處理程序發送第一個WM_PAINT消息。這個消息通知窗口消息處理程序:必須繪制顯示區域。此后,窗口消息處理程序應在任何時刻都準備好處理其它WM_PAINT消息,必要的話,甚至重新繪制窗口的整個顯示區域。在發生下面幾種事件之一時,窗口消息處理程序會接收到一個WM_PAINT消息:
在使用者移動窗口或顯示窗口時,窗口中先前被隱藏的區域重新可見。
使用者改變窗口的大小(如果窗口類別樣式有著CS_HREDRAW和CS_VREDRAW位旗標的設定)。
程序使用ScrollWindow或ScrollDC函數滾動顯示區域的一部分。
程序使用InvalidateRect或InvalidateRgn函數刻意產生WM_PAINT消息。
在某些情況下,顯示區域的一部分被臨時覆蓋,Windows試圖保存一個顯示區域,并在以后恢復它,但這不一定能成功。在以下情況下,Windows可能發送WM_PAINT消息:
Windows擦除覆蓋了部分窗口的對話框或消息框。
菜單下拉出來,然后被釋放。
顯示工具提示消息。
在某些情況下,Windows總是保存它所覆蓋的顯示區域,然后恢復它。這些情況是:
鼠標光標穿越顯示區域。
圖標拖過顯示區域。
處理WM_PAINT消息要求程序寫作者改變自己向顯示器輸出的思維方式。程序應該組織成可以保留繪制顯示區域需要的所有信息,并且僅當「響應要求」-即Windows給窗口消息處理程序發送WM_PAINT消息時才進行繪制。如果程序在其它時間需要更新其顯示區域,它可以強制Windows產生一個WM_PAINT消息。這看來似乎是在屏幕上顯示內容的一種舍近求遠的方法。但您的程序結構可以從中受益。
有效矩形和無效矩形
盡管窗口消息處理程序一旦接收到WM_PAINT消息之后,就準備更新整個顯示區域,但它經常只需要更新一個較小的區域(最常見的是顯示區域中的矩形區域)。顯然,當對話框覆蓋了部分顯示區域時,情況即是如此。在擦除對話框之后,需要重畫的只是先前被對話框遮住的矩形區域。
這個區域稱為「無效區域」或「更新區域」。正是顯示區域內無效區域的存在,才會讓Windows將一個WM_PAINT消息放在應用程序的消息隊列中。只有在顯示區域的某一部分失效時,窗口才會接受WM_PAINT消息。
Windows內部為每個窗口保存一個「繪圖信息結構」,這個結構包含了包圍無效區域的最小矩形的坐標以及其它信息,這個矩形就叫做「無效矩形」,有時也稱為「無效區域」。如果在窗口消息處理程序處理WM_PAINT消息之前顯示區域中的另一個區域變為無效,則Windows計算出一個包圍兩個區域的新的無效區域(以及一個新的無效矩形),并將這種變化后的信息放在繪制信息結構中。Windows不會將多個WM_PAINT消息都放在消息隊列中。
窗口消息處理程序可以通過呼叫InvalidateRect使顯示區域內的矩形無效。如果消息隊列中已經包含一個WM_PAINT消息,Windows將計算出新的無效矩形。否則,它將一個新的WM_PAINT消息放入消息隊列中。在接收到WM_PAINT消息時,窗口消息處理程序可以取得無效矩形的坐標(我們馬上就會看到這一點)。通過呼叫GetUpdateRect,可以在任何時候取得這些坐標。
在處理WM_PAINT消息處理期間,窗口消息處理程序在呼叫了BeginPaint之后,整個顯示區域即變為有效。程序也可以通過呼叫ValidateRect函數使顯示區域內的任意矩形區域變為有效。如果這呼叫具有令整個無效區域變為有效的效果,則目前隊列中的任何WM_PAINT消息都將被刪除。
GDI 簡介
要在窗口的顯示區域繪圖,可以使用Windows的圖形設備接口(GDI)函數。Windows提供了幾個GDI函數,用于將字符串輸出到窗口的顯示區域內。我們已經在上一章看過DrawText函數,但是目前使用最為普遍的文字輸出函數是TextOut。該函數的格式如下:
TextOut (hdc, x, y, psText, iLength) ;
TextOut向窗口的顯示區域寫入字符串。psText參數是指向字符串的指針,iLength是字符串的長度。x和y參數定義了字符串在顯示區域的開始位置(不久會講述關于它們的詳細情況)。hdc參數是「設備內容句柄」,它是GDI的重要部分。實際上,每個GDI函數都需要將這個句柄作為函數的第一個參數。
設備內容
讀者可能還記得,句柄只不過是一個數值,Windows以它在內部使用對象。程序寫作者從Windows取得句柄,然后在其它函數中使用該句柄。設備內容句柄是GDI函數的窗口「通行證」,有了這種設備內容句柄,程序寫作者就能自如地在顯示區域上繪圖,使圖形如自己所愿地變得好看或者難看。
設備內容(簡稱為「DC」)實際上是GDI內部保存的數據結構。設備內容與特定的顯示設備(如視訊顯示器或打印機)相關。對于視訊顯示器,設備內容總是與顯示器上的特定窗口相關。
設備內容中的有些值是圖形「屬性」,這些屬性定義了GDI繪圖函數工作的細節。例如,對于TextOut,設備內容的屬性確定了文字的顏色、文字的背景色、x坐標和y坐標映像到窗口的顯示區域的方式,以及顯示文字時Windows使用的字體。
當程序需要繪圖時,它必須先取得設備內容句柄。在取得了該句柄后,Windows用內定的屬性值填入內部設備內容結構。在后面的章節中您會看到,可以通過呼叫不同的GDI函數改變這些默認值。利用其它的GDI函數可以取得這些屬性的目前值。當然,還有其它的GDI函數能夠在窗口的顯示區域真正地繪圖。
當程序在顯示區域繪圖完畢后,它必須釋放設備內容句柄。句柄被程序釋放后就不再有效,且不能再被使用。程序必須在處理單個消息處理期間取得和釋放句柄。除了呼叫CreateDC(函數,在本章暫不講述)建立的設備內容之外,程序不能在兩個消息之間保存其它設備內容句柄。
Windows應用程序一般使用兩種方法來取得設備內容句柄,以備在屏幕上繪圖。
取得設備內容句柄:方法一
在處理WM_PAINT消息時,使用這種方法。它涉及BeginPaint和EndPaint兩個函數,這兩個函數需要窗口句柄(作為參數傳給窗口消息處理程序)和PAINTSTRUCT結構的變量(在WINUSER.H表頭文件中定義)的地址為參數。Windows程序寫作者通常把這一結構變量命名為ps并且在窗口消息處理程序中定義它:
PAINTSTRUCT ps ;
在處理WM_PAINT消息時,窗口消息處理程序首先呼叫BeginPaint。BeginPaint函數一般在準備繪制時導致無效區域的背景被擦除。該函數也填入ps結構的字段。BeginPaint傳回的值是設備內容句柄,這一傳回值通常被保存在叫做hdc的變量中。它在窗口消息處理程序中的定義如下:
HDC hdc ;
HDC數據型態定義為32位的無正負號整數。然后,程序就可以使用需要設備內容句柄的TextOut等GDI函數。呼叫EndPaint即可釋放設備內容句柄。
一般地,處理WM_PAINT消息的形式如下:
caseWM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
使用GDI函數
EndPaint (hwnd, &ps) ;
return 0 ;
在處理WM_PAINT消息時,必須成對地呼叫BeginPaint和EndPaint。如果窗口消息處理程序不處理WM_PAINT消息,則它必須將WM_PAINT消息傳遞給Windows中DefWindowProc(內定窗口消息處理程序)。DefWindowProc以下列代碼處理WM_PAINT消息:
case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
這兩個BeginPaint和EndPaint呼叫之間中沒有任何敘述,僅僅使先前無效區域變為有效。但以下方法是錯誤的:
case WM_PAINT:
return 0 ; // WRONG !!!
Windows將一個WM_PAINT消息放到消息隊列中,是因為顯示區域的一部分無效。如果不呼叫BeginPaint和EndPaint(或者ValidateRect),則Windows不會使該區域變為有效。相反,Windows將發送另一個WM_PAINT消息,且一直發送下去。
繪圖信息結構
前面提到過,Windows為每個窗口保存一個「繪圖信息結構」,這就是PAINTSTRUCT,定義如下:
typedef struct tagPAINTSTRUCT
{
HDC hdc ;
BOOL fErase ;
RECT rcPaint ;
BOOL fRestore ;
BOOL fIncUpdate ;
BYTE rgbReserved[32] ;
} PAINTSTRUCT ;
在程序呼叫BeginPaint時,Windows會適當填入該結構的各個字段值。使用者程序只使用前三個字段,其它字段由Windows內部使用。hdc字段是設備內容句柄。在舊版本的Windows中,BeginPaint的傳回值也曾是這個設備內容句柄。在大多數情況下, fErase被標志為FALSE(0),這意味著Windows已經擦除了無效矩形的背景。這最早在BeginPaint函數中發生(如果要在窗口消息處理程序中自己定義一些背景擦除行為,可以自行處理WM_ERASEBKGND消息)。Windows使用WNDCLASS結構的hbrBackground字段指定的畫刷來擦除背景,這個WNDCLASS結構是程序在WinMain初始化期間登錄窗口類別時使用的。許多Windows程序使用白色畫刷。以下敘述設定窗口類別結構字段值:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
不過,如果程序通過呼叫Windows函數InvalidateRect使顯示區域中的矩形失效,則該函數的最后一個參數會指定是否擦除背景。如果這個參數為FALSE(即0),則Windows將不會擦除背景,并且在呼叫完BeginPaint后PAINTSTRUCT結構的fErase字段將為TRUE(非零)。
PAINTSTRUCT結構的rcPaint字段是RECT型態的結構。您已經在第三章中看到,RECT結構定義了一個矩形,其四個字段為left、top、right和bottom。PAINTSTRUCT結構的rcPaint字段定義了無效矩形的邊界,如圖4-1所示。這些值均以圖素為單位,并相對于顯示區域的左上角。無效矩形是應該重畫的區域。
圖4-1 無效矩形的邊界
PAINTSTRUCT中的rcPaint矩形不僅是無效矩形,它還是一個「剪取」矩形。這意味著Windows將繪圖操作限制在剪取矩形內(更確切地說,如果無效矩形區域不為矩形,則Windows將繪圖操作限制在這個區域內)。
在處理WM_PAINT消息時,為了在更新的矩形外繪圖,可以使用如下呼叫:
InvalidateRect (hwnd, NULL, TRUE) ;
該呼叫在BeginPaint呼叫之前進行,它使整個顯示區域變為無效,并擦除背景。但是,如果最后一個參數等于FALSE,則不擦除背景,原有的東西將保留在原處。
通常這是Windows程序在無論何時收到WM_PAINT消息而不考慮rcPaint結構的情況下簡單地重畫整個顯示區域最方便的方法。例如,如果在顯示區域的顯示輸出中包括了一個圓,但是只有圓的一部分落到了無效矩形中,它就使僅繪制圓的無效部分變得沒有意義。這需要畫整個圓。在您使用從BeginPaint傳回的設備內容句柄時,Windows不會繪制rcPaint矩形外的任何部分。
在第三章的HELLOWIN程序中,我們并不關心處理WM_PAINT消息時的無效矩形。如果文字顯示區域恰巧在無效矩形內,則由DrawText恢復之。否則,在處理DrawText呼叫的某個時刻,Windows會確定它無須向顯示器上輸出。不過,這一決定需要時間。關心程序性能和速度的程序寫作者希望在處理WM_PAINT期間使用無效矩形范圍,以避免不必要的GDI呼叫。如果繪制時需要存取例如位圖這樣的磁盤文件,則這就顯得尤其重要。
取得設備內容句柄:方法二
雖然最好是在處理WM_PAINT消息處理期間更新整個顯示區域,但是您也會發現在處理非WM_PAINT消息處理期間繪制顯示區域的某個部分也是非常有用的。或者您需要將設備內容句柄用于其它目的,如取得設備內容的信息。
要得到窗口顯示區域的設備內容句柄,可以呼叫GetDC來取得句柄,在使用完后呼叫ReleaseDC:
hdc = GetDC (hwnd) ;
使用GDI函數
ReleaseDC (hwnd, hdc) ;
與BeginPaint和EndPaint一樣,GetDC和ReleaseDC函數必須成對地使用。如果在處理某消息時呼叫GetDC,則必須在退出窗口消息處理程序之前呼叫ReleaseDC。不要在一個消息中呼叫GetDC卻在另一個消息呼叫ReleaseDC。
與從BeginPaint傳回設備內容句柄不同,GetDC傳回的設備內容句柄具有一個剪取矩形,它等于整個顯示區域。可以在顯示區域的某一部分繪圖,而不只是在無效矩形上繪圖(如果確實存在無效矩形)。與BeginPaint不同,GetDC不會使任何無效區域變為有效。如果需要使整個顯示區域有效,可以呼叫
ValidateRect (hwnd, NULL) ;
一般可以呼叫GetDC和ReleaseDC來對鍵盤消息(如在字處理程序中)和鼠標消息(如在畫圖程序中)作出反應。此時,程序可以立刻根據使用者的鍵盤或鼠標輸入來更新顯示區域,而不需要考慮為了窗口的無效區域而使用WM_PAINT消息。不過,一旦確實收到了WM_PAINT消息,程序就必須要收集足夠的信息后才能更新顯示。
與GetDC相似的函數是GetWindowDC。GetDC傳回用于寫入窗口顯示區域的設備內容句柄,而GetWindowDC傳回寫入整個窗口的設備內容句柄。例如,您的程序可以使用從GetWindowDC傳回的設備內容句柄在窗口的標題列上寫入文字。然而,程序同樣也應該處理WM_NCPAINT (「非顯示區域繪制」)消息。
TextOut:細節
TextOut是用于顯示文字的最常用的GDI函數。語法是:
TextOut (hdc, x, y, psText, iLength) ;
以下將詳細地討論這個函數。
第一個參數是設備內容句柄,它既可以是GetDC的傳回值,也可以是在處理WM_PAINT消息時BeginPaint的傳回值。
設備內容的屬性控制了被顯示的字符串的特征。例如,設備內容中有一個屬性指定文字顏色,內定顏色為黑色;內定設備內容還定義了白色的背景。在程序向顯示器輸出文字時,Windows使用這個背景色來填入字符周圍的矩形空間(稱為「字符框」)。
該文字背景色與定義窗口類別時設置的背景并不相同。窗口類別中的背景是一個畫刷,它是一種純色或者非純色組成的畫刷,Windows用它來擦除顯示區域,它不是設備內容結構的一部分。在定義窗口類別結構時,大多數Windows應用程序使用WHITE_BRUSH,以便內定設備內容中的內定文字背景顏色與Windows用以擦除顯示區域背景的畫刷顏色相同。
psText參數是指向字符串的指針,iLength是字符串中字符的個數。如果psText指向Unicode字符串,則字符串中的字節數就是iLength值的兩倍。字符串中不能包含任何ASCII控制字符(如回車、換行、制表或退格),Windows會將這些控制字符顯示為實心塊。Text0ut不識別作為字符串結束標志的內容為零的字節(對于Unicode,是一個短整數型態的0),而需要由nLength參數指明長度。
TextOut中的x和y定義顯示區域內字符串的開始位置,x是水平位置,y是垂直位置。字符串中第一個字符的左上角位于坐標點(x,y)。在內定的設備內容中,原點(x和y均為0的點)是顯示區域的左上角。如果在TextOut中將x和y設為0,則將從顯示區域左上角開始輸出字符串。
當您閱讀GDI繪圖函數(例如TextOut)的文件時,就會發現傳遞給函數的坐標常常被稱為「邏輯坐標」。在第五章會詳細地解釋這種情況。現在請注意,Windows有許多「坐標映像方式」,它們用來控制GDI函數指定的邏輯坐標轉換為顯示器的實際圖素坐標的方式。映像方式在設備內容中定義,內定映像方式是MM_TEXT(使用WINGDI.H中定義的標識符)。在MM_TEXT映像方式下,邏輯單位與實際單位相同,都是圖素;x的值從左向右遞增,y的值從上向下遞增(參看圖4-2)。MM_TEXT坐標系與Windows在PAINTSTRUCT結構中定義無效矩形時使用的坐標系相同,這為我們帶來了很多方便(但是,其它映像方式并非如此)。
圖4-2 MM_TEXT映像方式下的x坐標和y坐標
設備內容也定義了一個剪裁區域。您已經看到,對于從GetDC取得的設備內容句柄,內定剪裁區域是整個顯示區域;而對于從BeginPaint取得的設備內容句柄,則為無效區域。Windows不會在剪裁區域之外的任何位置顯示字符串。如果一個字符有一部分在剪裁區域外,則Windows將只顯示此區域內的那部分。要想將輸出寫到窗口的顯示區域之外不是那么容易的,所以不用擔心會無意間出現這種事情。
系統字體
設備內容還定義了在您呼叫TextOut顯示文字時Windows使用的字體。內定字體為「系統字體」,或用Windows表頭文件中的標識符,即SYSTEM_FONT。系統字體是Windows用來在標題列、菜單和對話框中顯示字符串的內定字體。
在Windows的早期版本中,系統字體是等寬(fixed-pitch)字體,這意味著所有字符均具有同樣的寬度,非常類似于打字機。然而,從Windows 3.0開始,系統字體成為一種變寬(variable-pitch)字體,這意味著不同的字符具有不同的大小,比如,「W」要比「i」寬。變寬字體比等寬字體好讀,這已經是公認的事實。不過,可以想見,這一轉變使很多原來的Windows程序代碼不再適用,從而要求程序寫作者學習一些使用字體的新技術。
系統字體是一種「點陣字體」,這意味著字符被定義為圖素塊(在第十七章,將討論TrueType字體,它是由輪廓定義的)。至于確切的大小,系統字體的字符大小取決于視訊顯示器的大小。系統字體設計為至少能在顯示器上顯示25行80列文字。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -