Programming Windows 第五版讀書筆記 第三章 視窗和訊息

來源:互聯網
上載者:User
Programming windows 5th Edition Chapter 3 視窗和訊息

1. 本章講述了一個最簡單的帶視窗的windows程式HelloWin。

2. 視窗類別別。在建立視窗(調用CreateWindow)之前,需要先註冊視窗類別別(RegisterClass)。所謂視窗類別別表示的是視窗大體應該遵循 的共性,比如按鈕視窗,他們的類別是一樣的,比如游標,背景,最關鍵的是訊息處理函數,這些東西都應該是一樣的,至於每個按鈕視窗不同的地方,比如大小, 位置等,放在CreateWindow中由我們來定義。所以視窗類別別就是抽象了視窗的一些共性的東西,多個視窗在CreateWindow的時候,可以共 享一個視窗類別別,也就是只需執行一次RegisterClass。視窗類別別中最重要的就是定義了訊息CALLBACK函數了,也就是訊息處理函數了。看到 這裡有人會擔心了,既然視窗類別別中定義了訊息處理函數,那麼,如果一堆視窗建立的時候都是用的一個視窗類別別的話,那這些視窗的訊息處理函數就都是一樣的 嘍?--沒錯,OK,那既然一樣,我怎麼對這些視窗的同一個訊息做不同的處理呢?--很簡單,在訊息處理函數中,有一個參數hWnd,用來標識了視窗的句 柄(也就是CreateWindow返回的東西),用這個就可以區分不同的視窗了。

3. 來看本章HelloWin的代碼:

Code: Select all
/*------------------------------------------------------------
   HELLOWIN.C -- Displays "Hello, Windows!" in client area
                 Eric Zhang 2007
  ------------------------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("HelloWin") ;
     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 (BLACK_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,                  // window class name
                          TEXT ("The Hello Program"), // window caption
                          WS_OVERLAPPEDWINDOW,        // window style
                          CW_USEDEFAULT,              // initial x position
                          CW_USEDEFAULT,              // initial y position
                          CW_USEDEFAULT,              // initial x size
                          CW_USEDEFAULT,              // initial y size
                          NULL,                       // parent window handle
                          NULL,                       // window menu handle
                          hInstance,                  // program instance handle
                          NULL) ;                     // creation parameters
     
     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)
{
     HDC         hdc ;
     PAINTSTRUCT ps ;
     RECT        rect ;
     
     switch (message)
     {
     case WM_CREATE:
          PlaySound (TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
          return 0 ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
         
          GetClientRect (hwnd, &rect) ;
         
          DrawText (hdc, TEXT ("Hello, Windows!"), -1, &rect,
                    DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;
         
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

4. HelloWin很簡單,就是建立了一個視窗,在中央位置寫上文字,另外在視窗顯示的時候會播放一個wav檔案。註:為了播放這個wav檔案,代碼中調用了PlaySound函數,要使用這個函數在Project中記得將WinMM.lib檔案連結進去。

5. 現在來看代碼。首先代碼中有很多常量,比如CS_HREDRAW, WM_PAINT等,這些常量不用記憶,需要的時候查手冊即可,這些常量都有特定的意義和一些約定俗成的書寫格式:

附件1

除此以外,代碼中還有一些其他的資料類型,如下:

附件2 附件3

附 件3中列的都是控制代碼,只是指向不同東西的控制代碼而已。在windows中,控制代碼使用非常普遍,就好像是Linux編程中的檔案描述符一樣。windows中 的控制代碼就是一個32位的整數,用來代表一個對象而已。很多windows函數都需要控制代碼,這樣windows函數才知道我們操作的對象是誰。

6. 匈牙利標記法。代碼中的變數名稱基本上都使用了匈牙利標記法,其實第一章中的程式開始就已經開始使用了,這裡做了一個總結,以後寫代碼可以參考:

附件4

7. 現在來看代碼。首先定義了一個函數原型,也就是我們的訊息處理函數的原型。LRESULT就是long型;CALLBACK和WINAPI一樣,就是 __stdcall,加上CALLBACK,windows將來調用我們的訊息處理函數的時候,免得發生call convention不一致的問題,所以一般都要加這個CALLBACK;WndProc的四個參數中,第一個參數是視窗控制代碼,第二個參數是訊息代碼,第 三個參數和第四個參數是兩個parameter,wParam在32位windows下,就是UINT -- unsigned int,lParam就是long型,在16位windows下,wParam原來是WORD -- unsigned short類型。所以,按照匈牙利標記法,wParam應該寫成uiParam,但由於原來16位windows下是wParam,所以就沒有修改保留了 下來。

8. 然後的代碼就是註冊視窗類別別了。註冊視窗類別別調用RegisterClass,這個函數只需要一個參數,就是視窗類別別structure WNDCLASS。由於這個WNDCLASS中含有兩個字串成員變數,所以回想起第二章的內容,自然這個WNDCLASS就有WNDCLASSA和 WNDCLASSW兩個版本了。而這個WNDCLASS中的字串自然就是 T 類型的字串了。OK,下面來看這個Structure中的內容:

style -- 指出視窗的風格。代碼中寫的是CS_HREDRAW|CS_VREDRAW,這表示視窗在橫向或縱向發生變化的時候,重繪視窗。後面會看到,這就是為什麼 視窗大小改變後,文字依然會顯示在視窗的正中,就是因為這裡的設定,視窗重繪,觸發WM_PAINT訊息。在WINUSER.H中可以找到全部的CS打頭 的(表示視窗類別別樣式)的常量。注意觀察這些常量的數值,其實他們每個數值都將位元中的某一位置成了1,所以,他們之間可以互相用 或 符號串連起來。

lpfnWndProc -- 這是最重要的了,指定訊息處理函數,這是一個函數指標。

cbClsExtra, cbWndExtra -- 預留的空間,如果需要,程式可以自訂這部分內容。

hInstance -- 就是我們這個程式的執行個體控制代碼,WinMain的第一個參數

hIcon -- 指定視窗表徵圖,該表徵圖出現在視窗標題列的最左邊和工作列中

hCursor -- 指定滑鼠的游標。這裡,LoadIcon和LoadCursor,如果第一個參數是NULL,就表示使用windows預定義好的那些表徵圖和滑鼠游標。具 體看這兩個函數的MSDN;如果我們要使用自己的表徵圖或游標,那麼,第一個參數要設定成hInstance,也就是我們這個程式的執行個體控制代碼。

hbrBackground -- 視窗的背景。hbr表示handle to a brush。windows中的brush表示用來填充一個地區的著色樣式。Windows有幾個標準的brush,他們稱為Stock Brush(庫存畫刷)。所以我們用GetStockObject得到了一個白色畫刷。

lpszMenuName -- 指定應用程式菜單

lpszClassName -- 視窗類別別名稱。這裡的內容要和CreateWindow中的第一個參數一樣,如果我們建立的這個window想使用這個視窗類別別的話。

9. OK,一切具備,調用RegisterClass了,這裡代碼做了一個出錯處理判斷,這是應該的,因為RegisterClass也有A/W兩個版本,如 果我們定義了UNICODE條件編譯變數,那麼將來被調用的將是RegisterClassW,而win98雖然有這個函數,但是一調這個函數win98 會立刻返回錯誤(win9x只有很小一部分支援Unicode,比如MessageBoxW是可以用的)。

BTW: 對於出錯的處理,很多時候我們會調用GetLastError函數,這個函數能返回上次操作失敗的錯誤碼,這些錯誤碼對應的含義可以在MSDN的 /Platform SDK/Windows Base Services/Debugging and Error Handling/Error Codes/System Errors - Numerical Order中找到

10. 然後就看到CreateWindow函數了。代碼中有注釋,就不一項一項說了。只有第二個參數,CW_OVERLAPPEDWINDOW,這個常量是一批常量的合集(定義在WINUSER.H中):

Code: Select all
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \
                             WS_CAPTION        | \
                             WS_SYSMENU        | \
                             WS_THICKFRAME     | \
                             WS_MINIMIZEBOX    | \
                             WS_MAXIMIZEBOX)

用來表示視窗的表現形態。

11. 然後代碼調用了ShowWindow,用來顯示視窗,然後又調了UpdateWindow,這個函數會觸發WM_PAINT訊息,用來初始化視窗中的圖形和文字。

其 實我自己試了一下,發現不調用UpdateWindow也照樣有WM_PAINT訊息的產生,視窗也照樣能正常顯示。原來我以為這裡調用 UpdateWindow只是為了規範,可是後來通過下一章的學習,發現這裡調用UpdateWindow是有道理的。道理就在於 UpdateWindow是產生WM_PAINT訊息,但是不同的是,這次產生的WM_PAINT訊息是非入隊訊息,也就是說,調用這個函數 後,windows會立馬調用我們的訊息處理函數,直到這個訊息處理完畢,UpdateWindow才返回。如果不使用這個函數,一般情況下產生的 WM_PAINT訊息只會排在訊息佇列中,而且windows約定WM_PAINT訊息是一個優先順序低的訊息,所以,這樣會導致介面更新不及時--簡言 之,當我們需要視窗介面立即更新的時候,請使用UpdateWindow,在很多地方都可以使用。

12. 然後就進入訊息迴圈了,用GetMessage從訊息佇列中取出訊息,注意:windows中不是什麼訊息都會進訊息佇列的,有些訊息是不進訊息佇列的, 此時windows會直接調用訊息處理函數,這些訊息叫非入隊訊息。不過我們不需要關心這些複雜的問題,我們只需要知道--任何訊息都會在我們的訊息處理 函數中被處理。GetMessage中MSG的結構是這樣的:

Code: Select all
typedef struct tagMSG
{
     HWND   hwnd ;
     UINT   message ;
     WPARAM wParam ;
     LPARAM lParam ;
     DWORD  time ;
     POINT  pt ;
} MSG, * PMSG ;

GetMessage函數的參數含義可以看MSDN,第二個參數為NULL表示接受本進程建立的所有視窗的訊息;第三個參數和第四個參數表示過濾 訊息的min和max值,也就是說,訊息編號在這個區間內的才會被GetMessage抓下來,如果這兩個值都是0,那就是沒有訊息過濾。 GetMessage函數會一直返回非0的數,除非取到了WM_QUIT訊息。

MSG結構中,hwnd是訊息發生視窗控制代碼,message是訊息編號,wParam,lParam是訊息參數,可以在這裡取到訊息的詳細內容,time是發生時間,PT是發生該訊息的時候滑鼠所在的座標。

13. TranslateMessage是把msg傳給windows,進行一些鍵盤轉換。這一點會在第六章深入討論;DispatchMessage是把 msg傳回給windows,然後windows就會調用我們的訊息處理函數(WndProc),所以對於入隊訊息,是由我們程式手動 GetMessage,然後再派發的,非入隊訊息不通過GetMessage,直接由windows調用訊息處理函數。

14. 對於訊息迴圈和訊息處理。有幾點需要重視:

1. DispatchMessage將訊息派發出去後,會一直等到這個訊息被處理完(訊息處理函數返回了),本函數才會返回,GetMessage才會被執行去取下一條訊息

2. 在訊息處理函數中,也是一樣,如果在訊息處理過程中觸發了其他的訊息,那麼觸發訊息的代碼也會block,一直到被觸發出來的新訊息被處理完為止。

3. 訊息處理函數必須return 0,除非是return DefWindowProc。

15. 下面的代碼就是訊息處理函數了,有這麼一些要點:

WM_CREATE訊息會在CreateWindow執行過程中產生

對於WM_PAINT訊息的處理,基本上都是從BeginPaint開始,以EndPaint結束。因為BeginPaint會返回一個hdc, 有了這個hdc,我們才能在視窗上寫字,繪圖。而且BeginPaint會填充一個PAINTSTRUCTURE,能告訴我們哪些地方需要重繪 (invalid)。在BeginPaint中,如果顯示地區背景沒有被刪除,則由windows來刪除,windows根據註冊視窗類別別中的背景來擦除 顯示地區。如果我們的訊息處理函數不處理WM_PAINT,那麼DefWindowProc只是簡單的調用BeginPaint和EndPaint來使顯 示地區重新生效。

WM_DESTROY訊息相應中我們調用了PostQuitMessage函數,該函數 會發出WM_QUIT訊息,GetMessage碰到這個訊息會返回0,從而WinMain中的訊息迴圈結束。參數0會被設定到該訊息的wParam,所 以我們在WinMain中return了msg.wParam

有時候,DefWindowProc處理完訊息後會產生其它的訊息。例如,假設使用者執行HELLOWIN,並且使用者最終單擊了 Close按鈕,或者假設用鍵盤或滑鼠從系統功能表中選擇了 Close, DefWindowProc處理這一鍵盤或者滑鼠輸入,在檢測到使用者選擇了Close選項之後,它給視窗訊息處理常式發送一條 WM_SYSCOMMAND訊息。WndProc將這個訊息傳給DefWindowProc。DefWindowProc給視窗訊息處理常式發送一條 WM_CLOSE訊息來響應之。WndProc再次將它傳給DefWindowProc。DestroyWindow呼叫DestroyWindow來響 應這條WM_CLOSE訊息。DestroyWindow導致Windows給視窗訊息處理常式發送一條WM_DESTROY訊息。WndProc再呼叫 PostQuitMessage,將一條WM_QUIT訊息放入訊息佇列中,以此來響應此訊息。這個訊息導致WinMain中的訊息迴圈終止,然後程式結 束。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.