windows程式設計第四章 輸出文字

NO IMAGE

繪製和更新

WM_PAINT訊息

Windows通過傳送WM_PAINT訊息通知視窗訊息處理程式,視窗的部分顯示區域需要繪製。

在發生下面幾種事件之一時,視窗訊息處理程式會接收到一個WM_PAINT訊息:

在使用者移動視窗或顯示視窗時,視窗中先前被隱藏的區域重新可見。

使用者改變視窗的大小(如果視窗類別樣式有著CS_HREDRAW和CS_VREDRAW位旗標的設定)。

程式使用ScrollWindow或ScrollDC函式滾動顯示區域的一部分。

程式使用InvalidateRect或InvalidateRgn函式刻意產生WM_PAINT訊息。

在某些情況下,顯示區域的一部分被臨時覆蓋,Windows試圖儲存一個顯示區域,並在以後恢復它,但這不一定能成功。在以下情況下,Windows可能傳送WM_PAINT訊息:

Windows擦除覆蓋了部分視窗的對話方塊或訊息框。

選單下拉出來,然後被釋放。

顯示工具提示訊息。

有效矩形和無效矩形

儘管視窗訊息處理程式一旦接收到WM_PAINT訊息之後,就準備更新整個顯示區域,但它經常只需要更新一個較小的區域(最常見的是顯示區域中的矩形區域)。顯然,當對話方塊覆蓋了部分顯示區域時,情況即是如此。在擦除對話方塊之後,需要重畫的只是先前被對話方塊遮住的矩形區域。

只有在顯示區域的某一部分失效時,視窗才會接受WM_PAINT訊息。

Windows內部為每個視窗儲存一個「繪圖資訊結構」,這個結構包含了包圍無效區域的最小矩形的座標以及其它資訊,這個矩形就叫做「無效矩形」,有時也稱為「無效區域」。如果在視窗訊息處理程式處理WM_PAINT訊息之前顯示區域中的另一個區域變為無效,則Windows計算出一個包圍兩個區域的新的無效區域(以及一個新的無效矩形),並將這種變化後的資訊放在繪製資訊結構中。Windows不會將多個WM_PAINT訊息都放在訊息佇列中。

視窗訊息處理程式可以通過呼叫InvalidateRect使顯示區域內的矩形無效。通過呼叫GetUpdateRect,可以在任何時候取得無效矩形座標。

在處理WM_PAINT訊息處理期間,視窗訊息處理程式在呼叫了BeginPaint之後,整個顯示區域即變為有效。程式也可以通過呼叫ValidateRect函式使顯示區域內的任意矩形區域變為有效。如果這呼叫具有令整個無效區域變為有效的效果,則目前佇列中的任何WM_PAINT訊息都將被刪除。

GDI 簡介

hdc引數是「裝置內容控制代碼」,它是GDI的重要部分。實際上,每個GDI函式都需要將這個控制代碼作為函式的第一個引數。

裝置內容

裝置內容控制代碼是GDI函式的視窗「通行證」。

當程式在顯示區域繪圖完畢後,它必須釋放裝置內容控制代碼。控制代碼被程式釋放後就不再有效,且不能再被使用。程式必須在處理單個訊息處理期間取得和釋放控制代碼。除了呼叫CreateDC(函式,在本章暫不講述)建立的裝置內容之外,程式不能在兩個訊息之間儲存其它裝置內容控制代碼。

取得裝置內容控制代碼:方法一

在處理WM_PAINT訊息時,視窗訊息處理程式首先呼叫BeginPaint。BeginPaint函式一般在準備繪製時導致無效區域的背景被擦除。該函式也填入ps結構的欄位。BeginPaint傳回的值是裝置內容控制代碼,這一傳回值通常被儲存在叫做hdc的變數中。呼叫EndPaint即可釋放裝置內容控制代碼。

繪圖資訊結構

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程式使用白色畫刷。

如果程式通過呼叫Windows函式InvalidateRect使顯示區域中的矩形失效,則該函式的最後一個引數會指定是否擦除背景。如果這個引數為FALSE(即0),則Windows將不會擦除背景,並且在呼叫完BeginPaint後PAINTSTRUCT結構的fErase欄位將為TRUE(非零)。

PAINTSTRUCT結構的rcPaint欄位是RECT型態的結構。您已經在第三章中看到,RECT結構定義了一個矩形,其四個欄位為left、top、right和bottom。PAINTSTRUCT結構的rcPaint欄位定義了無效矩形的邊界。這些值均以畫素為單位,並相對於顯示區域的左上角。無效矩形是應該重畫的區域。

