標籤:
一、WINDOWS的訊息和視窗簡介:
1、什麼是windows在這裡我就不介紹了,但是作為一個程式員我們要知道WINDOWS最重要的一個也是我們程式員常用的一個東西就是訊息。視窗是以訊息的形式輸入的,視窗也用訊息與其它視窗通訊。
2、我們常會說windows給程式發送了一個訊息,其實這是指windows調用程式中的一個函數,該函數的參數描述了這個特定訊息。這種位於windows程式中的函數稱為“表單訊息處理常式”。程式建立的每一個表單都有相關的視窗訊息處理常式。這個視窗訊息處理常式是一個函數,既可以在程式中,也可以在動態連結程式庫中。Windows通過調用視窗訊息處理常式來給表單發送訊息。視窗訊息處理常式根據此訊息進行處理,然後將控制傳回給windows。
3、在對象導向的程式設計中,對象是程式與資料的組合。視窗是一種對象,其程式是視窗訊息處理常式。資料是視窗訊息處理常式儲存的資訊和windows為每個視窗以及系統中那個視窗類別別儲存的資訊。
4、windows程式開始執行後,windows為該程式建立一個“訊息佇列”。這個訊息佇列用來存放該程式可能建立的各種不同視窗的訊息。程式中有一小段程式碼,叫做“訊息迴圈”,用來從隊列中取出訊息,並且將它們發送給相應的視窗訊息處理常式。有些訊息直接發送給視窗訊息處理常式,不用放入訊息佇列中。
二、一個真正的windows程式:
// HELLOWIN.C/*------------------------------------------------------------------------HELLOWIN.C -- Displays "Hello, Windows 98!" in client area(c) Charles Petzold, 1998-----------------------------------------------------------------------*/#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){ // TEXT 宏——UNICODE標識 static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; MSG msg ; WNDCLAS wndclass ; // 視窗類別 wndclass.style = CS_HREDRAW | CS_VREDRAW ; // 視窗類別風格 wndclass.lpfnWndProc = WndProc ; // 視窗過程 wndclass.cbClsExtra = 0 ; // 在類結構儲存的視窗結構中預留一些額外空間 wndclass.cbWndExtra = 0 ; // 在Windows內部儲存的視窗結構中預留一些額外空間 wndclass.hInstance = hInstance ; // 程式的執行個體控制代碼 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; // 載入表徵圖資源 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; // 載入游標資源 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; // GetStockObject擷取(畫筆、刷子等)GDI對象 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, // 父視窗控制代碼 NULL, // 菜單控制代碼 hInstance, // program instance handle NULL ) ; // creation parameters // 視窗建立成功後,系統將在記憶體中為其分配一塊記憶體,但是此時視窗並未顯示在顯示器上,所以需要使用兩個調用: ShowWindow (hwnd, iCmdShow) ; // 顯示視窗 UpdateWindow (hwnd) ; // 將導致客戶區被繪製 // 程式通過執行一個叫做“訊息迴圈”的代碼從訊息佇列中取出訊息 // 注意GetMessage(阻塞,若沒有訊息則不返還控制權)和PeekMessage(非阻塞,若沒有訊息也會返回)的區別 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; // 將msg結構傳給Windows,進行一些鍵盤轉換 DispatchMessage (&msg) ; // 將msg結構傳給Windows,然後Windows將裡面的訊息發給相應的視窗過程進行處理 } return msg.wParam ;}// CALLBACK類型(供系統調用)LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ HDC hdc ; PAINTSTRUCT ps ; RECT rect ; switch (message) { // 當Windows在處理CreateWindow函數時,視窗過程就會接收到WM_CREATE訊息 case WM_CREATE: PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ; return 0 ; // 當視窗用戶端區域的一部分或全部變成“無效”時,必須進行重新整理,WM_PAINT將通知程式 case WM_PAINT: // BeginPaint調用使整個客戶區有效,並返回一個“裝置環境控制代碼 hdc = BeginPaint (hwnd, &ps) ; // 當改變視窗大小時,WndProc通過調用GetClientRect來擷取變化後的視窗大小,重新繪製客戶區 GetClientRect (hwnd, &rect) ; // 在指定的地區內輸出格式化的文本 DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; EndPaint (hwnd, &ps) ; return 0 ; // 當使用者點擊關閉按鈕時發生 case WM_DESTROY: // 調用PostQuitMessage以標準方式響應WM_DESTROY訊息; // PostQuitMessage(0); // 該函數在程式的訊息佇列插入一個WM_QUIT訊息。 // GetMessage對於除了WM_QUIT訊息之外的從訊息佇列中取出的所有訊息都返回非0值。而當GetMessage取到一個WM_QUIT訊息時,返回0 PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ;}
1、程式碼分析:
(1)、在這裡你除了winMain你還可以看到一個WndPro的函數這個函數就是傳說中的訊息處理函數,即訊息迴圈就在這裡啟動並執行也是一個程式的心臟,有了他程式才可以得到正常的運行,他在視窗註冊類的類裡就被移植入視窗類別中wndclass.lpfnWndProc=WndPro;要先申明後使用。
(2)、在這裡我還要說一下下CreateWindow和WNDCLASS也就是建立視窗和註冊視窗的聯絡,他們之間就好比造車廠和設計車的關係,RegisterClass函數註冊了一個WNDCLASS的對象,然後CreateWindow通過其中的lpClassName這個參數也就是WNDCLASS對象的lpszClassName與視窗類別聯絡起來,lpszClassName都是字串的類型。
(3)、windows函數的調用:這個基本的架構裡我們調用了18個windows 函數如下:
LoadIcon // 載入表徵圖代程式使用 LoadCursor // 載入滑鼠游標供程式使用 GetStockObject // 取得一個繪圖物件 RegisterClass // 為程式視窗註冊視窗類別別 MessageBox // 顯示訊息框 CreateWindow // 根據視窗類別別建立一個視窗 ShowWindow // 在螢幕上顯示視窗 UpdateWindow // 指示視窗自我更新 GetMessage // 從訊息佇列中取得訊息 TranslateMessage // 轉譯某些鍵盤訊息 DispatchMessage // 將訊息發送給視窗訊息處理常式 PlaySound // 播放一個音效檔 BeginPaint // 開始繪製視窗 GetClientRect // 取得視窗顯示地區的大小 DrawText // 顯示字串 EndPaint // 結束繪製視窗 PostQuitMessage // 在訊息佇列中插入一個退出訊息 DefWindowProc // 執行內定訊息處理
(4)、在這個函數裡面有很多大小寫字母宏定義:這些解釋一下下
在上面的函數中出現了如下幾個大寫字母宏定義:
首碼: 類別 使用 CS 視窗類別別樣式 WNDCLASS.style=CS_HREDRAW|CS_VREDRAW; CW 建立視窗 CreateWindow---(CW) DT 繪製文字 DrawText----(DT) IDI 圖示ID LoadIcon(...) IDC 滑鼠ID LoadCursor(...) MB 訊息框 MessageBox------MB SND 聲音 PlaySound (...) WM 視窗訊息 WindowsMessage-------(WM) WS 視窗樣式 WindowStyle-----(WS)
(5)、我們常會問WPARAM和LPARAM有什麼區別幹什麼用: 其實這兩個參數是win16和win32並存時留下的產物現在都是32位了,原來wParam(WORD)是一個16位的lParam(LONG)是32位的,現在的WPARAM和LPARAM都是MS重新定義的,他們通常是與一個訊息有關的常量值如下: wParam 通常是一個與訊息有關的常量值,也可能是視窗或控制項的控制代碼。 lParam 通常是一個指向記憶體中資料的指標。 由於wParam、lParam和指標都是32位的,需要時可以強制類型轉換。具體表示什麼,與message相關,他們是事先定義好的。
(6)、WndPro函數傳回一個形態為LRESULT的值,議該值簡單地被定義為一個LONG,在這裡WndPro函數被指定成一個CALLBACK形態,其實也是一個和WINAPI一樣的宏定義他是一個_stdcall一個從右至左的壓棧方式,也就是傳說中的回呼函數(紙老虎不用怕)。
2、控制代碼簡介: 控制代碼是一個(通常為32位的)整數,它代表一個對象。Windows中的夠本類似傳統C或MS-Dos程式設計中使用的檔案控制代碼。程式幾乎總是通過呼叫window函數取得控制代碼。程式在函數中使用夠本,用以代表對象。控制代碼值對程式來說是無關緊要的,但是windows對於這個代號來說他就可以利用這個代號(控制代碼)使用相對應的對象。 一句話控制代碼是什麼,比如你是一個對象那麼控制代碼就是你的名字,如果windows要叫你去買醬油那麼他會給你說:“XX控制代碼去買醬油”,和叫對象去買醬油的效果是一樣,但是用了控制代碼會更好管理。(我不知道這樣理解是否會好一點!) 三個大寫標識符,用於不同形態的控制代碼:
標識符 含義 HINSTANCE 執行實體(程式自身)控制代碼 HWND 視窗控制代碼 HDC 裝置內容控制代碼
控制代碼用的很頻繁比如:HICON(表徵圖控制代碼)、HCURSOR(滑鼠游標控制代碼)、HBRUSH(畫刷控制代碼)、windows中的每個視窗都有一個夠本,程式用夠本來使用視窗表單控制代碼是windows程式所處理最重要的控制代碼之一。
3、我順便也說一下instance吧,其實這就相當於是你運行QQ整個QQ就是一個instance!
三、註冊視窗類別別: 1、視窗依照某一視窗類別別建立,視窗類別別用以標識處理視窗訊息的訊息處理常式。視窗類別別定義了視窗訊息處理常式和依據引類別建立的視窗的其它特徵。在建立視窗時,要定義一些該視窗所專屬的特徵。在為程式建立視窗之前,必須首先調用RegisterClass註冊一個視窗類別別。該函數只需要一個參數,即一個指向形態為WNDCLASS的結構指標。這些結構包括兩個指向字串的欄位(LPCTSTR lpszMenuName; LPCTSTR lpszClassName;--ASCII。 LPCWSTR lpszMenuName ;LPCWSTR lpszClassName;--UNICODE)因此結構在WINUSER.H表檔案中定義了兩種不同的方式(ASCII,UNICODE)在編程過程我們不必去理會!因為在預定義時已經處理過了(WNDCLASSA,WNDCLASSW---都用WNDCLASS就行)! 2、在表單類定義中最重要的是lpfnWndProc和最後一個欄位lpszClassName。
3、視窗類別別 他定義了視窗的一般特徵,因此可以使用同一視窗類別別建立許多不同的視窗,建立視窗時,可能指定有關視窗的更詳細的資訊。傳遞給RegisterClass函數的資訊會在一個資料結構中設定好,而傳遞給CreateWindow函數的資訊會在函數單獨的參數中設定好。
四、訊息迴圈:
Windows為當前執行的每個windows程式維護一個“訊息佇列”。在發生輸入事件後,windows將事件轉換為一個訊息並將訊息放入程式訊息佇列中。程式通過執行一塊稱之為訊息迴圈的程式碼從訊息佇列中取出訊息。
在這裡我們瞭解一下下MSG結構:
Typedef struct tagMSG{ HWND hwnd; //接收訊息的視窗控制代碼 UNIT message; //訊息標識符WM_XXXX WPARAM wparam; //32位其含義和數值根據訊息的不同而不同 LPARAM lparam; DWORD time; //訊息放入訊息佇列中的時間 POINT pt; //訊息放入訊息佇列時的滑鼠座標 }MSG,*PMSG; BOOL GetMessage( LPMSG lpMsg, // address of structure with message HWND hWnd, // handle of window UINT wMsgFilterMin, // first message UINT wMsgFilterMax // last message);
這個調用付給windows一個指標,指向名這msg的MSG結構。lpMsg是一個訊息地址,hWnd如果為NULL表示程式接收它自己建立的所有視窗的所有訊息,Windows用從訊息佇列中取出的下一個訊息來填充訊息結構的各個欄位。只要從訊息佇列中取出訊息的message欄位不為WM_QUIT,GetMessage就傳回一個非零值。
BOOL TranslateMessage( CONST MSG *lpMsg // address of structure with message);//將msge結構傳給windows,進行一些鍵盤轉換 LONG DispatchMessage( CONST MSG *lpmsg // pointer to structure with message);
又將msg結構回傳給windows。然後,windows將該訊息發送給適當的視窗訊息處理常式,讓它進行處理。這裡就是windows將會調用訊息處理常式,也就是這個視窗的wndPro函數。處理完訊息之後,WndProc傳回到windows,此時window還停留在despatchMessage調用中,在結束dispatchMessage調用的處理之後,windows回到GetMessage調用開始訊息迴圈。
五、視窗訊息處理常式:
1、在上面我們介紹了一個視窗的產生過程和動作過程:註冊視窗類別別,建立視窗,然後在螢幕上顯示視窗,程式進入訊息迴圈,然後不斷從訊息佇列中取出訊息來處理。
2、實際的動作發生在視窗訊息處理常式中。視窗訊息處理常式確定了在視窗的顯示地區中顯示以及窗品怎樣響應使用者輸入。視窗訊息處理常式可任意命名,一個windows程式中可以包含多個視窗訊息處理常式。一個視窗訊息處理常式總與調用的registerClass註冊的特定視窗類別別相關聯。createWindow函數根據特定視窗類別別建立一個視窗。但依據一個視窗類別別,可以建立多個視窗。
視窗訊息處理常式總是定義為如下形式:
LRESULT CALLBACK WndProc(HWND hwnd,UNIT message,WPARAM wparam,LPARAM lparam)
這四個參數和Msg結構的前四個欄位是相同的。
3、程式通常不直接呼叫視窗訊息處理常式,訊息處理常式通常由windows本身調用。視窗訊息處理常式在處理訊息時,必須傳回視窗訊息處理常式不處理的所有訊息應傳給名為DefWindowProc的Window函數。Wndproc只先擇處理三種訊息:WM_CREATE,WM_PAINT,WM_DESTROY。 WM_CREATE:處理進行一次視窗初始化。
4、WM_PAINT:當視窗顯示地區的一部分或者全部內容必要更新顯示或無效時,將由這個訊息通知程式。
什麼叫無效,無效就是說如果視窗調用updateWindows時例如在視窗類別中我們寫的style=CS_HREDRAW|CS_VREDRAW這表示在視窗大小改變後就把整個視窗顯示內容變為無效。
5、調用updateWindows進行重繪。在對WM_PAINT的處理幾乎總是從一個BeginPaint(hwnd,&ps)調用開始,以EndPaint(hwnd,&ps)結束,ps包含一些視窗訊息處理常式可以用來更新顯示地區的內容。
6、在BeginPaint調用中,如果顯示地區的前景還沒被刪除則由windows來刪除,它使用視窗類別別的WNDCLASS的結構中的hbrBackground欄位中指定的畫刷來刪除前景。BeginPain返回一個只能在目前範圍繪圖的裝置內容控制代碼。EndPain釋放裝置內容控制代碼。GetClientRect(hwnd,&rect)rect是返回一個視窗顯示地區的尺寸。
7、WM_DESTROY:該訊息是使用者單擊Close按鈕或者程式的系統功能表上選擇Close時發生的。在這裡用的是PostQuitMessage(0)來生效的。該函數在程式的訊息佇列中插入一個WM_QUIT訊息使GetMessage得到一個WM_QUIT的訊息,它傳回0,終止程式然後執行return msg.wparam;
六、隊列化訊息與非隊列化訊息: Windows給視窗發送訊息,這意味著windows調用視窗訊息處理常式。但是,windows程式也有一個訊息迴圈,它調用GetMessage從訊息佇列中取出訊息,並且調用dispatchMessage將訊息發送給視窗訊息處理常式。
訊息被分為“隊列化的”和“非隊列化的”。
1、 隊列化的訊息:是由windows放入程式訊息佇列中的。在程式的訊息迴圈中,重新傳回並分配給視窗訊息處理常式。如鍵盤訊息,WM_PAINT windows訊息等。
2、 非隊列化的訊息在windows調用視窗時直接送給視窗訊息處理常式。非隊列化訊息多來自己於調用windows函數如CreateWindows,鍵盤或滑鼠輸入時發出的隊列化訊息訊號,也能在非隊列化訊息中出現。
3、 隊列化的訊息被發送給訊息佇列,而非隊列的訊息則發送給視窗訊息處理常式。
任何發問下,視窗訊息處理常式都將獲得視窗所有的訊息――包括隊列和非隊列化的訊息。視窗訊息處理常式是視窗的訊息中心。
4、在許多情況下,視窗訊息處理常式必須儲存它從訊息中取得的資訊,並在處理另一個訊息時使用這些資訊。這些資訊可以儲存在視窗的靜態變數或整體變數中。
WINDOWS訊息和視窗簡介