Windows窗口開發原理(窗口的創建&消息機制)


在windows應用程序中,窗口是其非常重要的一個元素。並且窗口是通過窗口句柄來標識的。句柄(HANDLE)是windows程序中一個重要的概念,其標識各種資源,包括圖標句柄(HICON)、光標句柄(HCURSOR)和畫刷句柄(HBRUSH)。

下面以一個帶有自定義的畫刷、光標和圖標的windows窗口為例,講解win32窗口的創建過程。

windows消息機制

windows程序是基於事件驅動方式的程序設計模式,主要是基於消息的。比如當用戶在窗口中畫圖的時候,按下鼠標左鍵,此時os會感知到這一事件,於是將此事件包裝成一個消息,投遞到應用程序的消息隊列中,然后應用程序從消息隊列中取出消息,經過transltor翻譯、分發消息,然后交由os調用

窗口過程函數(應用程序注冊的回調函數)或(DefWindowProc系統默認的回調處理函數)進行處理。

創建一個窗口的基本流程

設計窗口類

窗口的特征是由WNDCLASS結構體來定義的,其定義了這個窗口的基本屬性,所以我們只需填充結構體各成員信息即可。

typedef struct tagWNDCLASSW {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
} WNDCLASSW
typedef WNDCLASSW WNDCLASS;//WNDCLASS是WNDCLASSW的別名
View Code

這里要首先說明下各類型定義

//LRESULT :long
// UINT:unsigned int
//WPARAM:unsigned int
//LPARAM:unsigned int
//LPCWSTR:const w_chart_t *;
// typedef WORD                ATOM;   //BUGBUG - might want to remove this from minwin
// typedef unsigned short      WORD;
// typedef unsigned long       DWORD;
//LPCWSTR const w_char_t *   寬字符
//LPCSTR  const char *
    wchar_t szAppclassName[] = _T("FirstWin32");
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口類的樣式(要讓窗口在水平和垂直尺寸發生變化時發生重繪,我們可以使用用位或|操作符將其組合起來)
    //WNDPROC 函數指針類型
    wc.lpfnWndProc = WindowProc;//窗口回調函數/窗口處理函數
    wc.cbClsExtra = 0;//窗口類的附加內存大小
    wc.cbWndExtra = 0;//窗口附加內存大小
    wc.hInstance = hInstance;//當前應用程序實例句柄
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加載自定義的圖標句柄
    //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光標句柄;加載系統光標,也可以采用下面的方式加載自定義的光標
    wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
    wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//紅綠藍三原色0~255,[0最暗,255最亮]
    wc.lpszMenuName = NULL;//菜單名
    wc.lpszClassName = szAppclassName;//窗口類型名 spy++(vs->工具選項)

其中的第二個成員變量lpfnWndProc是一個函數指針,指向窗口過程函數,窗口過程函數時一個回調函數。該函數簽名如下:

 WNDPROC     lpfnWndProc;
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

故窗口函數按照如下簽名定義,實現如下:

//窗口處理函數
//第一個參數:當前窗口句柄
//第二個參數:消息類型
//第三個參數:附加消息、附加操作
//第四個參數:附加消息、附加操作
LRESULT CALLBACK  WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE://窗口關閉消息
        DestroyWindow(hWnd);//銷毀窗口,干掉界面,不會發出WM_QUIT,會發出一個WM_DESTORY消息
        break;
    case WM_QUIT://窗口銷毀消息
        PostQuitMessage(0);//發布WM_QUIT
        break;
    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

回調函數機制:

⑴定義一個回調函數(WindowProc);
⑵提供函數實現的一方在初始化的時候,將回調函數的函數 指針注冊給調用者(注冊wc.lpfnWndProc = WindowProc);
⑶當特定的事件或條件發生的時候,調用者使用 函數指針調用回調函數對事件進行處理(eg雙擊鼠標按鈕時間發生后,os調用窗口過程函數進行處理)。

注冊窗口類

 通過ATOM WINAPI RegisterClass( _In_ CONST WNDCLASS*lpWndClass);進行窗口類注冊。形參為WNDCLASS地址。返回值為ATOM.

創建窗口

 創建窗口通過CreateWindow函數來實現,該函數API信息如下:

void CreateWindow(
   lpClassName,
   lpWindowName,
   dwStyle,
   x,
   y,
   nWidth,
   nHeight,
   hWndParent,
   hMenu,
   hInstance,
   lpParam
);
View Code
關於返回值:
類型:HWND 如果函數成功,則返回值是新窗口的句柄。 如果函數失敗,則返回值為NULL。要獲取擴展錯誤信息,請調用GetLastError。
//WS:window style
    HWND hWnd = CreateWindow(szAppclassName,                        //窗口類型名
        _T("這個是我的第一個windows應用程序")               //窗口左上角標題
        , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的風格
        200, 300,                                       //窗口左上角坐標
        800, 600,                                        // //窗口寬和高
        NULL,                                            //父窗口句柄
        NULL,                                            //菜單句柄
        hInstance,                                        //應用程序實例句柄
        NULL);                                        //創建窗口的附加參數,WM_CREATE消息,lparam來接受這個參數
    if (NULL == hWnd)
    {
        MessageBox(NULL, _T("創建窗口失敗"), _T("提示"), MB_OK);
    }
View Code

顯示窗口&更新窗口

 創建完窗口后,要對窗口進行具體的顯示。

顯示函數API如下:

BOOL ShowWindow(
  HWND hWnd,//創建窗口后返回的窗口句柄
  int  nCmdShow //指定窗口顯示的狀態,包括SW_MAXIMIZE  SW_MINIMIZE SW_NORMAL SW_HIDE  最大化、最小化、正常、隱藏顯示
);

在調用ShowWindow函數之后,需要調用UpdateWindow來刷新窗口。

UpdateWindow(hWnd);//hWnd是創建成功后的窗口句柄

注意:UpdateWindow函數通過發送一個WM_PAINT消息來刷新窗口,UpdateWindow將WM_PAINT消息直接發送給了窗口過程函數進行處理,而沒有放到消息隊列中。

消息循環

在創建窗口、顯示窗口、更新窗口后,我們需要編寫一個消息循環,調用GetMessage函數不斷從消息隊列中取出消息,並進行相應。

消息結構體:

//        typedef struct tagMSG {
    //HWND        hwnd;      //消息發向窗口的窗口句柄(指的是這個消息發個哪個窗口了,這里指定這個窗口的句柄)
    //UINT        message;   //消息編號
    //WPARAM      wParam;  //附加消息
    //LPARAM      lParam;//附加消息
    //DWORD       time;//消息放入消息隊列的時間
    //POINT       pt;//消息放入消息隊列時鼠標坐標
View Code
BOOL GetMessage(
  LPMSG lpMsg,//消息結構體地址
  HWND  hWnd,//窗口句柄
  UINT  wMsgFilterMin,//要獲取的消息的最小值,通常設置為0
  UINT  wMsgFilterMax//要獲取的消息的最大值。如果FiterMin,Max兩者都為0,則接收所有消息
);

代碼如下:

MSG msg;
    //GetMessage:何時返回FALSE
    //當獲取到WM_QUIT消息時,返回FALSE,沒有獲取到這個消息時,返回非0,不會退出循環
    while (GetMessage(&msg, NULL, 0, 0))
    {
        //將虛擬鍵消息轉換為字符消息
        TranslateMessage(&msg);
        //將消息分發給窗口處理函數
        DispatchMessage(&msg);
    }

此處的Windows應用消息的消息處理機制如下圖:

 

 

 

(1)os接收到應用消息的窗口消息【比如當用戶在窗口中畫圖的時候,按下鼠標左鍵,此時os會感知到這一事件,於是將此事件包裝成一個消息】,將消息投遞到該應用消息的消息隊列中。

(2)應用程序在消息循環中調用GetMessage函數從消息隊列中取出一條一條的消息。取出消息后,應用程序可以對消息進行一些預處理,比如放棄對某些消息的相應或調用TranslateMessage產生新的消息。

(3)應用程序調用DispatchMessage將消息回傳給os。消息MSG結構體中包含接受消息的窗口的句柄。因此DispatchMessage函數總能進行正確傳遞。

(4)os利用WNDCLASS結構體的lpfnWndProc成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理(即“系統給應用程序發送了消息”)。

 附錄完整代碼