PAINTSTRUCT中的rcPaint矩形不僅是無效矩形,它還是一個「剪取」矩形。這意味著Windows將繪圖操作限制在剪取矩形內(更確切地說,如果無效矩形區域不為矩形,則Windows將繪圖操作限制在這個區域內)。

在處理WM_PAINT訊息時,為了在更新的矩形外繪圖,可以使用如下呼叫:

InvalidateRect (hwnd, NULL, TRUE) ;

該呼叫在BeginPaint呼叫之前進行,它使整個顯示區域變為無效,並擦除背景。但是,如果最後一個引數等於FALSE,則不擦除背景,原有的東西將保留在原處。

通常這是Windows程式在無論何時收到WM_PAINT訊息而不考慮rcPaint結構的情況下簡單地重畫整個顯示區域最方便的方法。

取得裝置內容控制代碼:方法二

要得到視窗顯示區域的裝置內容控制代碼,可以呼叫GetDC來取得控制代碼,在使用完後呼叫ReleaseDC:

hdc= GetDC (hwnd) ;

//使用GDI函式

ReleaseDC (hwnd, hdc) ;

不要在一個訊息中呼叫GetDC卻在另一個訊息呼叫ReleaseDC。

與從BeginPaint傳回裝置內容控制代碼不同,GetDC傳回的裝置內容控制代碼具有一個剪取矩形,它等於整個顯示區域。可以在顯示區域的某一部分繪圖,而不只是在無效矩形上繪圖(如果確實存在無效矩形)。與BeginPaint不同,GetDC不會使任何無效區域變為有效。如果需要使整個顯示區域有效,可以呼叫

ValidateRect (hwnd, NULL) ;

TextOut:細節

TextOut (hdc, x, y, psText, iLength) ;

以下將詳細地討論這個函式。

第一個引數是裝置內容控制代碼,它既可以是GetDC的傳回值,也可以是在處理WM_PAINT訊息時BeginPaint的傳回值。

裝置內容的屬性控制了被顯示的字串的特徵。例如,裝置內容中有一個屬性指定文字顏色,內定顏色為黑色;內定裝置內容還定義了白色的背景。在程式向顯示器輸出文字時,Windows使用這個背景色來填入字元周圍的矩形空間(稱為「字元框」)。

該文字背景色與定義視窗類別時設定的背景並不相同,視窗類別中的背景是一個畫刷,它是一種純色或者非純色組成的畫刷,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,則將從顯示區域左上角開始輸出字串。

Windows有許多「座標映像方式」,它們用來控制GDI函式指定的邏輯座標轉換為顯示器的實際畫素座標的方式。映像方式在裝置內容中定義,內定映像方式是MM_TEXT(使用WINGDI.H中定義的識別符號)。在MM_TEXT映像方式下,邏輯單位與實際單位相同,都是畫素;x的值從左向右遞增,y的值從上向下遞增(參看圖4-2)。MM_TEXT座標系與Windows在PAINTSTRUCT結構中定義無效矩形時使用的座標系相同,這為我們帶來了很多方便(但是,其它映像方式並非如此)。

系統字型

系統字型是Windows用來在標題欄、選單和對話方塊中顯示字串的內定字型。系統字型的字元大小取決於視訊顯示器的大小。系統字型設計為至少能在顯示器上顯示25行80列文字。

字元大小

程式可以呼叫GetSystemMetrics函式以取使用者介面上各類視覺元件大小的資訊,呼叫GetTextMetrics取得字型大小。GetTextMetrics傳回裝置內容中目前選取的字型資訊,因此它需要裝置內容控制代碼。Windows將文字大小的不同值複製到在WINGDI.H中定義的TEXTMETRIC型態的結構中。TEXTMETRIC結構有20個欄位,我們只使用前七個:

typedef struct tagTEXTMETRIC 
{ 
LONG tmHeight ; 
LONG tmAscent ; 
LONG tmDescent ; 
LONG tmInternalLeading ; 
LONG tmExternalLeading ; 
LONG tmAveCharWidth ; 
LONG tmMaxCharWidth ; 
//其它結構欄位
} 
TEXTMETRIC, * PTEXTMETRIC ;

 

TEXTMETRIC結構還包括一個不包含在tmHeight值中的欄位tmExternalLeading。它是字型設計者建議加在橫向字元之間的空間大小。在安排文字行之間的空隙時,您可以接受設計者建議的值,也可以拒絕它。在系統字型中tmExternalLeading可以為0。

TEXTMETRICS結構包含有描述字元寬度的兩個欄位,即tmAveCharWidth(小寫字母加權平均寬度)和tmMaxCharWidth(字型中最寬字元的寬度)。對於定寬字型,這兩個值是相等的。

本章的範例程式還需要另一種字元寬度,即大寫字母的平均寬度,這可以用tmAveCharWidth乘以150%大致計算出來。

必須認識到,系統字型的大小取決於Windows所執行的視訊顯示器的解析度,在某些情況下,取決於使用者選取的系統字型的大小。

對於可變寬度字型,TEXTMETRIC結構中的tmPitchAndFamily欄位的低位為1,對於固定寬度字型,該值為0。使用這個位從小寫字母加權平均寬度cxChar計算大寫字母的平均寬度cxCaps:

cxCaps = (tm.tmPitchAndFamily & 1 ? 3 :2) * cxChar / 2 ;

顯示區域的大小

最大化了的顯示區域的尺寸可以通過以SM_CXFULLSCREEN和SM_CYFULLSCREEN為引數呼叫GetSystemMetrics來獲得。

我們使用GetClientRect函式來取得顯示區域的大小。使用這個函式沒有什麼不好,但是在您每次要使用資訊時就去呼叫它一遍是沒有效率的。確定視窗顯示區域大小的更好方法是在視窗訊息處理程式中處理WM_SIZE訊息。在視窗大小改變時,Windows給視窗訊息處理程式傳送一個WM_SIZE訊息。傳給視窗訊息處理程式的lParam引數的低字組中包含顯示區域的寬度,高字組中包含顯示區域的高度。

case WM_SIZE:

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

LOWORD和HIWORD巨集在Windows表標頭檔案WINDEF.H中定義。這些巨集的定義看起來像這樣:

#define LOWORD(l) ((WORD)(l))

#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))

用如下公式計算可以在顯示區域內顯示的文字的總行數:

cyClient / cyChar

滾動條

很容易在應用程式中包含水平或者垂直的滾動條,程式寫作者只需要在CreateWindow的第三個引數中包括視窗樣式(WS)識別符號WS_VSCROLL(垂直捲動)和/或WS_HSCROLL(水平捲動)即可。顯示區域不包含捲動列所佔據的空間。對於特定的顯示驅動程式和顯示解析度,垂直捲動列的寬度和水平捲動列的高度是恆定的。如果需要這些值,可以使用GetSystemMetrics呼叫來取得。

Windows負責處理對滾動條的所有滑鼠操作,但是,視窗滾動條沒有自動的鍵盤介面。如果想用游標鍵來完成捲動功能,則必須提供這方面的程式程式碼。

滾動條的範圍和位置

在內定情況下,滾動條的範圍是從0(頂部或左部)至100(底部或右部),但將範圍改變為更方便於程式的數值也是很容易的:

SetScrollRange (hwnd, iBar, iMin, iMax,bRedraw) ;

引數iBar為SB_VERT或者SB_HORZ,iMin和iMax分別是範圍的最小值和最大值。如果想要 Windows根據新範圍重畫滾動條,則設定bRedraw為TRUE(如果在呼叫SetScrollRange後,呼叫了影響滾動條位置的其它函式,則應該將bRedraw設定為FALSE以避免過多的重畫)。

您可以使用SetScrollPos在滾動條範圍內設定新的捲動方塊位置:

SetScrollPos (hwnd, iBar, iPos, bRedraw) ;

引數iPos是新位置,它必須在iMin至iMax的範圍內。Windows提供了類似的函式(GetScrollRange和GetScrollPos)來取得滾動條的目前範圍和位置。

在程式內使用滾動條時,程式寫作者與Windows共同負責維護滾動條以及更新捲動方塊的位置。下面是Windows對滾動條的處理:

處理所有滾動條滑鼠事件

當使用者在滾動條內單擊滑鼠時,提供一種「反相顯示」的閃爍

當使用者在滾動條內拖動捲動方塊時,移動捲動方塊

為包含滾動條視窗的視窗訊息處理程式傳送滾動條訊息

以下是程式寫作者應該完成的工作:

初始化滾動條的範圍和位置

處理視窗訊息處理程式的滾動條訊息

更新滾動條內捲動方塊的位置

更改顯示區域的內容以響應對滾動條的更改

滾動條訊息

在用滑鼠單擊滾動條或者拖動捲動方塊時,Windows給視窗訊息處理程式傳送WM_VSCROLL(供上下移動)和WM_HSCROLL(供左右移動)訊息。在滾動條上的每個滑鼠動作都至少產生兩個訊息,一條在按下滑鼠按鈕時產生,一條在釋放按鈕時產生。

和所有的訊息一樣,WM_VSCROLL和WM_HSCROLL也帶有wParam和lParam訊息引數。對於來自作為視窗的一部分而建立的滾動條訊息,您可以忽略lParam;它只用於作為子視窗而建立的滾動條(通常在對話方塊內)。

wParam訊息引數被分為一個低字組和一個高字組。wParam的低字組是一個數值,它指出了滑鼠對滾動條進行的操作。這個數值被看作一個「通知碼」。通知碼的值由以SB(代表「scroll bar(滾動條)」)開頭的識別符號定義。以下是在WINUSER.H中定義的通知碼:

#define SB_LINEUP 0 
#define SB_LINELEFT 0 
#define SB_LINEDOWN 1 
#define SB_LINERIGHT 1 
#define SB_PAGEUP 2 
#define SB_PAGELEFT 2 
#define SB_PAGEDOWN 3 
#define SB_PAGERIGHT 3 
#define SB_THUMBPOSITION 4 
#define SB_THUMBTRACK 5 
#define SB_TOP 6 
#define SB_LEFT 6 
#define SB_BOTTOM 7 
#define SB_RIGHT 7 
#define SB_ENDSCROLL 8

 

包含LEFT和RIGHT的識別符號用於水平滾動條,包含UP、DOWN、TOP和BOTTOM的識別符號用於垂直滾動條。

如果在滾動條的各個部位按住滑鼠鍵,程式就能收到多個滾動條訊息。當釋放滑鼠鍵後,程式會收到一個帶有SB_ENDSCROLL通知碼的訊息。一般可以忽略這個訊息,Windows不會去改變捲動方塊的位置,而您可以在程式中呼叫SetScrollPos來改變捲動方塊的位置。

當把滑鼠的游標放在捲動方塊上並按住滑鼠鍵時,您就可以移動捲動方塊。這樣就產生了帶有SB_THUMBTRACK和SB_THUMBPOSITION通知碼的滾動條訊息。在wParam的低字組是SB_THUMBTRACK時,wParam的高字組是使用者在拖動捲動方塊時的目前位置。該位置位於捲動列範圍的最小值和最大值之間。在wParam的低字組是SB_THUMBPOSITION時,wParam的高字組是使用者釋放滑鼠鍵後捲動方塊的最終位置。對於其它的捲動列操作,wParam的高字組應該被忽略。

為了給使用者提供回饋,Windows在您用滑鼠拖動捲動方塊時移動它,同時您的程式會收到SB_THUMBTRACK訊息。然而,如果不通過呼叫SetScrollPos來處理SB_THUMBTRACK或SB_THUMBPOSITION訊息,在使用者釋放滑鼠鍵後,捲動方塊會迅速跳回原來的位置。

程式能夠處理SB_THUMBTRACK或SB_THUMBPOSITION訊息,但一般不同時處理兩者。如果處理SB_THUMBTRACK訊息,在使用者拖動捲動方塊時您需要移動顯示區域的內容。而如果處理SB_THUMBPOSITION訊息,則只需在使用者停止拖動捲動方塊時移動顯示區域的內容。處理SB_THUMBTRACK訊息更好一些(但更困難),對於某些型態的資料,您的程式可能很難跟上產生的訊息。

WINUSER.H表標頭檔案還包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知碼,指出滾動條已經被移到了它的最小或最大位置。然而,對於作為應用程式視窗一部分而建立的滾動條來說,永遠不會接收到這些通知碼。

繪圖程式的組織

在處理完滾動條訊息後,SYSMETS2不更新顯示區域,相反,它呼叫InvalidateRect使顯示區域失效。這導致Windows將一個WM_PAINT訊息放入訊息佇列中。

然而,Windows將WM_PAINT訊息當成低優先順序訊息,如果系統有許多其它的動作正在發生,那麼也許會讓您等待一會兒工夫。

如果您希望立即更新無效區域,可以在呼叫InvalidateRect之後呼叫UpdateWindow:

UpdateWindow(hwnd) ;

如果顯示區域的任一部分無效,則UpdateWindow將導致Windows用WM_PAINT訊息呼叫視窗訊息處理程式(如果整個顯示區域有效,則不呼叫視窗訊息處理程式)。這一WM_PAINT訊息不進入訊息佇列,直接由Windows呼叫視窗訊息處理程式。視窗訊息處理程式完成更新後立即退出,Windows將控制傳回給程式中UpdateWindow呼叫之後的敘述。

