Windows編程-創建窗口

窗口創建的基本步驟是:

  1. 設計窗口類
  2. 註冊窗口類
  3. 創建窗口
  4. 顯示更新窗口
  5. 消息循環
  6. 編寫回調函數

    ——————————帥氣的分割線—————————–
    下面我們一步一步進行講解:

1.設計窗口類WNDCLASS

我們查看MSDN可以知道窗口類的成員變量有哪些:

typedef struct _WNDCLASS { 
    UINT       style; 
    WNDPROC    lpfnWndProc; 
    int        cbClsExtra; 
    int        cbWndExtra; 
    HINSTANCE  hInstance; 
    HICON      hIcon; 
    HCURSOR    hCursor; 
    HBRUSH     hbrBackground; 
    LPCTSTR    lpszMenuName; 
    LPCTSTR    lpszClassName; 
} WNDCLASS, *PWNDCLASS; 

微軟爲我們設計好了窗口類,我們只需要按照我們的需求填寫其中的值就可以了。

  • 我們先看窗口類的第一個字段UINT style;

這個字段是指明窗口的類型風格,其值是可選項,以”CS_“開頭,查看MSDN可以知道它所有的可選項,在這裏我說兩個選項:”CS_HREDRAW“、”CS_VREDRAW“,分別是水平重繪和豎直重繪,表示窗口在水平和豎直方向大小發生變化時會發出WM_PAINT消息來進行窗口的重新繪製。

  • 接下來看第二個字段:WNDPROC lpfnWndProc;

這是窗口的回調函數,這裏賦值一個函數的名字(函數指針),這個回調函數的參數有一定的要求,查看MSDN可以知道回調函數的聲明如下:

LRESULT CALLBACK WindowProc(  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter);

這裏對回調函數進行一個簡單的說明,操作系統(OS)可以捕獲程序的消息,並將消息放到該進程的消息隊列中,但是OS並不會對這個消息進行處理,如果想要對消息處理就需要程序員自己來寫消息響應函數。當用戶按下鍵盤的時候,OS會捕獲兩個消息WM_KETYDOWN、WM_KEYUP消息,通過TranslateMessage函數將鍵盤按下和彈起的消息轉換成一個WM_CHAR消息,並將按下的按鍵的ASCII碼存放到消息結構體MSG的附加參數中,然後使用DespatchMessage函數將這個消息傳遞到回調函數中,並由回調函數進行處理。

  • int cbClsExtra;
  • int cbWndExtra;

兩個附加參數,給0就行

  • 接下來是字段:HINSTANCE hInstance;

它表示一個應用程序的實例句柄,至於應用程序的實例,可以理解爲是一個進程,一個執行的程序。這個字段的值是直接從WinMain函數給過來的。

  • HICON hIcon;
  • HCURSOR hCursor;
  • HBRUSH hbrBackground;

這三個字段分別是窗口的圖標、光標、背景。如果要設置窗口的圖標要使用函數:

HICON LoadIcon(  
  HINSTANCE hInstance, // handle to application instance
  LPCTSTR lpIconName   // name string or resource identifier
  );

其中,如果想使用系統自帶的圖標,第一個參數必須寫NULL,第二個參數可以是”IDI_“開頭的任意可選項。
而光標一樣,需要使用函數:

HCURSOR LoadCursor(  
  HINSTANCE hInstance,  // handle to application instance
  LPCTSTR lpCursorName  // name or resource identifier
  );

但是對於背景來說,需要使用下面這個函數來設置:

HGDIOBJ GetStockObject(  
   int fnObject   // stock object type
);

其中fnObject爲可選項,詳情可查看MSDN

  • LPCTSTR lpszMenuName;
  • LPCTSTR lpszClassName;

下面是最後的兩個字段,其中第一個是菜單的名字,第二個是窗口類的名字。由於沒有菜單,菜單名設置爲空,窗口類名要在CreateWindow時使用。
以上就設計好了窗口類了。

——————–我是華麗的分割線————————–

2. 註冊窗口類

使用一下函數就可以,參數是窗口類對象的指針。
ATOM RegisterClass( CONST WNDCLASS *lpWndClass // class data);

3. 創建窗口

使用CreateWindow函數來創建窗口:

  HWND CreateWindow(  
  LPCTSTR lpClassName,  // registered class name
  LPCTSTR lpWindowName, // window name  
  DWORD dwStyle,        // window style
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width
  int nHeight,          // window height
  HWND hWndParent,      // handle to parent or owner window
  HMENU hMenu,          // menu handle or child identifier
  HINSTANCE hInstance,  // handle to application instance
  LPVOID lpParam        // window-creation data
  );

其中第一個參數”LPCTSTR lpClassName“就是 剛纔在第一步設置窗口類時所起的類名字,第二個參數是該窗口的標題,後面的四個參數是窗口左上角的座標和寬高,接下來兩個參數”hWndParent“、”hMenu“是窗口的父窗口句柄和菜單句柄,由於沒有就填NULL,最後一個參數”lpParam“是附加參數填NULL。

4. 顯示更新窗口

ShowWindow(hWnd, SW_SHOWNORMAL); //正常顯示窗口
UpdateWindow(hWnd); //更新窗口

5. 消息循環

OS每次從該進程的消息隊列中取出第一條消息,使用函數如下:

BOOL GetMessage(  
  LPMSG lpMsg,         // message information
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,  // first message  
  UINT wMsgFilterMax   // last message
  );

在取消息之前,需要聲明一個MSG結構的消息變量,它不需要初始化,直接傳入第一個參數,該函數會將取出的消息填入該變量。第二個參數是窗口句柄,表明你要接收那個窗口的消息,如果想接收該進程的所有消息,該參數填寫NULL。第三、四個參數是消息過濾的最小最大值,表明你要接受消息的範圍,如果接收全部消息,這兩個值填0就行。

這個GetMessage有一個BOOL型的返回值,如果取到的消息是WM_DESTROY消息則返回假,所以我們將這個函數放到while循環中,只要有消息就執行循環體,直到用戶關閉窗口時發出WM_DESTROY消息後while循環條件爲假退出。

在循環體中,我們要處理消息,使用以下兩個函數:

TranslateMessage(&msg); //翻譯消息
DispatchMessageA(&msg); //將消息傳入窗口的回調函數

這兩個函數上面說到過,這裏不進行贅述。

6. 編寫回調函數

由於在第五步的時候使用DispatchMessage函數將一個消息傳入回調函數,則在回調函數中對參數msg進行判斷和進一步處理。

再次特別說明兩個消息:”WM_CLOSE“、“WM_DESTROY”
當用戶點擊關閉窗口按鈕的時候會發出一個WM_CLOSE消息,我們進行窗口的銷燬,但是一定要注意此時只是銷燬了窗口,程序仍然在後臺執行。銷燬窗口的函數DestroyWindow又會發出一個WM_DESTROY的消息,在處理這個消息的時候使用PostQuitMessage這才真正的退出程序。

對於我們沒有處理的其他消息,我們不能對其不管,所以我們對其他不感興趣的消息調用系統默認的回調函數DefWindowProc

———————–這是另一條分割線————————
下面是程序的結果:
這裏寫圖片描述
上圖爲程序運行後的界面

這裏寫圖片描述
點擊鼠標左鍵後窗口顯示文字

這裏寫圖片描述
按下鍵盤按鍵後窗口彈出對話框

這裏寫圖片描述
點擊退出按鈕窗口彈出對話框,點擊是程序結束

———————–帥氣的分割線—————————–
下面就是程序的源代碼:

#include <Windows.h>
#include <stdio.h>

/*
* 窗口的回調函數
*/
LRESULT CALLBACK WindowProc(HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam)   // second message parameter
{
    HDC hDC;
    PAINTSTRUCT ps;
    switch (uMsg)
    {
    case WM_PAINT:
        /*
         * 窗口重繪時調用
         * 只有在WM_PAINT消息中纔可以使用BeginPaint、EndPaint
         * 其他消息使用GetDC、ReleaseDC
         */
        hDC = BeginPaint(hwnd, &ps);
        TextOut(hDC, 0, 50, "這是在WM_PAINT消息中重繪的文字", strlen("這是在WM_PAINT消息中重繪的文字"));
        EndPaint(hwnd, &ps);
        break;
    case WM_CHAR:
        MessageBox(hwnd, "WM_CHAR消息觸發了", "提示", MB_OK);
        break;  
    case WM_LBUTTONDOWN:
        hDC = GetDC(hwnd);
        TextOut(hDC, 0, 70, "這是在WM_LBUTTONDOWN消息中重繪的文字", strlen("這是在WM_LBUTTONDOWN消息中重繪的文字"));
        ReleaseDC(hwnd, hDC);
        break;
    case WM_CLOSE:
        if (IDYES == MessageBox(hwnd, "確定要退出嗎?", "提示", MB_YESNO))
        {
            //確定退出,銷燬窗口,拋出一個WM_DESTYRY的消息
            DestroyWindow(hwnd); 
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance,      // handle to current instance
    HINSTANCE hPrevInstance,  // handle to previous instance
    LPSTR lpCmdLine,          // command line
    int nCmdShow)              // show state
{
    //第一步、設計窗口類
    WNDCLASS wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW; //設置水平豎直重繪,發送WM_PAINT消息
    wndclass.lpfnWndProc = WindowProc; //指定窗口的回調函數
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0; //兩個額外數據
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_CROSS);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = "myWindowClass";

    //第二步、註冊窗口類
    RegisterClass(&wndclass);

    //第三步、創建窗口
    HWND hWnd = CreateWindow(
        "myWindowClass",    //窗口類的名字
        "my first window",  //窗口標題
        WS_OVERLAPPEDWINDOW,    //樣式
        0, 0, 500, 500, //左上角座標,寬高
        NULL, //父窗口
        NULL, //菜單
        hInstance, //實例
        NULL); //附加參數

    //第四部、顯示更新窗口
    ShowWindow(hWnd, SW_SHOWNORMAL); //正常顯示窗口
    UpdateWindow(hWnd); //更新窗口

    //第五步、消息循環
    MSG msg;
    while (GetMessageA(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg); //翻譯消息
        DispatchMessageA(&msg); //將消息傳入窗口的回調函數
    }

    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章