1. 程式人生 > 實用技巧 >WIN32程式設計經驗總結

WIN32程式設計經驗總結

WIN32程式設計經驗總結

一 視窗和訊息

  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-]