//Sysmets.h
#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))
struct
{
int Index ;
TCHAR * szLabel ;
TCHAR * szDesc ;
}
sysmetrics [] =
{
SM_CXSCREEN, TEXT ("SM_CXSCREEN"),
TEXT ("Screen width in pixels"),
SM_CYSCREEN, TEXT ("SM_CYSCREEN"),
TEXT ("Screen height in pixels"),
SM_CXVSCROLL, TEXT ("SM_CXVSCROLL"),
TEXT ("Vertical scroll width"),
SM_CYHSCROLL, TEXT ("SM_CYHSCROLL"),
TEXT ("Horizontal scroll height"),
SM_CYCAPTION, TEXT ("SM_CYCAPTION"),
TEXT ("Caption bar height"),
SM_CXBORDER, TEXT ("SM_CXBORDER"),
TEXT ("Window border width"),
SM_CYBORDER, TEXT ("SM_CYBORDER"),
TEXT ("Window border height"),
SM_CXFIXEDFRAME,TEXT ("SM_CXFIXEDFRAME"),
TEXT ("Dialog window frame width"),
SM_CYFIXEDFRAME,TEXT ("SM_CYFIXEDFRAME"),
TEXT ("Dialog window frame height"),
SM_CYVTHUMB, TEXT ("SM_CYVTHUMB"),
TEXT ("Vertical scroll thumb height"),
SM_CXHTHUMB, TEXT ("SM_CXHTHUMB"),
TEXT ("Horizontal scroll thumb width"),
SM_CXICON, TEXT ("SM_CXICON"),
TEXT ("Icon width"),
SM_CYICON, TEXT ("SM_CYICON"),
TEXT ("Icon height"),
SM_CXCURSOR, TEXT ("SM_CXCURSOR"),
TEXT ("Cursor width"),
SM_CYCURSOR, TEXT ("SM_CYCURSOR"),
TEXT ("Cursor height"),
SM_CYMENU, TEXT ("SM_CYMENU"),
TEXT ("Menu bar height"),
SM_CXFULLSCREEN,TEXT ("SM_CXFULLSCREEN"),
TEXT ("Full screen client area width"),
SM_CYFULLSCREEN,TEXT ("SM_CYFULLSCREEN"),
TEXT ("Full screen client area height"),
SM_CYKANJIWINDOW,TEXT ("SM_CYKANJIWINDOW"),
TEXT ("Kanji window height"),
SM_MOUSEPRESENT, TEXT ("SM_MOUSEPRESENT"),
TEXT ("Mouse present flag"),
SM_CYVSCROLL,TEXT ("SM_CYVSCROLL"),
TEXT ("Vertical scroll arrow height"),
SM_CXHSCROLL,TEXT ("SM_CXHSCROLL"),
TEXT ("Horizontal scroll arrow width"),
SM_DEBUG, TEXT ("SM_DEBUG"),
TEXT ("Debug version flag"),
SM_SWAPBUTTON,TEXT ("SM_SWAPBUTTON"),
TEXT ("Mouse buttons swapped flag"),
SM_CXMIN, TEXT ("SM_CXMIN"),
TEXT ("Minimum window width"),
SM_CYMIN, TEXT ("SM_CYMIN"),
TEXT ("Minimum window height"),
SM_CXSIZE, TEXT ("SM_CXSIZE"),
TEXT ("Min/Max/Close button width"),
SM_CYSIZE, TEXT ("SM_CYSIZE"),
TEXT ("Min/Max/Close button height"),
SM_CXSIZEFRAME,TEXT ("SM_CXSIZEFRAME"),
TEXT ("Window sizing frame width"),
SM_CYSIZEFRAME,TEXT ("SM_CYSIZEFRAME"),
TEXT ("Window sizing frame height"),
SM_CXMINTRACK,TEXT ("SM_CXMINTRACK"),
TEXT ("Minimum window tracking width"),
SM_CYMINTRACK,TEXT ("SM_CYMINTRACK"),
TEXT ("Minimum window tracking height"),
SM_CXDOUBLECLK,TEXT ("SM_CXDOUBLECLK"),
TEXT ("Double click x tolerance"),
SM_CYDOUBLECLK,TEXT ("SM_CYDOUBLECLK"),
TEXT ("Double click y tolerance"),
SM_CXICONSPACING,TEXT ("SM_CXICONSPACING"),
TEXT ("Horizontal icon spacing"),
SM_CYICONSPACING,TEXT ("SM_CYICONSPACING"),
TEXT ("Vertical icon spacing"),
SM_MENUDROPALIGNMENT,TEXT ("SM_MENUDROPALIGNMENT"),
TEXT ("Left or right menu drop"),
SM_PENWINDOWS, TEXT ("SM_PENWINDOWS"),
TEXT ("Pen extensions installed"),
SM_DBCSENABLED, TEXT ("SM_DBCSENABLED"),
TEXT ("Double-Byte Char Set enabled"),
SM_CMOUSEBUTTONS, TEXT ("SM_CMOUSEBUTTONS"),
TEXT ("Number of mouse buttons"),
SM_SECURE, TEXT ("SM_SECURE"),
TEXT ("Security present flag"),
SM_CXEDGE, TEXT ("SM_CXEDGE"),
TEXT ("3-D border width"),
SM_CYEDGE, TEXT ("SM_CYEDGE"),
TEXT ("3-D border height"),
SM_CXMINSPACING, TEXT ("SM_CXMINSPACING"),
TEXT ("Minimized window spacing width"),
SM_CYMINSPACING, TEXT ("SM_CYMINSPACING"),
TEXT ("Minimized window spacing height"),
SM_CXSMICON, TEXT ("SM_CXSMICON"),
TEXT ("Small icon width"),
SM_CYSMICON, TEXT ("SM_CYSMICON"),
TEXT ("Small icon height"),
SM_CYSMCAPTION, TEXT ("SM_CYSMCAPTION"),
TEXT ("Small caption height"),
SM_CXSMSIZE, TEXT ("SM_CXSMSIZE"),
TEXT ("Small caption button width"),
SM_CYSMSIZE, TEXT ("SM_CYSMSIZE"),
TEXT ("Small caption button height"),
SM_CXMENUSIZE, TEXT ("SM_CXMENUSIZE"),
TEXT ("Menu bar button width"),
SM_CYMENUSIZE, TEXT ("SM_CYMENUSIZE"),
TEXT ("Menu bar button height"),
SM_ARRANGE, TEXT ("SM_ARRANGE"),
TEXT ("How minimized windows arranged"),
SM_CXMINIMIZED, TEXT ("SM_CXMINIMIZED"),
TEXT ("Minimized window width"),
SM_CYMINIMIZED, TEXT ("SM_CYMINIMIZED"),
TEXT ("Minimized window height"),
SM_CXMAXTRACK, TEXT ("SM_CXMAXTRACK"),
TEXT ("Maximum draggable width"),
SM_CYMAXTRACK, TEXT ("SM_CYMAXTRACK"),
TEXT ("Maximum draggable height"),
SM_CXMAXIMIZED, TEXT ("SM_CXMAXIMIZED"),
TEXT ("Width of maximized window"),
SM_CYMAXIMIZED, TEXT ("SM_CYMAXIMIZED"),
TEXT ("Height of maximized window"),
SM_NETWORK, TEXT ("SM_NETWORK"),
TEXT ("Network present flag"),
SM_CLEANBOOT, TEXT ("SM_CLEANBOOT"),
TEXT ("How system was booted"),
SM_CXDRAG, TEXT ("SM_CXDRAG"),
TEXT ("Avoid drag x tolerance"),
SM_CYDRAG, TEXT ("SM_CYDRAG"),
TEXT ("Avoid drag y tolerance"),
SM_SHOWSOUNDS, TEXT ("SM_SHOWSOUNDS"),
TEXT ("Present sounds visually"),
SM_CXMENUCHECK, TEXT ("SM_CXMENUCHECK"),
TEXT ("Menu check-mark width"),
SM_CYMENUCHECK, TEXT ("SM_CYMENUCHECK"),
TEXT ("Menu check-mark height"),
SM_SLOWMACHINE, TEXT ("SM_SLOWMACHINE"),
TEXT ("Slow processor flag"),
SM_MIDEASTENABLED, TEXT ("SM_MIDEASTENABLED"),
TEXT ("Hebrew and Arabic enabled flag"),
SM_MOUSEWHEELPRESENT,TEXT ("SM_MOUSEWHEELPRESENT"),
TEXT ("Mouse wheel present flag"),
SM_XVIRTUALSCREEN, TEXT ("SM_XVIRTUALSCREEN"),
TEXT ("Virtual screen x origin"),
SM_YVIRTUALSCREEN, TEXT ("SM_YVIRTUALSCREEN"),
TEXT ("Virtual screen y origin"),
SM_CXVIRTUALSCREEN, TEXT ("SM_CXVIRTUALSCREEN"),
TEXT ("Virtual screen width"),
SM_CYVIRTUALSCREEN, TEXT ("SM_CYVIRTUALSCREEN"),
TEXT ("Virtual screen height"),
SM_CMONITORS, TEXT ("SM_CMONITORS"),
TEXT ("Number of monitors"),
SM_SAMEDISPLAYFORMAT,TEXT ("SM_SAMEDISPLAYFORMAT"),
TEXT ("Same color format flag")
} ;

//Sysmets.cpp

#include <windows.h>
#include "Sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("SysMets2") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
//Defoe.Tu [email protected] Windows 程式設計 PDF 美化版
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 2"),
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos ;
HDC hdc ;
int i, y ;
PAINTSTRUCT ps ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight   tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
return 0 ;
case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_VSCROLL:
switch (LOWORD (wParam))
{
case SB_LINEUP:
iVscrollPos -= 1 ;
break ;
case SB_LINEDOWN:
iVscrollPos  = 1 ;
break ;
case SB_PAGEUP:
iVscrollPos -= cyClient / cyChar ;
break ;
case SB_PAGEDOWN:
iVscrollPos  = cyClient / cyChar ;
break ;
case SB_THUMBPOSITION:
iVscrollPos = HIWORD (wParam) ;
break ;
default :
//Defoe.Tu [email protected] Windows 程式設計 PDF 美化版
break ;
}
iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) ;
if (iVscrollPos != GetScrollPos (hwnd, SB_VERT))
{
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
}
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i  )
{
y = cyChar * (i - iVscrollPos) ;
TextOut (hdc, 0, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, 22 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, 22 * cxCaps   40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].Index))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

建立更好的滾動

滾動條檔案(在/Platform SDK/User InterfaceServices/Controls/Scroll Bars中)指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函式是「過時的」,但這並不完全正確。這些函式在Windows 1.0中就出現了,在Win32 API中升級以處理32位引數。它們仍然具有良好的功能。

Win32 API介紹的兩個滾動條函式稱作SetScrollInfo和GetScrollInfo。這些函式可以完成以前函式的全部功能,並增加了兩個新特性。

第一個功能涉及捲動方塊的大小,捲動方塊大小與在視窗中顯示的檔案大小成比例。顯示的大小稱作「頁面大小」。可以使用SetScrollInfo來設定頁面大小。

GetScrollInfo函式增加了第二個重要的功能,或者說它改進了目前API的不足。假設您要使用65,536或更大單位的範圍,這在16位Windows中是不可能的。當然在Win32中,函式被定義為可接受32位引數,因此是沒有問題的。(記住如果使用這樣大的範圍,捲動方塊的實際物理位置數仍然由捲動列的畫素大小限制)。然而,當使用SB_THUMBTRACK或SB_THUMBPOSITION通知碼得到WM_VSCROLL或WM_HSCROLL訊息時,只提供了16位資料來指出捲動方塊的目前位置。通過GetScrollInfo函式可以取得真實的32位值。

SetScrollInfo和GetScrollInfo函式的語法是

SetScrollInfo (hwnd, iBar, &si, bRedraw);

GetScrollInfo (hwnd, iBar, &si) ;

像在其它滾動條函式中那樣,iBar引數是SB_VERT或SB_HORZ,它還可以是用於滾動條控制的SB_CTL。SetScrollInfo的最後一個引數可以是TRUE或FALSE,指出了是否要Windows重新繪製計算了新資訊後的滾動條。

兩個函式的第三個引數是SCROLLINFO結構,定義為:

typedef struct tagSCROLLINFO 
{ 
UINT cbSize ;// set to sizeof (SCROLLINFO) 
UINT fMask ; // values to set or get 
int nMin ; // minimum range value 
int nMax ; // maximum range value 
UINT nPage ; // page size 
int nPos ; // current position 
int nTrackPos ;// current tracking position 
} SCROLLINFO, * PSCROLLINFO ;

 

在程式中,可以定義如下的SCROLLINFO結構型態:

SCROLLINFO si ;

在呼叫SetScrollInfo或GetScrollInfo之前,必須將cbSize欄位設定為結構的大小:

si.cbSize = sizeof (si) ;

或 si.cbSize = sizeof (SCROLLINFO) ;

新滾動條函式的一個好的功能是當使用與滾動條範圍一樣大的頁面時,它已經為您做掉了一大堆雜事。可以像下面的程式程式碼一樣使用SCROLLINFO結構和SetScrollInfo:

si.cbSize = sizeof (SCROLLINFO) ;

si.cbMask = SIF_RANGE | SIF_PAGE ;

si.nMin = 0 ;

si.nMax = NUMLINES – 1 ;

si.nPage = cyClient / cyChar ;

SetScrollInfo (hwnd, SB_VERT, &si, TRUE);

這樣做之後,Windows會把最大的滾動條位置限制為si.nMax – si.nPage 1而不是si.nMax。像前面那樣做出假設:NUMLINES等於75 (所以si.nMax等於74),si.nPage等於50。這意味著最大的滾動條位置限制為74 – 50 1,即25。這正是我們想要的。