#include<Windows.h>
#include<tchar.h>
#include"resource.h"
//LRESULT :long
// UINT:unsigned int
//WPARAM:unsigned int
//LPARAM:unsigned int
//LPCWSTR:const w_chart_t *;
// typedef WORD                ATOM;   //BUGBUG - might want to remove this from minwin
// typedef unsigned short      WORD;
// typedef unsigned long       DWORD;
//LPCWSTR const w_char_t *   寬字符
//LPCSTR  const char *
//窗口處理函數
//第一個參數:當前窗口句柄
//第二個參數:消息類型
//第三個參數:附加消息、附加操作
//第四個參數:附加消息、附加操作
LRESULT CALLBACK  WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE://窗口關閉消息
        DestroyWindow(hWnd);//銷毀窗口,干掉界面,不會發出WM_QUIT,會發出一個WM_DESTORY消息
        break;
    case WM_QUIT://窗口銷毀消息
        PostQuitMessage(0);//發布WM_QUIT
        break;
    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
    //創建一個窗口的流程
    //1設計窗口類
  /*  typedef struct tagWNDCLASSW {
        UINT        style;
        WNDPROC     lpfnWndProc;
        int         cbClsExtra;
        int         cbWndExtra;
        HINSTANCE   hInstance;
        HICON       hIcon;
        HCURSOR     hCursor;
        HBRUSH      hbrBackground;
        LPCWSTR     lpszMenuName;
        LPCWSTR     lpszClassName;
    } WNDCLASSW,*/
    //typedef WNDCLASSW WNDCLASS;

    //LRESULT CALLBACK WindowProc(
    //    _In_ HWND   hwnd,
    //    _In_ UINT   uMsg,
    //    _In_ WPARAM wParam,
    //    _In_ LPARAM lParam
    //);
    //typedef LRESULT(CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
    wchar_t szAppclassName[] = _T("FirstWin32");
    WNDCLASS wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;//窗口類的風格
    //WNDPROC 函數指針類型
    wc.lpfnWndProc = WindowProc;//窗口回調函數/窗口處理函數
    wc.cbClsExtra = 0;//窗口類的附加內存大小
    wc.cbWndExtra = 0;//窗口附加內存大小
    wc.hInstance = hInstance;//當前應用程序實例句柄
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));//加載自定義的圖標句柄
    //wc.hCursor = LoadCursor(NULL,IDC_CROSS);//光標句柄;加載系統光標,也可以采用下面的方式加載自定義的光標
    wc.hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
    wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 0));//紅綠藍三原色0~255,[0最暗,255最亮]
    wc.lpszMenuName = NULL;//菜單名
    wc.lpszClassName = szAppclassName;//窗口類型名 spy++(vs->工具選項)


    //2注冊窗口類
    //返回值ATOM
    if (0 == RegisterClass(&wc))
    {
        MessageBox(NULL, _T("此程序不能復制在winNT平台"), _T("提示"), MB_OK);
        return 0;
    }
    //3創建窗口
    //WS:window style
    HWND hWnd = CreateWindow(szAppclassName,                        //窗口類型名
        _T("這個是我的第一個windows應用程序")               //窗口左上角標題
        , WS_BORDER | WS_CAPTION | WS_MAXIMIZEBOX | WS_SYSMENU, //窗口的風格
        200, 300,                                       //窗口左上角坐標
        800, 600,                                        // //窗口寬和高
        NULL,                                            //父窗口句柄
        NULL,                                            //菜單句柄
        hInstance,                                        //應用程序實例句柄
        NULL);                                        //創建窗口的附加參數,WM_CREATE消息,lparam來接受這個參數
    if (NULL == hWnd)
    {
        MessageBox(NULL, _T("創建窗口失敗"), _T("提示"), MB_OK);
    }
    //4顯示窗口
    //SW_SHOW:原來在何處顯示,就在此處顯示
    //SW_MAXIMIZE  SW_MINIMIZE     SW_NORMAL SW_HIDE  最大化、最小化、正常、隱藏顯示




    ShowWindow(hWnd, SW_SHOW);
    //5更新窗口
    UpdateWindow(hWnd);
    //6消息循環
    //        typedef struct tagMSG {
    //HWND        hwnd;      //消息發向窗口的窗口句柄(指的是這個消息發個哪個窗口了,這里指定這個窗口的句柄)
    //UINT        message;   //消息編號
    //WPARAM      wParam;  //附加消息
    //LPARAM      lParam;//附加消息
    //DWORD       time;//消息放入消息隊列的時間
    //POINT       pt;//消息放入消息隊列時鼠標坐標
    //windows程序都是通過消息機制驅動運行的。
    MSG msg;
    //GetMessage:何時返回FALSE
    //當獲取到WM_QUIT消息時,返回FALSE,沒有獲取到這個消息時,返回非0,不會退出循環
    while (GetMessage(&msg, NULL, 0, 0))
    {
        //將虛擬鍵消息轉換為字符消息
        TranslateMessage(&msg);
        //將消息分發給窗口處理函數
        DispatchMessage(&msg);
    }


    MessageBox(NULL, _T("這是第一個win32應用程序"), _T("提示"), MB_OK);
    return 0;
}
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM