一 窗口和消息
1. 前綴:
2 WPARAM和LPARAM的意義
在Windows是一種16位系統時,WndProc的第三個參數被定義為WORD,是一個16位的無符號整數,而第四個參數被定義為一個LONG,是一個32位有符號整數,所以導致對單詞PARAM(參數)加前綴W和L。
但在32位Windows中,WPARAM被定義為一個UINT,而LPARAM被定義為一個LONG,因此窗口過程的這兩個參數都是32位的值。
3 新的函數類型
WndProc函數返回一個類型為LRESULT的值,該值是一個LONG型,32位有符號。
WndProc函數被指定為CALLBACK類型(供系統調用的函數),WinMain函數被指定為WINAPI類型。這些類型指在Windows本身和用戶的應用程序之間發生的函數調用的特殊調用序列。
4 窗口類結構WNDCLASS
有10個字段,分別是:
① style:類風格,用於在什么時候發出窗口變化消息
② cbClsExtra:在類結構保存的窗口結構中預留一些額外空間
③ cbWndExtra:在Windows內部保存的窗口結構中預留一些額外空間
④ hbrBackground:指定基於這個類創建的窗口背景顏色
⑤ hCursor:讀取光標
⑥ hIcon:讀取圖標
⑦ hInstance:程序的實例句柄
⑧ lpfnWndProc:指定處理基於這個窗口類創建的所有窗口的窗口過程
⑨ lpszClassName:指定類名
⑩ lpszMenuName:指定窗口類菜單
5 注冊窗口類RegisterClass
一般在Windows XP及以后都可以很順利的注冊成功。所以可以只寫RegisterClass(&wndclass);
6 創建窗口CreateWindow
窗口類定義了窗口的一般特征,調用CreateWindow可以指定有關窗口的更詳細的信息。
hwnd = CreateWindow (szAppName, // 指定一個窗口類,基於該窗口類創建窗口 TEXT ("Hello Win"), // 這個字符串會出現在標題欄中 WS_OVERLAPPEDWINDOW, // 本窗口風格 CW_USEDEFAULT, //窗口左上角的X坐標 CW_USEDEFAULT, //窗口左上角的Y坐標 CW_USEDEFAULT, //窗口的寬度 CW_USEDEFAULT, //窗口的高度 NULL, //窗口對象的父窗口句柄 NULL, //窗口對象的菜單句柄或者子窗口編號 hInstance, //當前進程的實例句柄 NULL) ; //窗口對象的參數指針句柄
創建窗口返回的是窗口句柄。
7 顯示窗口
窗口創建成功后,系統將在內存中為其分配一塊內存,但是此時窗口並未顯示在顯示器上,所以需要使用兩個調用。
① ShowWindow(窗口句柄,iCmdShow);
其中的第二個參數用於確定如何兒子屏幕上顯示窗口,是最小化還是常規還是最大化。
② UpdateWindow(窗口句柄)
調用上句將導致客戶區被繪制。它通過給窗口過程發送一個WM_PAINT消息來做到這一點。
8 消息循環
調用UpdateWindow之后,窗口就出現在顯示器上。
Windows為當前運行的每個Windows程序維護一個“消息隊列”。在發生事件的時候,Windows將事件轉換為一個“消息”,並將消息放入程序的消息隊列中。
程序通過執行一個叫做“消息循環”的代碼從消息隊列中取出消息。(注意GetMessage(阻塞,若沒有消息則不返還控制權)和PeekMessage(非阻塞,若沒有消息也會返回)的區別)
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } //其中msg是一個類型為MSG的結構,MSG結構在WINUSER.H中定義如下: typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG /* 其中: hwnd 是消息發向的窗口句柄。 message 是消息標識符,以WM_開頭。 wParam 一個32位的消息參數。 lParam 一個32位的消息參數。 time 消息放入消息隊列的時間。 pt 消息放入消息隊列時鼠標的坐標。*/
消息循環以GetMessage調用開始,它從消息隊列中取出一個消息,GetMessage(&msg, NULL, 0, 0)這一調用傳給Windows一個指向名為msg的MSG結構的指針。其余三個參數設置為NULL或0,表示程序接收自己創建的所有窗口的所有消息。 只要從消息隊列中取出的消息的message域不為WM_QUIT,GetMessage就返回一個非0值,while循環就可以繼續。
TranslateMessage(&msg); 將msg結構傳給Windows,進行一些鍵盤轉換。 DispatchMessage(&msg); 將msg結構傳給Windows,然后Windows將里面的消息發給相應的窗口過程進行處理。處理后,WndProc返回到Windows,Windows返回到程序,程序繼續下一個while循環。
9 窗口過程WndProc
實際的動作發生在窗口過程中。窗口過程確定了在窗口的客戶區顯示什么,以及怎么處理用戶輸入。
(1)窗口過程是命名為WndProc的函數。(也可以其他不沖突的名字)
(2)一個Windows程序可以包含多個窗口過程。
(3)一個窗口過程總是與調用RegisterClass注冊的特定窗口類相關聯。
(4)CreateWindow函數根據特定的窗口類創建一個窗口,返回該窗口的句柄。
(5)但是基於一個窗口類可以創建多個窗口。
1程序 —— 包括 ——n個窗口過程
1窗口過程 —— 關聯 —— 1窗口類
1窗口類 —— 創建 —— n個窗口
CreateWindows根據窗口類創建一個窗口。
窗口過程總是聲明成如下形式:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); /*這4個參數跟MSG結構的前4個域是相同的。 第一個參數:接收消息的窗口句柄。 第二個參數:消息類型,標識消息的數字 最后兩個參數:32位的消息參數。 */
程序通常不直接調用窗口過程,而是由Windows調用窗口過程。
10 窗口過程處理消息
窗口過程接收的每個消息均是用一個數值來標識的,也就是傳給窗口過程的message參數。
窗口過程處理消息時,必須返回0.窗口過程不予處理的所有消息應該被傳給名為DefWindowsProc的Windows函數。
用switch語句來處理不同消息,消息以WM_開頭。
11 WM_CREATE消息
當Windows在處理CreateWindow函數時,窗口過程就會接收到WM_CREATE消息。
通常,窗口過程在WM_CREATE處理期間進行一次窗口初始化。
12 WM_PAINT消息
當窗口客戶區域的一部分或全部變成“無效”時,必須進行刷新,WM_PAINT將通知程序。
在最初創建窗口的時候,整個客戶區都是無效的,因為程序還沒有在窗口上畫任何東西。在調用UpdateWindow時,通常會觸發第一個WM_PAINT消息,指示窗口過程在客戶區域上畫一些東西。
在改變程序窗口大小后,客戶區也會變得無效,至於怎么變得無效由CS_引導的類風格選項確定。
對WM_PAINT的處理幾乎總是從一個BeginPaint調用開始,以一個EndPaint結束。
hdc = BeginPaint(hwnd, &pt); //do something;(如GetClientRect(hwnd, &rect);) EndPaint(hwnd, &pt);
hwnd是要刷新的窗口的窗口句柄。
pt是指向類型為PAINTSTRUCT的結構指針。
在BeginPaint調用中,如果客戶區域的背景還未被擦除,就由Windows來擦除。然后使用注冊窗口類的WNDCLASS結構中的hbrBackground域中第一的刷子來刪除背景。
BeginPaint調用使整個客戶區有效,並返回一個“設備環境句柄”。設備環境是指物理輸出設備及其驅動程序。可以利用該“設備環境句柄”在客戶區域顯示文本和圖形。
EndPaint調用釋放設備環境句柄。
GetClientRect(hwnd, &rect);
第一個參數:程序的窗口句柄;
第二個參數:指向RECT類型的rectangle結構。該結構有4個LONG域,標識客戶區域的尺寸。
當改變窗口大小時,WndProc通過調用GetClientRect來獲取變化后的窗口大小,重新繪制客戶區。
13 WM_DESTROY消息
當用戶點擊關閉按鈕時發生。程序可以通過調用PostQuitMessage以標准方式響應WM_DESTROY消息;
PostQuitMessage(0); // 該函數在程序的消息隊列插入一個WM_QUIT消息。
GetMessage對於除了WM_QUIT消息之外的從消息隊列中取出的所有消息都返回非0值。而當GetMessage取到一個WM_QUIT消息時,返回0.
14 關閉程序時的消息傳遞
① 用戶點擊關閉按鈕
② 產生WM_SYSCOMMAND消息;
③ 產生WM_CLOSE消息響應WM_SYSCOMMAND;
④ 產生WM_DESTROY消息響應WM_CLOSE;
⑤ 產生WM_QUIT消息響應WM_DESTROY。
15 進隊消息和不進隊消息
消息能夠分為:進隊消息和不進隊消息。
進隊消息是由Windows放入程序消息隊列中的。在程序的消息循環中,重新返回並分配給窗口過程。
不進隊消息在Windows調用窗口時直接發送給窗口過程。
也就是說,進隊消息發送給消息隊列,不進隊消息發送給窗口過程。
在任何情況下,窗口過程都將獲得窗口所有的消息。窗口過程是窗口的消息中心。
進隊消息基本上是用戶輸入的結果,還包括時鍾消息、刷新消息、退出消息。
不進隊消息基本上是來自調用特定的Windows函數。
--------------------------------------------------------------------------------------------------
二 輸出文本
16 有效矩形和無效矩形
窗口過程一旦接受到WM_PAINT消息之后,就准備更新整個客戶區,但往往只需更新一個較小的區域。這個區域就稱為“無效區域”。正是客戶區內存在無效區域,才提示Windows將一個WM_PAINT消息放入消息隊列。
Windows內部為每個窗口保存一個“繪圖信息結構”,這個結構包含了包圍無效區域的最小矩形的坐標以及其他信息,這個矩形就叫做“無效矩形”。
如果在窗口過程處理WM_PAINT消息之前,客戶區又有一個區域變為無效,那么Windows計算出一個包圍兩個無效區域的新的無效矩形,並將這個變化后的信息放在繪制信息結構中。
一個消息隊列在一個時刻只能有一個WM_PAINT消息在隊列中。
窗口過程可以調用InvalidateRect使客戶區變為無效。如果消息隊列包含一個WM_PAINT消息,那么Windows將計算出新的無效矩形;否則,就在消息隊列中添加一個WM_PAINT消息。
在處理WM_PAINT消息期間,窗口過程在調用了BeginPaint之后,整個客戶區就會變得有效。
程序也可以顯式調用ValidateRect函數使客戶區內的任意矩形區域變得有效。如果這條調用使整個客戶區都有效,那么將在當前消息隊列中刪除WM_PAINT消息。
17 設備環境
要在窗口的客戶區繪圖,可以使用Windows的圖形設備接口GDI函數。
設備環境DC是GDI內部保存的數據結構。
設備環境與特定的顯示設備有關。
設備環境中的有些值是圖形化的“屬性”,如指出顏色、背景色、坐標映射方式等。
當程序要繪圖時,必須先獲取設備環境句柄。在獲取了該句柄之后,Windows用默認的屬性值填充設備環境結構的內部各域。
當程序在客戶區繪圖完畢后,必須釋放設備環境句柄。句柄被釋放后不再有效,也不再使用。程序必須在處理單個消息期間獲取和釋放句柄。
18 獲取設備環境句柄的方法之一
在使用WM_PAINT消息時,使用這種方法。它涉及到BeginPaint和EndPaint兩個函數。
在處理WM_PAINT消息時,窗口過程首先調用BeginPaint。BeginPaint函數一般在准備繪制時導致無效區域的背景被擦除。BeginPaint返回的值是設備環境句柄,這一返回值通常被保持在叫做hdc的變量中。
HDC hdc;
HDC數據類型定義為32位的無符號數。
然后,程序就可以使用需要設備環境句柄的GDI函數了。
調用EndPaint即可釋放設備環境句柄。
一般地,處理WM_PAINT消息的形式如下:
case WM_PAINT: hdc = BeginPaint(hwnd, &ps); //GDI函數 EndPaint(hwnd, &ps); return 0;
處理WM_PAINT消息時,必須成對地調用BeginPaint和EndPaint。
19 繪圖信息結構
Windows為每一個窗口保存一個繪圖信息結構。這就是PAINTSTRUCT,定義如下:
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT
在用BeginPaint時,Windows填充該結構的各個字段。用戶程序只需要使用前三個字段。
hdc是設備環境句柄。
fErase通常被標識為FLASE,這意味着Windows已經擦除了無效矩形的背景。
如果程序通過調用Windows函數InvalidateRect使客戶區中的矩形失效,那么該函數的最后一個參數會指定fErase的值。如果指定0,那么在稍后的PAINTSTRUCT里面的fErase會被設置為TRUE。
rcPaint是RECT結構,定義了無效矩形的邊界。RECT結構中的left、top、right、bottom以像素點為單位。此時,Windows將繪圖操作限制在此RECT結構定義的矩形范圍內,如果要在無效矩形外繪圖,應該在調用BeginPaint之前,使用如下調用:
InvalidateRect(hwnd, NULL, TRUE);
它將使整個客戶區無效,並擦除背景。
20 取設備環境句柄的方法之二
要得到窗口客戶區的設備環境句柄,可以調用GetDC來獲取句柄。在使用完后調用ReleaseDC;
hdc = GetDC(hwnd); //使用GDI函數 ReleaseDC(hwnd, hdc);
GetDC和ReleaseDC函數必須成對地使用。
GetDC返回的設備環境句柄具有一個剪取矩形,等於整個客戶區。
GetDC不會使任何無效區域變為有效,要是整個客戶區有效,需要調用ValidateRect(hwnd, NULL);
一般可以調用GetDC和ReleaseDC來對鍵盤消息、鼠標消息作出反應。
21 TextOut細節
TextOut是用於顯示文本的最常用的GDI函數。語法是:
TextOut(hdc, x, y, psText, iLength);
第一個參數:設備環境句柄,既可以是GetDC的返回值,也可以是BeginPaint的返回值。
第二個參數:定義客戶區內字符串的開始位置的水平坐標。
第三個參數:定義客戶區內字符串的開始位置的垂直坐標。
第四個參數:指向要輸出的字符串的指針。
第五個參數:字符串中字符的個數。如果psText中的字符是Unicode的,那么串中的字節數就是iLength值的兩倍。
設備環境還定義了一個剪取區域。
對於從GetDC獲取的設備環境句柄,默認的剪取區是整個客戶區。
對於從BeginPaint獲取的設備環境句柄,默認的剪取區是無效區域。
Windows不會在剪取區域之外的任何位置顯示字符串。
22 字符大小
要用TextOut顯示多行文本,就必須確定字體的字符大小,可以根據字符的高度來定位字符的后續行,以及根據字符的寬度來定位字符的后續列。
系統字體的字符高度和平均寬度取決於視頻顯示器的像素大小。
程序可以調用GetSystemMetrics函數來確定關於用戶界面構件大小的信息。
程序可以調用GetTextMetrics函數來確定字體大小。
metric是度量的意思。
TEXTMETRIC的結構:
typedef struct tagTEXTMETRIC { LONG tmHeight; LONG tmAscent; LONG tmDescent; LONG tmInternalLeading; LONG tmExternalLeading; LONG tmAveCharWidth; LONG tmMaxCharWidth; 其他字段 } TEXTMETRIC, *PTEXTMETRIC;
要使用GetTextMetrics函數,需要先定義一個通常被稱為tm的結構變量:
TEXTMETRIC tm;
在需要確定文本尺寸時,先要獲取設備環境句柄,再調用GetTextMetrics:
hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm); // 操作; ReleaseDC(hwnd,hdc);
23 文本尺寸
字體的縱向大小由5個值確定:
① tmHeight,等於tmAscent加上tmDescent。這兩個值表示了基線上下字符的最大縱向高度。
② tmAscent,基線以上的高度
③ tmDescent,基線以下的高度
④ tmInternalLeading,重音號和字符之間的距離,如ü中的u和兩點的距離。
⑤ tmExternalLeading,一般用於多行文本間行距的調整。
字符的橫向大小由2個值確定:
① tmAveCharWidth,小寫字母加權平均寬度。
② tmMaxCharWidth,字體中最寬字符的寬度。
對於等寬字體,tmAveCharWidth和tmMaxCharWidth這兩個值相等。
大寫字母的平均寬度比較復雜,如果:
① 字體是等寬字體,那么大寫字母的平均寬度等於tmAveCharWidth。
② 字體是變寬字體,那么大寫字母的平均寬度等於tmAveCharWidth*1.5。
判斷字體是否是變寬字體,可以通過TEXTMETRIC結構中的tmPitchAndFamily域的低位判斷,如果低位是1,那么是變寬字體,如果是0,那么是等寬字體。
大寫字母寬度 = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * 小寫字母寬度
24 格式化文本
在一次Windows對話期間,系統字體的大小不會改變,因此在程序運行過程中,只需要調用一次GetTextMetric。最好是在窗口過程中處理WM_CREATE消息時進行此調用。
假設要編寫一個Windows程序,在客戶區顯示多行文本,這需要先獲取字符寬度和高度。可以在窗口過程內定義兩個變量來保存字符寬度和總的字符高度。
case WM_CREATE: hdc = BeginPaint(hwnd, &pt); GetTextMetric(hdc, &tm); cxChar = tm.tmAveCharWidth; 小寫字母寬度 cyChar = tm.Height + tm.tmExternalLeading; 字母高度 cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) / 2 * tm.tmAveCharWidth; 大寫字母寬度 EndPaint(hwnd, &pt); return 0;
25 客戶區的大小
窗口最大化之后的客戶區大小,可以通過以SM_CXFULLSCREEN和SM_CYFULLSCREEN為參數調用GetSystemMetric來獲得。
要確定客戶區的大小,最好的方法是在窗口過程處理WM_SIZE消息。在窗口大小改變時,就會產生WM_SIZE消息。傳給窗口過程的lParam參數的低位字中包含客戶區的寬度x,高位字中包含客戶區的高度y。要保存這些尺寸,可以定義兩個int型變量來保存。
static int cxClient,cyClient;
然后在WM_SIZE消息處理中:
case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0;
用cyClient/cyChar可以得到客戶區可以顯示的文本總行數。
26 滾動條的范圍和位置
每個滾動條都有一個相關的范圍和位置。這是一對整數。當滾動框在滾動條的頂部(左部)時,滾動框的位置是范圍的最小值;在滾動條的底部(右部)時,滾動框的位置是范圍的最大值。
在默認情況下,滾動條的范圍是0~100,但將范圍改變為更方便於程序的數值也是很容易的:
SetScrollRange(hwnd, iBar, iMin, iMax, bRedraw); /* hwnd為該窗口的句柄。 iBar為SB_VERT或SB_HORZ。 iMin和iMax為范圍。 bRedraw,如果要Windows根據新范圍重繪滾動條,則設置為TRUE。 */
滾動框的位置不是連續的,而是離散的整數值。
可以使用SetScrollPos在滾動條范圍內設置新的滾動框位置:
SetScrollPos(hwnd, iBar, iPos, bRedraw);
參數iPos是新位置,必須在iMin至iMax的范圍內。
Windows提供了類似的函數GetScrollRange和GetScrollPos來獲取滾動條的當前范圍和位置。
27 滾動條消息
在用鼠標單擊滾動條或者拖動滾動框時,Windows都給窗口過程發生WM_VSCROLL或WM_HSCROLL消息。在滾動條上的每個鼠標動作都至少產生兩個消息,一個在按下鼠標鍵時產生,一個在釋放鼠標鍵時產生。
WM_VSCROLL和WM_HSCROLL也帶有wParam和lParam消息參數。
lParam只用於作為子窗口而創建的滾動條(通常在對話框內)。
wParam消息參數被分為一個低位字和一個高位字。
低位字是一個數值,指出了鼠標對滾動條進行的操作。這個數值被看作一個“通知碼”。通知碼以SB開頭。
#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
當把鼠標的光標放在滾動框上並按住鼠標鍵時,就產生SB_THUMBPOSITION和SB_THUMBTRACK消息。
當wParam的低位字是SB_THUMBTRACK時,wParam的高位字是用戶在拖動滾動框時的當前位置。
當wParam的低位字是SB_THUMBPOSITION時,wParam的高位字是用戶釋放鼠標后滾動框的最終位置。
28 滾動條信息函數
滾動條文檔指出SetScrollPos、SetScrollRange、GetScrollPos、GetScrollRange函數是過時的。
在Win32 API中,升級了2個滾動條函數,稱作SetScrollInfo和GetScrollInfo。這些函數完成上述4個函數的全部功能,並增加了2個新特性。
第一個功能設計滾動框的大小。滾動框的大小稱作頁面大小。算法是:
滾動框大小 / 滾動長度 ≈ 頁面大小 / 范圍 ≈ 顯示的文檔數量 / 文檔的總大小
可以使用SetScrollInfo來設置頁面大小。
第二個功能是GetScrollInfo函數,它可以獲取32位的范圍值。
SetScrollInfo和GetScrollInfo函數的語法是:
SetScrollInfo(hwnd, iBar, &si, bRedraw); GetScrollInfo(hwnd, iBar, &si); //ar參數是SB_VERT或SB_HORZ。 //Redraw可以是TRUE或FALSE,指出了是否要Windows重新繪制計算了新信息后的滾動條。 //函數的第三個參數是SCROLLINFO結構,定義為: typedef struct tagSCROLLINFO { UINT cbSize; UINT fMask; int nMin; int nMax; UINT nPage; int nPos; int nTrackPos; } SCROLLINFO
在程序中,可以定義如下的SCROLLINFO結構類型:
SCROLLINFO si;
在調用SetScrollInfo或GetScrollInfo函數之前,必須將cbSize自動設置為結構的大小:
si.cbSize = sizeof(si);或si.cbSize = sizeof(SCROLLINFO);
fMask:把fMask字段設置為以SIF前綴開頭的一個或多個標識,來獲取或設置里面的結構中域的值。
① SIF_RANGE:用於獲取或設置滾動條的范圍
② SIF_POS:用於獲取或設置滾動框的位置
③ SIF_PAGE:用於獲取或設置滾動條的頁面大小
④ SIF_TRACKPOS:用於獲取或設置滾動框移動時的位置
-------------------------------------------------------------------------------------------------------------------
三 圖形基礎
29 GDI基礎(微軟GDI文檔GDI Reference)
圖形設備接口GDI是Windows的子系統,它負責在視頻顯示器和打印機上顯示圖形。
Windows NT中的圖形主要由GDI32.DLL動態鏈接庫輸出的函數來處理。
GDI的主要目的之一是支持與設備無關的圖形。
圖形輸出設備分為兩大類:光柵設備和矢量設備。
大多數PC機顯示器、打印機都是光柵設備。
繪圖儀是矢量設備。
組成GDI的函數可以分為這樣幾類:
① 獲取(或重建)和釋放(或清除)設備環境的函數;
② 獲取有關設備環境信息的函數;
③ 繪圖函數;
④ 設置和獲取設備環境參數的函數;
⑤ 使用GDI對象的函數。
30 GDI圖元
在屏幕或打印機上顯示的圖形類型本身可以被分為幾類,通常被稱為“圖元”。
① 直線和曲線
② 填充區域
③ 位圖:位圖是位的矩形數組,位對應於顯示設備上的像素,它們是光柵圖形的基本工具。GDI支持兩種類型的位圖——老的“設備有關”位圖,新的“設備無關”位圖。
④ 文本
31 GDI其他方面
① 映射模式和變化;
② 元文件:元文件是以二進制形式存儲的GDI命令的集合。元文件主要用於通過剪貼板傳輸矢量圖形表示。
③ 區域:區域是形狀任意的復雜區;
④ 路徑:路徑是GDI內部存儲的直線和曲線的集合;
⑤ 剪裁:繪圖可以限制在客戶區的某一部分中,這就是剪裁。剪裁通常是通過區域或者路徑定義的。
⑥ 調色板:定制調色板通常限於顯示256色的顯示器。Windows僅保留這些色彩之中的20種供系統使用,但可以改變其他236種色彩。
⑦ 打印
32 進一步探討設備環境
想在一個圖形輸出設備上繪圖時,首先必須獲得一個設備環境的句柄。將句柄返回給程序時,Windows就給了用戶使用設備的權限。然后在GDI函數中將該句柄作為一個參數,向Windows標識想在其上進行繪圖的設備。
(1)獲取設備環境句柄
如果在處理一條消息時獲取了設備環境句柄,應該在退出窗口函數之前釋放它。
獲取設備環境句柄的幾種方法:
① 在處理WM_PAINT消息時,使用BeginPaint和EndPaint調用
hdc = BeginPaint(hwnd, &ps); //GDI操作 EndPaint(hwnd, &ps);
注:變量ps是類型為PAINTSTRUCT的結構,該結構的hdc字段是BeginPaint返回的設備環境句柄。PAINTSTRUCT結構包含了一個名為rcRect的RECT結構,該結構定義了包圍窗口無效范圍的矩形。使用從BeginPaint獲得的設備環境句柄,只能在這個區域內繪圖。BeginPaint調用使這個區域有效。
② 可以在處理非WM_PAINT消息時獲取設備環境句柄
hdc = GetDC(hwnd); //GDI操作 ReleaseDC(hwnd, hdc);
注:這個設備環境適用於窗口句柄為hwnd的客戶區。這個調用可以在整個客戶區上繪圖。
③ Windows程序還可以獲取適用於整個窗口的設備環境句柄
hdc = GeWindowstDC(hwnd); //GDI操作 ReleaseDC(hwnd, hdc);
注:這個設備環境除了客戶區之外,還包括窗口的標題欄、菜單、滾動條和框架。如果要使用該函數,必須捕獲WM_NCPAINT消息。
④ 獲取整個屏幕的設備環境句柄
hdc = CreateDC(TEXT(“DISPLAY”), NULL, NULL, NULL);
原型是:
hdc = CreateDC(pszDriver, pszDevice, pszOutput, pData); //GDI操作 DeleteDC(hdc);
⑤ 如果只需要獲取關於某設備環境的一些信息,而並不進行任何繪畫,在這種情況下,可以使用CreateIC來獲取一個“信息描述表”的句柄,其參數和CreateDC一樣。
⑥ 一個設備環境通常是指一個物理顯示設備。通常,需要獲取有關該設備的信息,其中包括顯示器的顯示尺寸和色彩范圍。可以通過GetDeviceCaps函數來獲取這些信息。
iValue = GetDeviceCaps(hdc, iIndex);
參數iIndex的取值為WINGDI.H頭文件中定義的29個標識符之一。
33 用TextOut輸出整型的方法
設一開始有整型:int i = 100;
要用TextOut函數將i輸出,需要用到三個函數:
① wsprintf
② TEXT宏
③ TextOut
首先得先說明下wsprintf的原型:
int wsprintf(LPTSTR lpOut, LPCTSTR lpFmt,...); /* 一個參數:緩沖區,是一個字符數組,一般定義為TCHAR型。 第二個參數:格式字符串,因為第一個參數是TCHAR類型,一定要和TEXT宏聯合使用,這樣才能在不同的編譯環境下都可以順利編譯。 后續參數:要輸出的的整型變量。 * //第一個參數:要定義一個TCHAR的字符數組作為緩沖區。 TCHAR szBuffer[10]; //足夠大就行了 //第二個參數,需要使用到TEXT宏。 //XT宏的原型: TEXT(LPTSTR string //ANSI or Unicode string); //處理要轉換的整型,具體用法是: TEXT(“%d”); //上述的兩個調用應該寫成: int iLength = 0; //用來保存字符串中的字符個數; iLength = wsprintf(szBuffer, TEXT(“%d”), i); //語句的作用是:將i存進szBuffer中,返回szBuffer存有的字符個數到iLength中。 TextOut的原型是: TextOut(hdc, x, y, psText, iLength); /* 數是設備環境句柄; 第二個參數是輸出的文本的x坐標; 第三個參數是輸出的文本的y坐標; 第四個參數是是指向要輸出的字符串的指針; 第五個參數是字符串中的字符個數; */ //那么TextOut函數應該寫成: TextOut(hdc, x, y, szBuffer, iLength);
34 設備的大小
使用GetDeviceCaps函數能獲取有關輸出設備物理大小的信息。
對於打印機,用“每英寸的點數dpi”表示分辨率。
對於顯示器,用水平和垂直的總的像素數來表示分辨率。
用“像素大小”或“像素尺寸”表示設備水平或垂直顯示的總像素數。
用“度量大小”或“度量尺寸”表示以每英寸或毫米為單位的顯示區域的大小。
像素大小 / 度量大小 = 分辨率
使用SM_CXSCREEN和SM_CYSCREEN參數從GetDeviceCaps得到像素大小;
使用HORZSIZE和VERTSIZE參數從GetDeviceCaps得到度量大小;
兩者相除就可以得到水平分辨率和垂直分辨率。
如果設備的水平分辨率和垂直分辨率相等,就稱該設備具有“正方形像素”。
因為整個屏幕的度量大小是固定的,所以可以根據分辨率調整水平或垂直顯示的像素數。如果分辨率小,那么“像素大小”也就小,也就是說,總像素數少了,那么每個像素的尺寸也就變得大些。
35 字體的大小
現在討論字體的大小問題,這里不是說字號,而是說字體顯示的dpi值。Windows系統默認是每英寸96點,所另外一種選擇,就是每英寸120點。
我們在調整分辨率的時候,從小分辨率變化到大分辨率時,會覺得圖標的文字變小,那是因為在大分辨率下,每個像素的面積變小,假設一個字需要100個像素來顯示,那么從小分辨率變化到大分辨率時,字的總面積就變小了,所以字的大小也就發生變化,而這一變化是字體的大小變化,而不是該字的字號發生變化。
在傳統的排版中,字體的字母大小由“磅”表示。1磅≈1/72英寸,在計算機排版中1磅正好為1/72英寸。
理論上,字體的磅值是從字體中最高的字符頂部到字符下部的字符底部的距離,其中不包括重音號。根據TEXTMETRIC結構,字體的磅值等於tmHeight – tmInternalLeading。
36 關於色彩 “全色”視頻顯示器的分辨率是每個像素24位:8位紅色、8位綠色、8位藍色。 “高彩色”顯示分辨率是每個像素16為:5位紅色、6位綠色、5位藍色。 顯示256種顏色的視頻適配器每個像素需要8位。然而這些8位的值一般由定義實際顏色的調色板表組織。 使用GetDeviceCaps可以使程序員確定視頻適配器的存儲組織,以及能夠表示的色彩數目。 這個調用返回色彩平面的數目:iPlanes = GetDeviceCaps(hdc, PLANES); 這個調用返回每個像素的色彩位數:iBitsPixel = GetDeviceCaps(hdc, BITSPIXEL); 大多數彩色圖形顯示設備要么使用多個色彩平面,要么每像素有多個色彩位,但是不能同時二者兼用;即這兩個調用必須有一個返回1.(一般都是第一個返回1)。 在大多數GDI函數調用中,使用COLORREF值(32位)來表示一種色彩。
理論上,COLORREF可以指定2的24次方或1600萬種色彩。 這個無符號長整數常常稱為一個“RGB色彩”。在使用RGB(r, g, b);宏時注意參數的順序是紅、綠、藍。而在無符號長整數中,由高位到低位是0、藍、綠、紅。 當三個參數都是0時,表示黑色,當三個參數都是255時,表示白色。 黑色 = RGB(0,0,0) = 0x00000000 白色 = RGB(255, 255, 255) = 0x00FFFFFF
37 保存設備環境
通常,在調用GetDC或BeginPaint時,Windows會用默認值創建一個新的設備環境,對設備環境其屬性所做的一切修改在調用ReleaseDC或EndPaint被釋放掉。
如果需要使用非默認的設備環境屬性,則必須在每次獲取設備環境句柄時初始化設備環境。
如果需要在釋放設備環境之后,仍然保存程序中對設備環境所做的改變,以便在下一次調用GetDC和BeginPaint時它們仍起作用。則應該在窗口類那將CS_OWNDC標志包含進窗口類風格中。
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
現在,基於這個窗口類所創建的每個窗口都將擁有自己的設備環境,它一直存在,直到窗口被刪除。
如果使用了CS_OWNDC風格,就只需初始化設備環境一次,可以在處理WM_CREATE消息期間完成這一操作。
CS_OWNDC風格只影響GetDC和BeginPaint獲得的設備環境,不影響其他函數獲得的設備環境,如GetWindowDC獲得的設備環境。
在某些情況下,可以需要改變某些設備環境,用改變后的屬性進行繪圖,然后又要恢復回改變前的屬性。這時,可以通過如下調用來保存設備環境的狀態。
//保存: int idSaved = 0; idSaved = SaveDC(hdc); //恢復: RestoreDC(hdc, idSaved);
也可以不保存SaveDC的返回值,這時候如果要恢復,就只能恢復到最近保存的狀態,RestoreDC(hdc, -1);
38 寫像素
寫像素SetPixel(hdc, x, y, crColor);其中:
hdc是設備環境句柄;
x, y是像素點的坐標;
crColor是要設置的顏色,一般可以用RGB(r, g, b)設置。
39 線條
幾種畫線函數:
① LineTo:畫直線
② Polyline和PolylineTo:畫一系列相連的直線
③ PolyPolyline:畫多組相連的線
④ Arc和ArcTo和AngleArc:畫橢圓線
⑤ PolyBezier和PolyBezierTo:畫貝塞爾線條
⑥ PolyDraw:畫一系列相連的線以及貝塞爾線條
幾種填充函數:
① Rectangle:畫矩形
② Ellipse:畫橢圓
③ RoundRect:畫帶圓角的矩形
④ Pie:畫橢圓的一部分,使其看起來像一個扇形
⑤ Chord:畫橢圓的一部分,使其看起來像弓形
⑥ Polygon:畫多邊形
⑦ PolyPolygon:畫多個多邊形
設備環境的5個屬性影響着用這些函數所畫線條的外觀:
① 當前畫筆的位置;
② 畫筆;
③ 背景方式;
④ 背景色;
⑤ 繪圖模式。
畫一條直線,必須調用2個函數,第一個函數指定了線的開始點坐標,第二個函數指出了線的終點坐標:
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y2);
MoveToEx不會畫線,只是設置了設備環境的“當前位置”屬性。然后LineTo函數從當前的位置到它所指定的點畫一直線。在默認的設備環境中,當前位置最初是在點(0,0)。
MoveToEx最后一個參數是指向POINT結構的指針。從該函數返回后,POINT結構的x和y字段指出了之前的“當前位置”,如果不需要這個信息,直接填NULL。
如果需要獲取當前位置,先定義一個POINT的結構變量pt,然后通過下面的調用:
GetCurrentPositionEx(hdc, &pt);
幾個函數的原型:
Rectangle(hdc, xLeft, yTop, xRight, yBottom);
Ellipse(hdc, xLeft, yTop, xRight, yBottom);
RoundRect(hdc, xLeft, yTop, xRight, yBottom, xCorner, yCorner);
Chord(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);
Pie(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);
Arc(hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd);
一個二維的貝塞爾線條由4個點定義——兩個端點和兩個控制點。曲線的控制點固定,將曲線從兩個端點間的直線處拉伸構造曲線。
40 使用畫筆
調用任何畫筆函數時,Windows使用設備環境中當前選中的“畫筆”來畫線。畫筆決定線的色彩、寬度、線型。線型可以是實線、點划線、虛線,默認設備環境中畫筆是BLACK_PEN,一個像素寬,實線。
Windows提供三種現有畫筆,分別是:BLACK_PEN, WHITE_PEN和NULL_PEN。
Windows使用句柄來引用畫筆。用HPEN的類型定義,即畫筆的句柄。
HPEN hPen;
調用GetStockObject,可以獲得現有畫筆的句柄。
hPen = GetStockObject(WHITE_PEN);
調用SelectObject將畫筆選進設備環境。
SelectObject(hdc,hPen);
SelectObject的返回值是選進前設備環境的畫筆句柄。
使用CreatePen或CreatePenIndirect創建一個“邏輯畫筆”,這僅僅是對畫筆的描述。這些函數返回邏輯畫筆的句柄,然后調用SelectObject將畫筆選進設備環境,之后才可以使用新的畫筆來畫線。
在任何時候,只能有一種畫筆選進設備環境。
在釋放設備環境或在選擇了另一種畫筆到設備環境中之后,就可以調用DeleteObject來刪除所創建的邏輯畫筆。
邏輯畫筆是一種“GDI對象”,GDI對象有六種:畫筆、刷子、位圖、區域、字體、調色板。
CreatePen的原型是:
HPEN CreatePen(iPenStyle, iWidth, crColor);
iPenStyle參數確定畫筆是實線、虛線還是點線。
iWidth參數確定線寬,如果iPenStyle不是實線,且iWith大於1,那么畫筆將變成實線。
crColor是RGB顏色。
獲取當前畫筆句柄:
hPen = GetCurrentObject(hdc, OBJ_PEN);
還可以建立一個邏輯畫筆LOGPEN結構,調用CreatePenIndirect來創建畫筆。
LOGPEN logpen;
此結構有三個成員:UINT lopnStyle 是畫筆線型;POINT lopnWidth是按邏輯單位度量的畫筆寬度,只用其中的x值;COLORREF lopnColor是畫筆顏色
41 填充空隙
點式畫筆和虛線畫筆的空隙的着色取決於設備環境的兩個屬性——背景模式和背景顏色。默認的背景模式是OPAQUE,在這種方式下,Windows使用背景色填充空隙,默認的背景色為白色。
下述調用用來改變和獲取Windows用來填充空隙的背景色:
改變:SetBkColor(hdc, crColor);
獲取:GetBkColor(hdc);
下述調用用來改變和獲取背景模式:
改變:SetBkMode(hdc, 模式);
模式:TRANSPARENT,忽略背景色,並且不填充空隙。
OPAQUE默認。
獲取:GetBkMode(hdc);
42 繪圖方式
設備環境中定義的繪圖方式也影響顯示器上所畫線的外觀。
當Windows使用畫筆來畫線時,實際上執行畫筆像素與目標位置處原來像素之間的某種按位布爾運算。像素間的按位布爾運算叫做“光柵運算”,簡稱為“ROP”。由於畫一條直線只涉及兩種像素(畫筆和目標),因此這種布爾運算又稱為“二元光柵運算”,簡稱為“ROP2”。
在默認設備環境中,繪圖方式定義為R2_COPYPEN,這意味着Windows只是將畫筆像素復制到目標像素代替之。
Windows定義了16種不同的ROP2碼,用來設置不同的繪圖方式。
設置繪圖方式:SetROP2(hdc, iDrawMode);
獲取繪圖方式:iDrawMode = GetROP2(hdc);
43 繪制填充區域
Windows中有7個用來畫帶邊緣的填充圖形的函數:
① Rectangle:畫矩形
② Ellipse:畫橢圓
③ RoundRect:畫帶圓角的矩形
④ Pie:畫橢圓的一部分,使其看起來像一個扇形
⑤ Chord:畫橢圓的一部分,使其看起來像弓形
⑥ Polygon:畫多邊形
⑦ PolyPolygon:畫多個多邊形
Windows用設備環境中選擇的當前畫筆來畫圖形的邊界框,邊界框還使用當前背景方式、背景色彩和繪圖方式,跟畫線時一樣。
圖形以當前設備環境中選擇的刷子來填充。默認情況下,使用現有對象,這意味着圖形內部將畫成白色。
Windows定義6種現有刷子:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH。
也可以自己定義刷子 HBRUSH hBrush;
通過GetStockObject來獲取現有刷子:
hBrush = GetStockObject(WHITE_BRUSH);
通過SeletctObject將刷子選進設備環境:
SelectObject(hdc, bBrush);
如果要畫一個沒有邊界框的圖形,可以將NULL_PEN選進設備環境。
SelectObject(hdc, GetStockObject(NULL_PEN));
如果要畫一個沒有填充內部的圖像,可以將NULL_BRUSH選進設備環境。
SelectObject(hdc, GetStockObject(NULL_BRUSH));
畫多邊形函數的原型:
Polygon(hdc, apt, iCount);
apt參數是POINT結構的一個數組,iCount是點的數目。如果該數組中的最后一個點和第一個點不同,則Windows將會再加一條線,將最后一個點與第一個點連起來。
畫多個多邊形函數的原型:
PolyPolygon(hdc, apt, aiCounts, iPolyCount);
apt數組具有全部多邊形的所有點。
aiCounts數組給出了多邊形的端點數。
iPolyCount給出了所畫的多邊形的個數。
44 用畫刷填充內部
Rectangle、RoundRect、Ellipse、Chord、Pie、Polygon和PolyPolygon圖形的內部是用選進設備環境的當前畫刷來填充的。畫刷是一個8×8的位圖,它水平和垂直地重復使用來填充內部區域。
Windows有5個函數,可以自己創建邏輯畫刷,然后用SelectObject將畫刷選進設備環境。
① hBrush = CreateSolidBrush(crColor); 純顏色刷子
② hBrush = CreateHatchBrush(iHatchStyle, crColor); 帶影射線的刷子
crColor是影線的顏色,影線的間隙用設備環境定義的背景方式和背景色來着色。
③ CreatePatternBrush()
④ CreateDIBPatternBrushPt() 基於位圖的刷子
⑤ hBrush = CreateBrushIndirect(&logbrush);
該函數包含其他4個函數。
變量logbrush是一個類型為LOGBRUSH的結構,該結構有三個字段
UINT lbStyle;
COLORREF lbColor;
LONG lbHatch;
45 矩形函數
Windows包含了幾種使用RECT結構和“區域”的繪圖函數。區域就是屏幕上的一塊地方,是矩形,多邊形和橢圓的組合。
FillRect(hdc, &rect, hBrush); //用指定畫刷來填充矩形。該函數不需要事先將畫刷選進設備環境。 FrameRect(hdc, &rect, hBrush); //使用畫刷畫矩形框,但不填充矩形。 InvertRect(hdc, &rect); //將矩形中所有像素反轉。
常用矩形函數:
① SetRect(&rect, xLeft, yTop, xRight, yBottom); 設置矩形的4個字段值。
② OffsetRect(&rect, x, y); 將矩形沿x軸和y軸移動幾個單元。
③ InflateRect(&rect, x, y); 增減矩形尺寸
④ SetRectEmpty(&rect); 將矩形各字段設為0
⑤ CopyRect(&DestRect, &SrcRect); 將矩形復制給另一個矩形。
⑥ IntersectRect(&DestRect, &SrcRect1,&ScrRect2);獲取兩個矩形的交集
⑦ UnionRect(&DestRect, &SrcRect1,&ScrRect2); 獲取兩個矩形的並集
⑧ bEmpty = IsRectEmpty(&rect); 確定矩形是否為空
⑨ binRect = PtinRect(&rect, point);確定點是否在矩形內
46 創建和繪制區域
區域是對顯示器上一個范圍的描述,這個范圍是矩形、多邊形和橢圓的組合。
區域可以用於繪制和剪裁,通過將區域選進設備環境,就可以用區域來進行剪裁。
當創建一個區域時,Windows返回一個該區域的句柄,類型為HRGN。
HRGN hRgn;
① 創建矩形區域:
hRgn = CreateRectRgn(xLeft, yTop, xRight, yBottom);
或
hRgn = CreateRectRgnIndirect(&rect);
② 創建橢圓區域:
hRgn = CreateEllipticRgn(xLeft, yTop, xRight, yBottom);
或
hRgn = CreateEllipticRgnIndirect(&rect);
③ 創建多邊形區域:
hRgn = CreatePolygonRgn(&point, iCount, iPolyFillMode);
point參數是個POINT類型的結構數組;
iCount是點的數目;
iPolyFillMode是ALTERNATE或者WINDING
④ 區域的融合
iRgnType = CombineRgn(hDestRgn, hSrcRgn1, hSrcRgn2, iCombine);
這一函數將兩個源區域組合起來並用句柄hDestRgn指向組合成的目標區域。
iCombine參數說明了hSrcRgn1和hSrcRgn2是怎么組合的。 RGN_AND 公共部分 RGN_OR 全部 RGN_XOR 全部除去公共部分 RGN_DIFF hSrcRgn1不在hSrcRgn2的部分 RGN_COPY hSrcRgn1的全部,忽略hSrcRgn2 //區域的句柄可以用到4個繪圖函數: FillRgn(hdc, hRgn, hBrush); FrameRgn(hdc, hRgn, xFrame, yFrame); //xFrame, yFrame是畫在區域周圍邊框的寬度和高度。 InvertRgn(hdc, hRgn); PaintRgn(hdc, hRgn);
47 矩形與區域的剪裁
區域也在剪裁中扮演了一個角色。
InvalidateRect函數使顯示的一個矩形區域失效,並產生一個WM_PAINT消息。
InvalidateRect(hwnd, NULL, TRUE); 清除客戶區;
可以通過調用GetUpdateRect來獲取失效矩形的坐標。
使用ValidateRect函數使客戶區的矩形有效。
當接收到一個WM_PAINT消息時,無效矩形的坐標可以從PAINTSTRUCT結構中得到,該結構是用BeginPaint函數填充的。
Windows中有兩個作用於區域而不是矩形的函數:
InvalidateRgn(hwnd, hRgn, bErase);
和
ValidateRgn(hwnd, hRgn);
所以當接收到一個WM_PAINT消息時,可能由無效區域引起的。剪裁區域不一定是矩形。
SelectObject(hdc, hRgn);
或
SelectClipObject(hdc, hRgn);
通過將一個區域選進設備環境來創建自己的剪裁區域。
---------------------------------------------------------------------------------------------------------------------------
四 鍵盤
48 鍵盤基礎
Windows程序獲得鍵盤輸入的方式:鍵盤輸入以消息的形式傳遞給程序的窗口過程。
Windows用8種不同的消息來傳遞不同的鍵盤事件。
Windows程序使用“鍵盤加速鍵”來激活通用菜單項。加速鍵通常是功能鍵或字母同ctrl鍵的組合。Windows將這些鍵盤加速鍵轉換為菜單命令消息。
程序用來從消息隊列中檢索消息的MSG結構包括hwnd字段。此字段指出接收消息的窗口句柄。消息循環中的DispatchMessage函數向窗口過程發生該消息,此窗口過程與需要消息的窗口相聯系。當按下鍵盤上的鍵時,只有一個窗口過程接收鍵盤消息,並且此消息包括接收消息的窗口句柄。
接收特定鍵盤事件的窗口具有輸入焦點。
窗口過程通過捕獲WM_SETFOCUS和WM_KILLFOCUS消息來判定它的窗口何時擁有輸入焦點。WM_SETFOCUS指示窗口正在得到輸入焦點,WM_KILLFOCUS表示窗口正在失去輸入焦點。
當用戶按下並釋放鍵盤上的鍵時,Windows和鍵盤驅動程序將硬件掃描碼轉換為格式消息。Windows在“系統消息隊列”中保存這些消息。系統消息隊列是單消息隊列,它由Windows維護,用於初步保存用戶從鍵盤和鼠標輸入的信息。只有當Windows應用程序處理完前一個用戶輸入消息時,Windows才會從系統消息隊列中取出下一個消息,並放入應用程序的消息隊列。
49 擊鍵和字符
應用程序從Windows接受的關於鍵盤事件的消息可以分為擊鍵和字符兩類。
按下鍵是一次擊鍵,釋放鍵也是一次擊鍵。
對產生可顯示字符的擊鍵組合,Windows不僅給程序發送擊鍵消息,而且還發送字符消息。有些鍵不產生字符,對於這些鍵,Windows只產生擊鍵消息。
50 擊鍵消息
當按下一個鍵時,Windows把WM_KEYDOWN或者WM_SYSKEYDOWN消息放入有輸入焦點的窗口的消息隊列。
當釋放一個鍵時,Windows把WM_KEYUP或者WM_SYSKEYUP消息放入消息隊列。
可以有多個KEYDOWN,但相對來說只有一個KEYUP。
通過調用GetMessageTime可以獲得按下或者釋放鍵的相對時間。
51 系統擊鍵和非系統擊鍵
WM_SYSKEYDOWN和WM_SYSKEYUP中的“SYS”代表“系統”,它表示該擊鍵對Windows比對Windows應用程序更加重要。
程序通常可以忽略WM_SYSKEYDOWN和WM_SYSKEYUP消息,並將它們傳送到DefWindowProc。
如果想在自己的窗口過程中包括捕獲系統擊鍵的代碼,那么在處理這些消息之后再傳送到DefWindowProc,Windows就仍然可以將它們用於通常的目的。
對所有4類擊鍵消息,wParam是虛擬鍵代碼,表示按下或釋放的鍵。而lParam則包含屬於擊鍵的其他數據。
52 虛擬鍵碼
虛擬鍵碼保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN、WM_SYSKEYUP消息的wParam參數中。
53 lParam信息
在4個擊鍵消息中,wParam消息參數含有虛擬鍵碼,而lParam消息參數則含有對了解擊鍵非常有用的其他信息。
在lParam的32位中,分為6個字段。
0~15:重復計數;
16~23:8位OEM;
24:擴展鍵標志;
29:環境代碼;
30:鍵的先前狀態;
31:轉換狀態。
(1)重復計數
重復計數是該消息所表示的擊鍵次數。大多數情況下,重復計數設置為1。
在KEYDOWN消息中,重復計數可以大於1,表示該鍵重復n次。
在KEYUP消息中,重復計數總是1.
(2)OEM掃描碼
OEM掃描碼是由硬件(鍵盤)產生的代碼。Windows程序能夠忽略幾乎所有的OEM掃描碼,除非它取決於鍵盤的物理布局。
(3)擴展位標識
如果擊鍵結構來自IBM增強鍵盤的附加鍵之一,那么擴展鍵標志為1.
(4)環境代碼
環境代碼在按下Alt鍵后為1。對WM_SYSKEYDOWN和WM_SYSKEYUP消息,這一位總是1;對WM_KEYDOWN和WM_KEYUP消息,這一位總是0;
但是有2個例外:
① 如果活動窗口最小化了,則它沒有輸入焦點。這時候所有的擊鍵都產生WM_SYSKEYDOWN和WM_SYSKEYUP消息。
② 如果Alt鍵未按下,則環境代碼域被設置為0.
(5)鍵的先前狀態
如果在此之前鍵是釋放的,則鍵的先前狀態為0,否則為1.
對WM_KEYUP或者WM_SYSKEYUP消息,它總是設置為1.
對WM_KEYDOWN和WM_SYSKEYDOWN消息,此位可以是1,也可以是0.
(6)轉換狀態
如果鍵正在被按下,則轉換狀態為0;
如果鍵正在被釋放,則轉換狀態為1.
對WM_KEYDOWN和WM_SYSKEYDOWN消息,此域為0;
對WM_KEYUP或者WM_SYSKEYUP消息,此域為1.
54 換擋狀態
在處理擊鍵消息是,可能需要知道是否按下了換擋鍵(Shift, Ctrl, Alt)或開關鍵(Caps Lock, Num Lock, Scroll Lock)。通過調用GetKeyState函數,就能獲得此信息。
int iState;
iState = GetKeyState(VK_SHIFT);
如果按下了Shift,則iState值為負。(高位被置1)。
55 字符消息
在WinMain中,有這樣一個消息循環:
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* GetMessage函數用隊列中的下一個消息填充msg結構的字段。 DispatchMessage以此消息為參數調用適當的窗口過程。 TranslateMessage函數將擊鍵消息轉換為字符消息。如果消息為WM_KEYDOWN或WM_SYSKEYDOWN,並且擊鍵與換擋狀態相結合產生一個字符,
則TranslateMessage把字符消息放入消息隊列中。此字符消息將是GetMessage從消息隊列中得到的擊鍵消息之后的下一個消息。 */
56 四類字符消息
字符消息可以分為四類:WM_CHAR、WM_DEADCHAR和WM_SYSCHAR、WM_SYSDEADCHAR。
其中,WM_CHAR、WM_DEADCHAR是從WM_KEYDOWN消息得到的。WM_SYSCHAR、WM_SYSDEADCHAR是從WM_SYSKEYDOWN消息得到的。
大多數情況下,Windowns程序會忽略除WM_CHAR消息之外的任何消息。
伴隨四個字符消息的lParam參數與產生字符代碼消息的擊鍵消息的lParam參數相同。不過,參數wParam不是虛擬鍵碼,而是ANSI或Unicode字符代碼。
57 消息順序
因為TranslateMessage函數從WM_KEYDOWN和WM_SYSKEYDOWN消息產生了字符消息,所以字符消息是夾在擊鍵消息之間傳遞給窗口過程的。
(1)如果按下A鍵,再釋放A鍵,將產生3個消息。
① WM_KEYDOWN “A”的虛擬鍵碼(0x41)
② WM_CHAR “a”的字符代碼(0x61)
③ WM_KEYUP “A”的虛擬鍵碼(0x41)
(2)如果按下Shift鍵和A鍵,然后釋放,將產生5個消息。
① WM_KEYDOWN 虛擬鍵碼VK_SHIFT
② WM_KEYDOWN “A”的虛擬鍵碼(0x41)
③ WM_CHAR “a”的字符代碼(0x61)
④ WM_KEYUP “A”的虛擬鍵碼(0x41)
⑤ WM_KEYUP 虛擬鍵碼VK_SHIFT
(3)如果按住A鍵不釋放,將自動重復產生一系列的擊鍵,那么對每個WM_KEYDOWN消息,就會得到一個字符消息。
① WM_KEYDOWN “A”的虛擬鍵碼(0x41)
② WM_CHAR “a”的字符代碼(0x61)
③ WM_KEYDOWN “A”的虛擬鍵碼(0x41)
④ WM_CHAR “a”的字符代碼(0x61)
⑤ WM_KEYDOWN “A”的虛擬鍵碼(0x41)
⑥ WM_CHAR “a”的字符代碼(0x61)
⑦ WM_KEYUP “A”的虛擬鍵碼(0x41)
58 處理控制字符
處理擊鍵和字符消息的基本規則是:如果需要讀取輸入到窗口的鍵盤字符,那么可以處理WM_CHAR消息。如果需要讀取光標鍵、功能鍵、Delete鍵、Insert鍵、Shite鍵、Ctrl鍵、以及Alt鍵,那么可以處理WM_KEYDOWN消息。
對於刪除、制表、回車、退出鍵,它們產生WM_CHAR消息和WM_KEYDOWN消息,應該將這些按鍵處理成控制字符而不是虛擬鍵碼。
case WM_CHAR: { case ‘\b’: 刪除鍵← ….. break; case ‘\t’: 制表鍵tab ….. break; case ‘\n’: 回車 ….. break; case ‘\r’: 換行 ….. break; default: ….. break; }
59 插入符函數(鼠標閃爍)
主要有8個插入符函數:
① CreateCaret 創建與窗口相關的插入符;
② SetCaret 在窗口中設置插入符的位置
③ ShowCaret 顯示插入符
④ HideCaret 隱藏插入符
⑤ DestroyCaret 撤銷插入符
⑥ GetCaretPos 獲取插入符位置
⑦ GetCaretBlinkTime 獲取插入符閃爍時間
⑧ SetCaretBlinkTime 設置插入符閃爍時間
在Windows中,插入符定義為水平線、與字符大小相同的方框,或者與字符等高的豎線。
只有當窗口有輸入焦點時,窗口內顯示插入符才有意義。
通過處理WM_SETFOCUS和WM_KILLFOCUS消息,程序就可以確定它是否有輸入焦點。窗口過程在有輸入焦點的時候接受WM_SETFOCUS消息,失去輸入焦點的時候接受WM_KILLFOCUS消息。
使用插入符的規則:窗口過程在WM_SETFOCUS消息期間調用CreateCaret,在處理WM_KILLFOCUS消息期間調用DestroyCaret。
插入符剛創建的時候是隱蔽的,必須顯式使用ShowCaret函數將插入符設為可見。
當窗口過程處理一個非WM_PAINT消息而且希望在窗口內繪制某些東西時,必須調用HideCaret隱藏插入符。在繪制完畢之后,再調用ShowCaret顯式插入符。
---------------------------------------------------------------------------------------------------------------------------
五 鼠標
60 鼠標基礎
用GetSystemMetrics函數來確定鼠標是否存在:
fMouse = GetSystemMetrics(SM_MOUSEPRESENT);
要確定所安裝鼠標上鍵的個數,可使用:
cButtons = GetSystemMetrics(SM_CMOUSEBUTTONS); //如果沒有安裝鼠標,返回0.
當Windows用戶移動鼠標時,Windows在顯示屏上移動一個稱為“鼠標光標”的小位圖。鼠標光標有一個指向顯示屏上精確位置的單像素的“熱點”。
Windows支持幾種預定義的鼠標光標,程序可以使用這些光標。最常見的是稱為IDC_ARROW的斜箭頭。熱點在箭頭的頂端。IDC_CROSS光標的熱點在十字交叉線的中心。IDC_WAIT光標是一個沙漏,用於指示程序正在運行。
鼠標鍵動作的術語:
① 單擊 按下並放開一個鼠標鍵
② 雙擊 快速按下並放開鼠標鍵兩次
③ 拖曳 按住鼠標鍵並移動鼠標
對於三鍵鼠標,三個鍵分別被稱為左鍵、中鍵、右鍵。在Windows頭文件中定義的與鼠標有關的標識符使用縮寫LBUTTON、MBUTTON、RBUTTON。
61 客戶區鼠標消息
Windows只把鍵盤消息發送給擁有輸入焦點的窗口。鼠標消息與此不同,只要鼠標跨越窗口或者在某窗口中按下鼠標鍵,那么窗口過程就會收到鼠標消息,而不管該窗口是否活動或者擁有輸入焦點。
當鼠標移過窗口的客戶區時,窗口過程收到WM_MOUSEMOVE消息。當在窗口的客戶區按下或者釋放一個鼠標鍵時,窗口過程會收到如下消息:
鍵:
按下
釋放
雙擊鍵
左:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
中:
WM_MBUTTONDOWN
WM_MBUTTONUP
WM_MBUTTONDBLCLK
右:
WM_RBUTTONDOWN
WM_RBUTTONUP
WM_RBUTTONDBLCLK
僅當定義的窗口類能接收DBLCLK消息之后,窗口過程才能接收到雙擊消息。
對於所有這些消息來說,其lParam值均含有鼠標的位置:低位是x坐標,高位是y坐標。這兩個坐標是相對於窗口客戶區左上角的位置。
wParam的值指示鼠標鍵和Shift和Ctrl鍵的狀態。MK_前綴代表“鼠標鍵”。
MK_LBUTTON 按下左鍵
MK_MBUTTON 按下中鍵
MK_RBUTTON 按下右鍵
MK_SHIFT 按下Shift鍵
MK_CONTROL 按下Ctrl鍵
如果接收到WM_LBUTTONDOWN消息,而且值wParam & MK_SHIFT是TRUE,就知道當左鍵按下時,也按下了右鍵。
當把鼠標移過窗口的客戶區時,Windows並不為鼠標的每個可能的像素位置都產生一條WM_MOUSEMOVE消息。程序接收到WM_MOUSEMOVE消息的次數取決於鼠標硬件以及窗口過程處理鼠標移動消息的速度。
62 處理Shift鍵
要判斷鼠標移動時,是否按下了Shift鍵,可以通過wParam進行判斷,具體方法如下:
if (wParam & MK_SHIFT) { if (wParam & MK_CONTROL) { //按下了Shift和Ctrl鍵 } else { //按下了Shift鍵 } }
63 雙擊鼠標鍵
要確定為雙擊,這兩次單擊必須發生在其相互的物理位置十分接近的狀況下,默認時范圍是一個平均系統字體字符的寬,半個字符的高,並且發生在指定的時間間隔內。
如果希望窗口過程能夠接收到雙擊鍵的鼠標消息,那么在調用RegisterClass初始化窗口類結構時,必須在窗口風格中包含CS_DBCLKS標識符。
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBCLKS;
如果在窗口風格中未包含CS_DBCLKS,那么用戶在短時間內雙擊了鼠標鍵,窗口過程將接收到如下消息:
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP //如果窗口類風格中包含了CS_DBCLKS,那么雙擊鼠標鍵時窗口過程將接收到如下消息: WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBCLK WM_LBUTTONUP
64 非客戶區鼠標消息
在窗口的客戶區內移動或按下鼠標鍵時,將產生10個消息。如果鼠標在窗口的客戶區之外,但在窗口內,Windows將給窗口過程發生一個“非客戶區”鼠標消息。窗口非客戶區包括標題欄、菜單、和窗口滾動條。
通常不需要處理非客戶區鼠標消息,而將這些消息傳給DefWindowProc,從而使Windows執行系統功能。
消息中包含字母“NC”以表示是非客戶區消息。
如果鼠標在窗口的非客戶區移動,那么窗口過程接收到WM_NCMOUSEMOVE消息。其他動作產生如下消息:
鍵
按下
釋放
雙擊
左
WM_NCLBUTTONDOWN
WM_NCLBUTTONUP
WM_NCLBUTTONDBCLK
中
WM_NCMBUTTONDOWN
WM_NCMBUTTONUP
WM_NCMBUTTONDBCLK
右
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
WM_NCRBUTTONDBCLK
非客戶區鼠標消息的wParam和lParam參數意義如下:
wParam:指明移動或者單擊鼠標鍵的非客戶區位置。以HT開頭。
lParam:包含低位字的x坐標和高位字的y坐標,但是它們都是屏幕坐標,而不是客戶區坐標。
//使用以下兩個函數將屏幕坐標和客戶區坐標互換。 ScreenToClient(hwnd, &pt); ClientToScreen(hwnd, &pt);
如果屏幕坐標點在窗口客戶區的上面或者左邊,客戶區坐標x或y值就是負值。
65 命中測試
WM_NCHITTEST代表“非客戶區命中測試”。此消息優先於所有其他的客戶區和非客戶區鼠標消息。lParam參數含有鼠標位置的x和y屏幕坐標。wParam參數沒有用。
Windows應用程序通常把這個消息傳送給DefWindowProc,然后Windows用WM_NCHITTEST消息產生基於鼠標位置的所有其他鼠標消息。對於非客戶區鼠標消息,在處理WM_NCHITTEST消息時,從DefWindowProc返回的值將成為鼠標消息中的wParam參數,這個值可以是任意非客戶區鼠標消息的wParam值再加上以下內容:
HTCLIENT 客戶區
HTNOWHERE 不在窗口中
HTTRANSPARENT 窗口由另一個窗口覆蓋
HTERROR 使DefWindowProc產生蜂鳴聲
如果DefWindowProc在其處理WM_NCHITTEST消息后返回HTCLIENT,那么Windows將把屏幕坐標轉換為客戶區坐標並產生客戶區鼠標消息。
66 從消息產生消息
如果在一個Windows程序的系統菜單圖標上雙擊一下,那么程序將會終止。雙擊產生一些了的WM_NCHITTEST消息。由於鼠標定位在系統菜單圖標上,所以DefWindowProc將返回HTSYSMENU的值,並且Windows把wParam等於HTSYSMENU的WM_NCLBUTTONDBCLK消息放在消息隊列中。
當DefWindowProc接收到wParam參數為HTSYSMENU的WM_NCLBUTTONDBCLK消息時,就把wParam參數為SC_CLOSE的WM_SYSCOMMAND消息放入消息隊列中。同樣,窗口過程也把這個消息傳給DefWindowProc。DefWindowProc通過給窗口過程發生WM_CLOSE消息來處理該消息。
如果一個程序在終止前需要來自用戶的確認,那么窗口過程就必須捕獲WM_CLOSE,否則,DefWindowProc將調用DestroyWindow函數來處理WM_CLOSE。
67 捕獲鼠標
捕獲鼠標,只要調用SetCapture(hwnd);在這個函數調用之后,Windows將所有鼠標消息發給窗口句柄為hwnd的窗口過程。鼠標消息總是客戶區消息,即使鼠標正在窗口的非客戶區。lParam參數將指示鼠標在客戶區坐標中的位置。不過,當鼠標位於客戶區的左邊或者上方的時候,這些x和y坐標可以是負的。
當需要釋放鼠標時,調用ReleaseCapture();就可恢復正常。
如果鼠標被捕獲,而鼠標鍵並沒有被按下,並且鼠標光標移到了另一個窗口上,那么將不是由捕獲鼠標的那個窗口而是由光標下面的窗口來接收鼠標消息。
68 鼠標輪
鼠標輪的轉動產生一個WM_MOUSEWHEEL消息。
lParam參數將獲得鼠標的位置,坐標是相對於屏幕左上角的,不是客戶區的。
wParam參數低字包含一系列的標識,用於表明鼠標鍵和Shift與Ctrl鍵的狀態。
wParam的高字中有一個“delta”值,該值默認可以是120或-120,這取決於滾輪是向前轉動還是向后轉動。值120或-120表明文檔將分別向上或向下滾動三行。
---------------------------------------------------------------------------------------------------------------
六 計時器
69 計時器基礎
計時器是一種輸入設備,它周期性地每經過一個指定的時間間隔就用WM_TIMER消息通知應用程序一次。
可以通過調用SetTimer函數為Windows應用程序分配一個計時器。SetTimer有一個時間間隔范圍為1~4294967295毫秒的整型參數,這個值指示Windows每隔多長時間給程序發送WM_TIMER消息。
當程序用完計時器時,就調用KillTimer函數停止計時器消息。
KillTimer調用清除消息隊列中尚未被處理的WM_TIMER消息,從而使程序在調用KillTimer之后就不會再受到WM_TIMER消息。
70 系統和計時器
Windows計時器是PC的硬件和ROM BIOS構造的計時器邏輯的一種相對簡單的擴展。
BIOS的“計時器滴答”中斷約每54.915毫秒或者大約每秒18.2次。
在Microsoft Windows NT中,計時器的分辨率為10毫秒。
Windows應用程序不能以高於這個分辨率的速率接收WM_TIMER消息。在SetTimer調用中指定的時間間隔總是截尾為時鍾滴答的整數倍。例如,1000毫秒的間隔除以54.925毫秒≈18.207個時鍾滴答,截尾后為18個時鍾滴答,它實際上是989毫秒。對每個小於55毫秒的間隔,每個時鍾滴答都產生一個WM_TIMER消息。
71 計時器消息不是異步的
計時器是基於硬件計時器中斷。但WM_TIMER消息卻不是異步的。
WM_TIMER消息放在正常的消息隊列之中,和其他消息一起參加排序,因此,如果在SetTimer調用中指定間隔為1000毫秒,那么不能保持程序每1000毫秒就會收到一個WM_TIMER消息。如果其他程序的運行事件超過一秒,在此期間,程序將不會收到任何WM_TIMER消息。
Windows不能持續向消息隊列放入多個WM_TIMER消息,而是將多余的WM_TIMER消息合並成一個消息。
72 計時器的使用(方法一)
如果需要在整個程序期間使用計時器,那么可以在處理WM_CREATE消息時調用SetTimer,並在處理WM_DESTROY消息時調用KillTimer。
SetTimer函數如下所示:
SetTimer(hwnd, 1, uiMsecInterval, NULL); /* 第一個參數是其窗口過程將接收WM_TIMER消息的窗口的句柄; 第二個參數是計時器ID,只要是非0的整數就可以,如果設置多個計時器,那么各個計時器的ID應該不同。 第三個參數是一個32位無符號整數,以毫秒為單位指定一個時間間隔。 第四個參數是回調函數的地址,如果處理WM_TIMER消息不是回調函數,那么設置為NULL。 */ //KillTimer函數調用如下: KillTimer(hwnd, 1); // 第一個參數是其窗口過程將接收WM_TIMER消息的窗口的句柄; //第二個參數是計時器ID。 //KillTimer用於在任何時刻停止WM_TIMER消息。
當窗口過程收到一個WM_TIMER消息時,wParam參數等於計時器的ID值,lParam參數為0.如果需要設置多個計時器,那么對每個計時器都使用不同的計時器ID。wParam的值將隨傳遞到窗口過程的WM_TIMER消息的不同而不同。
73 計時器的使用(方法二)
第一種方法是把WM_TIMER消息發送到通常的窗口過程。
第二種方法是讓Windows直接將計時器消息發送給程序的另一個函數。
接收這些計時器消息的函數稱為“回調函數”,這是一個在程序之中,但是由Windows而不是程序本身調用的函數。先告訴Windows這個函數的地址,然后Windows調用此函數。
窗口過程其實就是一種回調函數。回調函數必須定義為CALLBACK,因為它是由Windows從程序的代碼段調用的。回調函數的參數和回調函數的返回值依賴於回調函數的目的。同計時器相關的回調函數中,輸入參數同窗口過程的輸入參數一樣。計時器回調函數不向Windows返回值,可以設置為VOID。
假設計時器回調函數稱為TimerProc,那么可以定義如下:
VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
WM_TIMER消息的處理過程;
}
TimerProc的參數hwnd是在調用SetTimer時指定的窗口句柄。Windows只把WM_TIMER消息消息送過TimerProc,因此消息參數總是等於WM_TIMER。iTimerID值是計時器ID,dwTimer值是與從GetTickCount函數的返回值兼容的值,是反映Windows啟動后所經過的毫秒數。
在使用回調函數處理WM_TIMER消息時,窗口過程中設置SetTimer的第4個參數由回調函數的地址取代,如下所示:
SetTimer(hwnd, iTimerID, iMsecInterval, TimerProc);
回調函數必須和窗口過程函數一樣,一起被聲明在程序的開始處,像TimerProc的函數聲明如下:
VOID CALLBACK TimerProc(HWND, UINT, UINT, DWORD);
74 計時器的使用(方法三)(少用)
設置計時器的第三種方法類似於第二種方法,只是傳遞給SetTimer的hwnd參數被設置為NULL,並且第二個參數計時器ID也被忽略,設置為0,最后才函數返回計時器ID。
iTimerID = SetTimer(NULL, 0, iMsecInterval, TimerProc);
如果沒有可用的計時器,那么從SetTimer返回的iTimerID值將為NULL。
KillTimer的第一個參數也必須是NULL,計時器ID必須是SetTimer的返回值。
傳遞給TimerProc計時器函數的hwnd參數也必須是NULL。
75 獲取當前時間
首先介紹下SYSTEMTIME結構,如下所示:
typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME;
SYSTEMTIME結構包含日期和時間。月份由1開始遞增,星期由0開始遞增(星期天是0)。wDay是本月的當前日子,從1開始遞增。
SYSTEM主要用於GetLocalTime和GetSystemTime函數。
GetSystemTime函數返回當前的世界時間(格林尼治時間)
GetSystemTime函數返回當地時間。
76 WM_SETTINGCHANGE消息
如果用戶改變了任何系統設置,Windows會產生WM_SETTINGCHANGE消息,並傳送給所有的應用程序。
------------------------------------------------------------------------------------------------------------------------
七 子窗口控制
77 子窗口控制概述
當子窗口的狀態發生改變時,子窗口處理鼠標和鍵盤消息並通知父窗口。子窗口這時就變成了其父窗口高一級的輸入設備。
可以建立自己的子窗口控制,也可以利用一些預定義的窗口類和窗口過程來建立標准的子窗口控制。
子窗口控制采用的形式有:按鈕、復選框、編輯框、列表框、組合框、文本串、滾動條。
子窗口控制在對話框中最常用。
子窗口控制的位置和尺寸是在程序的資源描述文中的對話框模板中定義的。也可以使用預定義的、位於普通窗口客戶區表面的子窗口控制。可以調用一次CreateWindow來建立一個子窗口,並通過調用MoveWindow來調整子窗口的位置和尺寸。父窗口過程向子窗口控制發送消息,子窗口控制向父窗口過程返回消息。
78 按鈕類
按鈕屬於窗口。
按鈕窗口風格都以字母BS開頭,它代表“按鈕風格”。按鈕使用CreateWindow創建。
HWND hwndButton; hwndButton = CreateWindow{ /* 參數1 ClassName 類名, 參數2 Window text 按鈕顯示的文本, 參數3 Window Style 窗口風格 有WS_CHILD | WS_VISIBLE | 按鈕種類 參數4 x位置 參數5 y位置 說明按鈕左上角相對於父窗口客戶區左上角的位置 參數6 Width 寬度 按鈕寬度 參數7 Height 高度 按鈕的高度 參數8 父窗口句柄 參數9 子窗口ID(強制HMENU類型) 參數10 實例句柄((LPCREATESTRUCT)lParam -> hInstance) 參數11 額外參數(一般為NULL) */ }
注:參數3中的按鈕種類分為:
下壓按鈕:BS_PUSHBUTTON、BS_DEFPUSHBUTTON 下壓按鈕
復選框:BS_CHECKBOX、BS_AUTOCHECKBOX
三狀態復選框:BS_3STATE、BS_AUTO3STATE
單選按鈕:BS_RADIOBUTTON、BS_AUTORADIOBUTTON
組合框:BS_GROUPBOX
自定義按鈕:BS_OWNEDRAW
參數9的子窗口ID對於每個子窗口都不同。在處理來自子窗口的WM_COMMAND消息時,ID幫助窗口過程識別出相應的子窗口。子窗口ID必須被強制轉換為HMENU。
參數10的實例句柄利用了如下事實:在處理WM_CREATE消息的過程中,lParam實際上是指向CREATESTRUCT結構的指針,該結構有一個hInstance成員。
也可以使用GetWindowLong(hwnd, GWL_HINSTANCE)函數調用來獲取實例句柄。
79 子窗口向父窗口發送消息
當用鼠標點擊按鈕時,子窗口控制就向其父窗口發送一個WM_COMMAND消息。父窗口過程捕獲WM_COMMAND消息,其wParam和lParam消息參數含義如下:
LOWORD(wParam) 子窗口ID
HIWORD(wParam) 通知碼
lParam 子窗口句柄
子窗口ID是在創建子窗口時傳遞給CreateWindow的值。
通知碼詳細表明了消息的含義。
當用鼠標單擊按鈕時,該按鈕文本的周圍會有虛線。這表明該按鈕擁有了輸入焦點,所有鍵盤輸入都將傳送給子窗口按鈕控制。然后按鈕即使擁有輸入焦點,也只能處理空格鍵。
80 父窗口向子窗口發送消息
父窗口可以給子窗口發送消息,這些消息包括以前綴為WM開頭的許多消息。另外,還有8個按鈕說明消息,以BM開頭。
BM_GETCHECK:獲取復選框和單選按鈕的選中標記
BM_SETCHECK:設置復選框和單選按鈕的選中標記
BM_GETSTATE:獲取按鈕狀態(正常還是按下)
BM_SETSTATE:設置按鈕狀態
BM_GETIMAGE
BM_SETIMAGE
BM_CLICK
BM_SETSTYLE:允許在按鈕創建后改變按鈕風格
每個子窗口控制都具有一個在其兄弟中唯一的窗口句柄和ID值,對於句柄和ID值這兩者,知道其中一個就可以獲得另一個。
id = GetWindowLong(hwndChild, GWL_ID);
hwndChild = GetDlgItem(hwndParent, id);
81 下壓按鈕
下壓按鈕控制主要用來觸發一個立即響應的動作,而不保**何開關指示。有兩種類型的按鈕控制窗口風格。分別是BS_PUSHBUTTON和BS_DEFPUSHBUTTON。
當用來設計對話框時,兩種風格作用不同,但當用作子窗口控制時,兩種類型的按鈕作用相同。
當按鈕的高度為文本字符高度的7/4倍時,按鈕的外觀最好。
按鈕的文本尺寸除了之前介紹的從TEXTMETRIC結構中獲取之外,更簡便的方法是通過GetDialogBaseUnits函數來獲得默認字體字符的高度和寬度。
此函數返回一個32位的值,其中低字位表示寬度,高字位表示高度。
當鼠標在按鈕中按下時,按鈕使用三維陰影重畫自己。
當鼠標放開時,就恢復按鈕原因,並向父窗口發送一個WM_COMMAND消息和BN_CLICKED通知碼。
82 復選框
復選框是一個文本框,文本通常出現在復選框的右邊。復選框通常用於允許用戶對選項進行選擇的應用程序中。復選框的常用功能如同一個開關:單擊一次顯示復選標記,再次單擊清除復選標記。
復選框最常用的2種風格是BS_CHECKBOX和BS_AUTOCHECKBOX。在使用BS_CHECKBOX時,需要自己向該控制發送BM_SETCHECK消息來設置復選標記。wParam參數置1時設置復選標記,置0時清除復選標記。通過向該控制發送BM_GETCHECK消息,可以得到該復選框的當前狀態。
可以用如下指令來翻轉復選標記:
SendMessage((HWND)lParam, BM_SETCHECK, (WPARAM) !SendMessage((HWND)lParma, BM_GETCHECK, 0, 0), 0);
對於BS_AUTOCHECKBOX風格,按鈕自己觸發復選標記的開和關,窗口過程可以忽略WM_COMMAND消息。當需要知道按鈕的當前狀態時,可以向控制發送BM_GETCHECK消息:
iCheck = (int)SendMessage(hwndButton, BM_GETCHECK, 0, 0);
如果按鈕被選中,則iCheck返回非0值。
其余兩種復選框風格是BS_3STATE和BS_AUTO3STATE,這兩種風格能顯示第三種狀態——復選框內是灰色的——它出現在向控制發送wParam = 2的WM_SETCHECK消息時。
83 單選按鈕
單選按鈕的形狀是個圓圈。圓圈內的加重圓點表示該單選按鈕已經被選中。單選按鈕有窗口風格BS_RADIOBUTTON或BS_AUTORADIOBUTTON兩種,后者只用於對話框。
當收到來自單選按鈕的WM_COMMAND消息時,應該向它發送wParam等於1的BM_SETCHECK消息來顯示其選中狀態:
SendMessage(hwndButton, BM_SETCHECK, 1, 0);
對相同組中的其他所有單選按鈕,可以通過向它們發送wParam等於0的BM_SETCHECK消息來顯示其未選中狀態。
SendMessage(hwndButton, BM_SETCHECK, 0, 0);
84 分組框
分組框即風格為BS_GROUPBOX的選擇框,它不處理鼠標輸入和鍵盤輸入,也不向其父窗口發送WM_COMMAND消息。分組框是一個矩形框,窗口文本在其頂部顯示。分組框常用來包含其他的按鈕控制。
85 更改按鈕文本
可以通過調用SetWindowText來更改按鈕內的文本:
SetWindowText(hwnd, pszString);
hwnd是窗口句柄,pszString是一個指向NULL終結串的指針。
對於一般的窗口,更改的是窗口的標題欄文本,對於按鈕控制來說,更改的是按鈕的顯示文本。
可以獲取窗口的當前文本:
iLength = GetWindowText(hwnd, pszBuffer, iMaxLength);
iMaxLength指定復制到pszBuffer指向的緩沖區中的最大字符數,該函數返回復制的字符數。
可以通過調用
iLength = GetWindowTextLength(hwnd);獲取文本的長度。
86 可見和啟用的按鈕
為了接受鼠標和鍵盤輸入,子窗口必須是可見的和被啟用的。當窗口是可見的但是非啟用時,窗口以灰色顯示正文。
如果在建立子窗口時,沒有將WS_VISIBLE包含在窗口類中,那么直到調用
ShowWindow(hwndChild, SW_SHOWNORMAL);
時子窗口才被顯示出來。
調用ShowWindow(hwndChild, SW_HIDE);
將子窗口隱藏起來。
使用EnableWindow(hwndChild, TRUE)來啟用窗口。
87 按鈕和輸入焦點
當Windows將輸入焦點從一個窗口轉換到另一個窗口時,首先給正在失去輸入焦點的窗口發送一個WM_KILLFOCUS消息,wParam參數是接收輸入焦點的窗口的句柄。然后,Windows向正在接收輸入焦點的窗口發送一個WM_SETFOCUS消息,同時wParam參數是正在失去輸入焦點的窗口的句柄。
可以通過調用SetFocus來恢復輸入焦點,如:
case WM_KILLFOCUS: if (hwnd == GetParent((HWND)wParam)) SetFoucs(hwnd); return 0;
88 靜態類
在CreateWindow函數中指定窗口類為“static”,就可以建立靜態的子窗口控制。這些子窗口既不接收鼠標或鍵盤輸入,也不向父窗口發送WM_COMMAND消息。
當在靜態子窗口上移動或按下鼠標時,這個子窗口將捕獲WM_NCHITTEST消息,並將HTTRANSPARENT的值返回給Windows,這將使Windows向其下層窗口發送相同的WM_NCHITTEST消息。
89 滾動條類
滾動條類是可以在父窗口的客戶區的任何地方出現的子窗口。可以使用預先定義的窗口類“scrollbar”以及兩個滾動條風格SBS_VERT和SBS_HORZ中的一個來建立子窗口滾動條控制。
與按鈕控制不同,滾動條控制不向父窗口發送WM_COMMAND消息,而是像窗口滾動條一樣發送WM_VSCROLL和WM_HSCROLL消息。在處理滾動條消息時,可以通過lParam參數來區分開窗口滾動條與滾動條控制。對於窗口滾動條,其值是0.對於滾動條控制,其值是滾動條窗口句柄。對於窗口滾動條和滾動條控制來說,wParam參數的高位字和低位字的含義相同。
窗口滾動條有固定的寬度,但滾動條控制可以自己修改尺寸。使用CreateWindow調用時,給出矩形尺寸來確定滾動條控制的尺寸。
如果想建立與窗口滾動條相同的滾動條控制,那么可以使用GetSystemMetrics獲取水平滾動條的高度GetSystemMetrics(SM_CYHSCROLL)或者垂直滾動條的寬度GetSystemMetrics(SM_CXVSCROLL);
滾動條窗口風格標識符SBS_LEFTALIGN、SBS_RIGHTALIGN、SBS_TOPALIGN和SBS_BUTTOMALIGN給出滾動條的標准尺寸,但是這些風格只在對話框中對滾動條有效。
對於滾動條控制,可以使用如下調用來設置范圍和位置:
SetScrollRange(hwndScroll, SB_CTL, iMin, iMax, bRedraw)
SetScrollPos(hwndScroll, SB_CTL,iPos, bRedraw)
SetScrollInfo(hwndScroll, SB_CTL, &si, bRedraw)
滾動條兩端按鈕之間較大的區域顏色是有COLOR_BTNFACE和COLOR_BTNHIGHLIGHT一起來確定的。
如果捕獲了WM_CTLCOLORSCROLLBAR消息,那么可以在消息處理中返回畫刷以取代該顏色。
90 窗口子類化
一般我們的消息都是傳給Windows程序的WndProc窗口過程的,但是當窗口內有子窗口控制時,我們可以給這個子窗口設置一個新的窗口過程,這個技術叫做“窗口子類化”。它能讓我們給現存的窗口過程(新的)設置“鈎子”,以便在程序中處理一些消息,同時將其他所有消息傳遞給舊的窗口過程(WndProc)。
Win32的子類化的原理是靠攔截Windows系統中的某些消息來自己進行處理,而不是交給WndProc或DefWindowProc。
將GWL_WNDPROC標識符作為參數來調用GetWindowLong,可以得到這個窗口過程的地址。
可以調用SetWindowLong給子窗口設置一個新的窗口過程。
可以用函數指針的辦法,將我們感興趣的消息攔截下來,處理完之后再讓預定義的窗口過程處理。這個過程大致如下:
WNDPROC OldProc;(用來保存舊的WndProc窗口過程)
OldProc = (WNDPROC)SetWindowsLong(hWnd, GWL_WNDPROC, (LONG)NewProc);
當然,這里的新窗口過程NewProc是預先由你實現好的。上述代碼執行以后,系統在處理hwnd的窗口消息時,就會先進入你實現的NewProc回調過程,然后在處理過你感興趣的消息之后,通過CallWindowProc函數和你預先保存的OldProc再次回到原來的回調過程中完成剩余的工作。
91 編輯類
當建立子窗口時,CreateWindow第一個參數,即類名使用“edit”,根據CreateWindow調用中的x位置、y位置、寬度、高度等這些參數定義了一個矩形。此矩形含有可編輯文本。當子窗口控制擁有輸入焦點時,可以輸入文本,移動光標,使用鼠標或者Shift鍵與一個光標鍵來選取部分文本,也可以刪除、剪切、復制、粘帖文本。
92 編輯類風格
① 是WS_CHILD風格。
② 是編輯控制中的文本對齊方式,可以左對齊ES_LEFT、右對齊ES_RIGHT、居中ES_CENTER。
③ 編輯控制是單行文本還是多行文本,默認是單行,如果要處理回車鍵,需要增加風格ES_MULTILINE。
④ 滾動條功能,縱向是ES_AUTOVSCROLL,橫向是ES_AUTOHSCROLL。
⑤ 邊框,默認是沒邊框的,可以使用風格WS_BORDER。
93 編輯控制通知
編輯控制給父窗口過程發生WM_COMMAND消息,wParam和lParam參數和按鈕控制一樣。
LOWORD(wParam) 子窗口ID
HIWORD(wParam) 通知碼(EN開頭)
lParam 子窗口句柄
通知碼如下所示:
EN_SETFOCUS 獲得輸入焦點
EN_KILLFOCUS 失去輸入焦點
EN_CHANGE內容將改變
EN_UPDATE 內容已經改變
EN_ERRSPACE 輸入的文本超過30000個字符
EN_MAXTEXT 插入之后的文本超過30000個字符
EN_HSCROLL 編輯控制的水平滾動條被單擊
EN_VSCROLL 編輯控制的垂直滾動條被單擊
94 發送給編輯控制的消息
發送給編輯控制的消息運行剪切、復制、清除當前的選擇。用戶使用鼠標或Shift鍵減少光標控制鍵選擇文本並進行上面的操作。
剪切:SendMessage(hwndEdit, WM_CUT, 0, 0); 復制:SendMessage(hwndEdit, WM_COPY, 0, 0); 清除;SendMessage(hwndEdit, WM_CLEAR, 0, 0); 粘帖:SendMessage(hwndEdit, WM_PASTE, 0, 0);
//獲取當前選中文本的起始位置和末尾位置: SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&iStart, (LPARAM)&iEnd); //末尾位置實際是最后一個選擇字符的位置加1。 //選擇文本: SendMessage(hwndEdit, EM_SETSEL, iStart, iEnd); //文本置換: SendMessage(hwndEdit, EM_REPLACESEL, 0, (LPARAM)szString); //獲取多行文本的行數: iCount = SendMessage(hwndEdit, EM_GETLINECOUNT, 0, 0); //對任何特定的行,可以獲取距離編輯緩沖區文本開頭的偏移量: iOffset = SendMessage (hwndEdit, EM_LINEINDEX, iLine, 0); //其中,行數從0開始計算,iLine值為-1時返回包含光標所在行的偏移量。 //獲取行的長度: iLength = SendMessage (hwndEdit, EM_LINELENGTH, iLine, 0); //將一行復制到一個緩沖區: iLength = SendMessage (hwndEdit, EM_GETLINE, iLine, (LPARAM) szBuffer) ;
95 列表框類
列表框也屬於子窗口控制。列表框是文本串的集合,這些文本串是一個矩形中可以滾動顯示的列狀列表。程序通過向列表框窗口過程發送消息,可以在列表中增加或者刪除串。當列表框中的某項被選定時,列表框控制就向其父窗口發送WM_COMMAND消息,父窗口也就可以確定選定是哪一項。
列表框可以是單選的,也可以是多選的。選定的項被加亮顯示,並且是反顯的。
在單項選擇的列表框中,用戶按空格鍵就可以選定光標所在位置的項。方向鍵移動光標和當前選擇指示,並且能夠滾動列表框的內容。
96 列表框風格
當使用CreateWindow建立列表框子窗口時,應該將“listbox”作為窗口類,將WS_CHILD作為窗口風格。但是,這個默認列表框風格不向其父窗口發送WM_COMMAND消息。所以,一般都要包括列表框風格標識符LBS_NOTIFY。它允許父窗口接收來自列表框的WM_COMMAND消息。如果希望列表框對其中各項進行排序,那么可以使用另一個風格LBS_SORT。
如果想建立一個多選選擇的列表框,那么可以使用風格LBS_MULTIPLESEL。
默認的列表框是無邊界的,所以一般都要加上WS_BORDER來加上邊界。
使用WS_VSCROLL來增加垂直滾動條。
有一個列表框風格,綜合了上述各種風格,那就是LBS_STANDARD風格。
97 將文本串放入列表框
將文本串放入列表框可以通過調用SendMessage給列表框窗口過程發消息來實現這一點。文本串通常通過以0開始計數的下標數來引用,其中0對應於最頂上的項。
一般子窗口列表框控制的句柄定義為hwndList
下標值定義為iIndex
在使用SendMessage傳遞文本串的情況下,lParam參數是指向null結尾串的指針。
當窗口過程存儲的列表框內容超過了可用內存空間時,SendMessage將返回LB_ERRSPACE(定義為-2)。如果是其他原因出錯,那么將返回LB_ERR(-1).
如果采用LBS_SORT風格,那么填充列表框最簡單的方法是借助LB_ADDSTRING消息:SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)szString);
//如果沒有采用LBS_SORT,那么可以使用LB_INSERTSTRING指定一個下標值,將字符串插入到列表框中: SendMessage (hwndList, LB_INSERTSTRING, iIndex, (LPARAM) szString) ; //如果iIndex等於4,那么szString將變為下標值為4的串——從頂頭開始算起的第5個串。下標值為-1時,將串增加在最后。 //可以在指定下標值的同時使用LB_DELETESTRING參數,這就可以從列表框中刪除串: SendMessage (hwndList, LB_DELETESTRING, iIndex, 0); //可以使用LB_RESETCONTENT清除列表框中的所有內容: SendMessage (hwndList, LB_RESETCONTENT, 0, 0);
98 選擇和獲取項
//獲取列表框項數:(LB_GETCOUNT) iCount = SendMessage (hwndList, LB_GETCOUNT, 0, 0); //加亮選中項:(LB_SETCURSEL) SendMessage (hwndList, LB_SETCURSEL, iIndex, 0); //將lParam設置為-1,取消所有選擇。 //根據項的第一個字母來選擇:(LB_SELSECTSTRING) iIndex = SendMessage (hwndList, LB_SELECTSTRING, iIndex, (LPARAM) szSearchString); //iIndex等於-1時,從頭開始搜索。 //當得到來自列表框的WM_COMMAND消息時,可以通過使用LB_GETCURSEL來確定當前選項的下標: (LB_GETCURSEL) iIndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0); //可以確定列表框中串的長度:(LB_GETTEXTLEN) iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0); //可以將某項復制到文本緩沖區:(LB_GETTEXT) iLength = SendMessage (hwndList, LB_GETTEXT, iIndex, (LPARAM) szBuffer);
99 接收來自列表框的消息
當用戶用鼠標單擊列表框時,列表框將接收輸入焦點。
列表框控制向其父窗口發送WM_COMMAND消息,對按鈕和編輯控制來說wParam和lParam參數的含義是相同的。
LOWORD(wParam) 子窗口ID HIWORD(wParam) 通知碼(LBN開頭) lParam 子窗口句柄 //通知碼及其值如下所示: LBN_ERRSPACE -2 表示列表框已經超出運行空間 LBN_SELCHANGE 1 表明當前選擇已經被改變 LBN_DBLCLK 2 表明某項已經被鼠標雙擊 LBN_SELCANCEL 3 LBN_SETFOCUS 4 列表框獲得焦點 LBN_KILLFOCUS 5 列表框失去焦點 //只有列表框窗口風格包括LBS_NOTIFY時,列表框控制才向父窗口發送LBN_SELCHANGE和LBN_DBLCLK碼
100 文件列表
LB_DIR是功能最強的列表框消息,它用文件目錄表填充列表框,並且可以選擇將子目錄和有效的磁盤驅動器也包括進來:
SendMessage(hwndList, LB_DIR, iAttr, (LPARAM)szFileSpec);
① 使用文件屬性碼:
當LB_DIR消息的iAttr值為DDL_READWRITE時,列表框列出普通文件、只讀文件和具有歸檔位集的文件。
當值為DDL_DIRECTORY時,列表框除列出上述文件之外,還列出子目錄,目錄位於方括號之內。
當值為DDL_DRIVES | DDL_DIRECTORY時,那么列表將擴展到包括所有有效的驅動器,驅動器字母顯示在虛線之間。
當值為DDL_EXCLUSIVE | DDL_ARCHIVE時,即將iAttr的最高位置位可以列出帶標志的文件,而不包括普通文件。
② 文件列表的排序
lParam參數是指向文件說明串如“*.*”的指針,這個文件說明串不影響列表框中的子目錄。
用戶也許希望給列有文件清單的列表框使用LBS_SORT消息,列表框首先列出符合文件說明的文件,再列出子目錄名。列出的第一個子目錄名將采用下面的格式:
[..]
這種兩個點的子目錄項允許用戶向根目錄返回一級。
最后,具體的子目錄名采用下面的形式:
[SUBDIR]
后面是以下面的形式列出的有效磁盤驅動器
[-A-]