當頁面大小與滾動條範圍一樣大時,會發生什麼情況呢?在這個例子中,就是nPage等於75或更大的情況。Windows通常隱藏滾動條,因為它並不需要。如果不想隱藏滾動條,可在呼叫SetScrollInfo時使用SIF_DISABLENOSCROLL,Windows只是讓那個滾動條不能被使用,而不隱藏它。

//Sysmets2.cpp
#include <windows.h>
#include "Sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("SysMets3") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc= WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 3"),
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;
HDC hdc ;
int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
PAINTSTRUCT ps ;
SCROLLINFO si ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight   tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
// Save the width of the three columns
iMaxWidth = 40 * cxChar   22 * cxCaps ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// Set vertical scroll bar range and page size
si.cbSize = sizeof (si) ;
si.fMask = SIF_RANGE | SIF_PAGE ;
si.nMin = 0 ;
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
// Set horizontal scroll bar range and page size
si.cbSize = sizeof (si) ;
si.fMask = SIF_RANGE | SIF_PAGE ;
si.nMin = 0 ;
si.nMax = 2   iMaxWidth / cxChar ;
si.nPage = cxClient / cxChar ;
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
return 0 ;
case WM_VSCROLL:
// Get all the vertical scroll bar information
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// Save the position for comparison later on
iVertPos = si.nPos ;
switch (LOWORD (wParam))
{
case SB_TOP:
si.nPos = si.nMin ;
break ;
case SB_BOTTOM:
si.nPos = si.nMax ;
break ;
case SB_LINEUP:
si.nPos -= 1 ;
break ;
case SB_LINEDOWN:
si.nPos  = 1 ;
break ;
case SB_PAGEUP:
si.nPos -= si.nPage ;
break ;
case SB_PAGEDOWN:
si.nPos  = si.nPage ;
break ;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos ;
break ;
default:
break ;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// If the position has changed, scroll the window and update it
if (si.nPos != iVertPos)
{
ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
NULL, NULL) ;
UpdateWindow (hwnd) ;
}
return 0 ;
case WM_HSCROLL:
// Get all the vertical scroll bar information
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
// Save the position for comparison later on
GetScrollInfo (hwnd, SB_HORZ, &si) ;
iHorzPos = si.nPos ;
switch (LOWORD (wParam))
{
case SB_LINELEFT:
si.nPos -= 1 ;
break ;
case SB_LINERIGHT:
si.nPos  = 1 ;
break ;
case SB_PAGELEFT:
si.nPos -= si.nPage ;
break ;
case SB_PAGERIGHT:
si.nPos  = si.nPage ;
break ;
case SB_THUMBPOSITION:
si.nPos = si.nTrackPos ;
break ;
default :
break ;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
GetScrollInfo (hwnd, SB_HORZ, &si) ;
// If the position has changed, scroll the window
if (si.nPos != iHorzPos)
{
ScrollWindow (hwnd, cxChar * (iHorzPos - si.nPos), 0,
NULL, NULL) ;
}
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
// Get vertical scroll bar position
si.cbSize = sizeof (si) ;
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
iVertPos = si.nPos ;
// Get horizontal scroll bar position
GetScrollInfo (hwnd, SB_HORZ, &si) ;
iHorzPos = si.nPos ;
// Find painting limits
iPaintBeg = max (0, iVertPos   ps.rcPaint.top / cyChar) ;
iPaintEnd = min ( NUMLINES - 1,
iVertPos   ps.rcPaint.bottom / cyChar) ;
for (i = iPaintBeg ; i <= iPaintEnd ; i  )
{
x = cxChar * (1 - iHorzPos) ;
y = cyChar * (i - iVertPos) ;
TextOut (hdc, x, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, x   22 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, x   22 * cxCaps   40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].Index))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

ScrollWindow函式在視窗的顯示區域中捲動資訊而不是重畫它。雖然該函式很複雜(在新版本的Windows中已被更復雜的ScrollWindowEx所替代),SYSMETS3仍以相當簡單的方式使用它。函式的第二個引數給出了水平捲動顯示區域的數值,第三個引數是垂直捲動顯示區域的數值,單位都是畫素。
ScrollWindow的最後兩個引數設定為NULL,這指出了要捲動整個顯示區域。Windows自動把顯示區域中未被捲動操作覆蓋的矩形設為無效。這會產生WM_PAINT訊息。再也不需要InvalidateRect了。注意ScrollWindow不是GDI函式,因為它不需裝置內容控制代碼。它是少數幾個非GDI的Windows函式之一,它可以改變視窗的顯示區域外觀。