MFC——基礎框架內容


MFC入門

微軟基礎類庫(英語:Microsoft Foundation Classes,簡稱MFC)是微軟公司提供的一個類庫(class libraries),以C++類的形式封裝了Windows API,並且包含一個應用程序框架,以減少應用程序開發人員的工作量。其中包含大量Windows句柄封裝類和很多Windows的內建控件和組件的封裝類。
MFC(微軟基礎類庫)是對於Windows平臺下做GUI開發的一個很好的選擇。

Windows消息機制

要想熟練掌握 Windows 應用程序的開發, 首先需要理解 Windows 平臺下程序運行的內部機制。如果想要更好的學習掌握 MFC,必須要先了解Windows 程序的內部運行機制。

基本概念

在Windows平臺下,也有類似標準庫下的函數可供調用:不同的是,這些函數是由Windows操作系統本身提供的(windows API)。

SDK 和 API

  • SDK:軟件開發工具包(Software Development Kit),一般都是一些被軟件工程師用於爲特定的軟件包、軟件框架、硬件平臺、操作系統等建立應用軟件的開發工具的集合。
  • API函數: Windows操作系統提供給應用程序編程的接口(Application Programming Interface)。Windows應用程序API函數是通過C語言實現的,所有主要的 Windows 函數都在 Windows.h 頭文件中進行了聲明。Windows 操作系統提供了 1000 多種 API函數。

窗口和句柄

  • 窗口:窗口是 Windows 應用程序中一個非常重要的元素,一個 Windows 應用程序至少要有一個窗口,稱爲主窗口。
    窗口是屏幕上的一塊矩形區域,是 Windows 應用程序與用戶進行交互的接口。利用窗口可以接收用戶的輸入、以及顯示輸出。
    一個應用程序窗口通常都包含標題欄、菜單欄、系統菜單、最小化框、最大化框、 可調邊框,有的還有滾動條。
    窗口可以分爲客戶區和非客戶區, 如下圖。 客戶區是窗口的一部分, 應用程序通常在客戶區中顯示文字或者繪製圖形。
    標題欄、 菜單欄、 系統菜單、 最小化框和最大化框、 可調邊框統稱爲窗口的非客戶區, 它們由 Windows 系統來管理, 而應用程序則主要管理客戶區的外觀及操作。
    窗口可以有一個父窗口, 有父窗口的窗口稱爲子窗口。除了上圖所示類型的窗口外, 對話框和消息框也是一種窗口。 在對話框上通常還包含許多子窗口, 這些子窗口的形式有按鈕、 單選按鈕、 複選框、 組框、 文本編輯框等。
    在 Windows 應用程序中, 窗口是通過窗口句柄( HWND) 來標識的。 我們要對某個窗口進行操作, 首先就要得到這個窗口的句柄。
    在這裏插入圖片描述
  • 句柄(HANDLE):是 Windows 程序中一個重要的概念, 使用也非常頻繁。 在 Windows 程序中, 有各種各樣的資源( 窗口、 圖標、光標,畫刷等), 系統在創建這些資源時會爲它們分配內存, 並返回標識這些資源的標識號, 即句柄。 在後面的內容中我們還會看到圖標句柄( HICON)、 光標句柄( HCURSOR) 和畫刷句柄( HBRUSH)。

消息與消息隊列
Windows 程序設計是一種完全不同於傳統的 DOS 方式的程序設計方法。它是一種事件驅動方式的程序設計模式,主要是基於消息的。
每一個 Windows 應用程序開始執行後, 系統都會爲該程序創建一個消息隊列, 這個消息隊列用來存放該程序創建的窗口的消息。
例如,當用戶在窗口中畫圖的時候,按下鼠標左鍵,此時,操作系統會感知到這一事件,於是將這個事件包裝成一個消息,投遞到應用程序的消息隊列中,等待應用程序的處理。然後應用程序通過一個消息循環不斷地從消息隊列中取出消息,並進行響應。
在這個處理過程中,操作系統也會給應用程序“ 發送消息”。所謂“ 發送消息”,實際上是操作系統調用程序中一個專門負責處理消息的函數,這個函數稱爲窗口過程。
在這裏插入圖片描述
WinMain函數
當Windows操作系統啓動一個程序時,它調用的就是該程序的WinMain函數( 實際是由插入到可執行文件中的啓動代碼調用的)。 WinMain是Windows程序的入口點函數,與DOS程序的入口點函數main的作用相同,當WinMain 函數結束或返回時,Windows應用程序結束。

Windows編程模型

一個完整的Win32程序(#include <windows.h>),該程序實現的功能是創建一個窗口,並在該窗口中響應鍵盤及鼠標消息,程序的實現步驟爲:

  1. WinMain函數的定義
  2. 創建一個窗口
  3. 進行消息循環
  4. 編寫窗口過程函數

創建一個win32程序,實現創建一個窗口並響應部分事件消息。

創建項目
在visual studio下創建一個空白的win32應用程序項目。

  • VS2010版本如下:
    在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
  • VS2019版本如下:
    在這裏插入圖片描述在這裏插入圖片描述
    WinMain函數的定義
int WINAPI WinMain(
	HINSTANCE hInstance,	//應用程序實例
	HINSTANCE hPrevInstance,	//上一個應用程序實例
	LPSTR lpCmdLine,		//命令行參數
	int nShowCmd);		//窗口顯示的樣式
  • WINAPI:是一個宏,它代表的是__stdcall(注意是兩個下劃線),表示的是參數傳遞的順序:從右往左入棧,同時在函數返回前自動清空堆棧。
  • hInstance:表示該程序當前運行的實例的句柄,這是一個數值。當程序在Windows下運行時,它唯一標識運行中的實例(注意,只有運行中的程序實例, 纔有實例句柄)。一個應用程序可以運行多個實例,每運行一個實例,系統都會給該實例分配一個句柄值,並通過hInstance參數傳遞給 WinMain 函數。
  • hPrevInstance:表示當前實例的前一個實例的句柄。在Win32環境下,這個參數總是NULL,即在Win32環境下,這個參數不再起作用。
  • lpCmdLine:是一個以空終止的字符串, 指定傳遞給應用程序的命令行參數,相當於C或C++中的main函數中的參數char *argv[]。
  • nShowCmd:表示一個窗口的顯示,表示它是要最大化顯示、最小化顯示、正常大小顯示還是隱藏顯示。

創建一個窗口
創建一個完整的窗口,需要經過下面幾個步驟:

  • 設計一個窗口類
    聲明一個窗口對象WNDCLASS類實例化,並初始化其中的參數。
typedef struct _WNDCLASS{
	UINT        style;//窗口樣式
	WNDPROC     lpfnWndProc;//窗口回調函數
	int         cbClsExtra;
	int         cbWndExtra;
	HINSTANCE   hInstance;
	HICON       hIcon;
	HCURSOR     hCursor;
	HBRUSH      hbrBackground;
	LPCWSTR     lpszMenuName;
	LPCWSTR     lpszClassName;
} WNDCLASS;
  • 註冊窗口類
    設計完窗口類(WNDCLASS)後, 需要調用RegisterClass函數對其進行註冊,註冊成功後,纔可以創建該類型的窗口。
    註冊函數的原型聲明如下:
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
//使用示例:RegisterClass(&wc);
  • 創建窗口
    設計好窗口類並且將其成功註冊之後, 即可用CreateWindow函數產生這種類型的窗口了。
//返回值說明:如果窗口創建成功,CreateWindow函數將返回系統爲該窗口分配的句柄,否則,返回NULL。
HWND CreateWindow(
		LPCTSTR lpClassName,//指定窗口類的名稱,此名字必須和WNDCLASS的lpszClassName成員指定的名稱一樣。
		LPCTSTR lpWindowName,//指定窗口的名字,即窗口的標題。
		DWORD dwStyle,//指定創建的窗口的樣式,常指定爲指WS_OVERLAPPEDWINDOW類型,這是一種多種窗口類型的組合類型。
		int x,int y,//指定窗口左上角的x,y座標。如果參數x被設爲CW_USEDEFAULT,那麼系統爲窗口選擇默認的左上角座標並忽略y參數。
		int nWidth,int nHeight,//指定窗口窗口的寬度,高度。如果參數nWidth被設爲 CW_USEDEFAULT,那麼系統爲窗口選擇默認的寬度和高度,參數nHeight被忽略。
		HWND hWndParent,//指定被創建窗口的父窗口句柄,沒有父窗口,則設置NULL。
		HMENU hMenu,//指定窗口菜單的句柄,沒有,則設置爲NULL。
		HINSTANCE hInstance,//窗口所屬的應用程序實例的句柄,用WinMain中的形參hInstance爲其賦值。
		LPVOID lpParam);//作爲WM_CREATE消息的附加參數lParam傳入的數據指針。通常設置爲NULL。
  • 顯示和更新窗口
    顯示窗口函數原型:
BOOL ShowWindow(HWND hWnd, int nCmdShow);

更新窗口函數原型:

BOOL UpdateWindow(HWND hWnd);

//示例
ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL爲普通模式
UpdateWindow(hWnd);

窗口類具體說明

typedef struct _WNDCLASS{
	UINT        style;//窗口樣式
	WNDPROC     lpfnWndProc;//窗口回調函數
	int         cbClsExtra;
	int         cbWndExtra;
	HINSTANCE   hInstance;
	HICON       hIcon;
	HCURSOR     hCursor;
	HBRUSH      hbrBackground;
	LPCWSTR     lpszMenuName;
	LPCWSTR     lpszClassName;
} WNDCLASS;
  • style:指定窗口的樣式(風格),常用的樣式如下
類型 含義
CS_HREDRAW 當窗口水平方向上的寬度發生變化時, 將重新繪製整個窗口。 當窗口發生重繪時, 窗口中的文字和圖形將被擦除。如果沒有指定這一樣式,那麼在水平方向上調整窗口寬度時,將不會重繪窗口。
CS_VREDRAW 當窗口垂直方向上的高度發生變化時,將重新繪製整個窗口。如果沒有指定這一樣式,那麼在垂直方向上調整窗口高度時,將不會重繪窗口。
CS_NOCLOSE 禁用系統菜單的 Close 命令,這將導致窗口沒有關閉按鈕。
CS_DBLCLKS 當用戶在窗口中雙擊鼠標時,向窗口過程發送鼠標雙擊消息。
  • lpfnWndProc:指定一個窗口回調函數,是一個函數的指針。
    當應用程序收到給某一窗口的消息時,就應該調用某一函數來處理這條消息。這一調用過程不用應用程序自己來實施,而由操作系統來完成,但是,回調函數本身的代碼必須由應用程序自己完成。對於一條消息,操作系統調用的是接受消息的窗口所屬的類型中的lpfnWndProc成員指定的函數。每一種不同類型的窗口都有自己專用的回調函數,該函數就是通過lpfnWndProc成員指定的。
LRESULT CALLBACK WindowProc(
	HWND hWnd,		//信息所屬的窗口句柄
	UINT uMsg,		//消息類型
	WPARAM wParam,	//附加信息(如鍵盤哪個鍵按下)
	LPARAM lParam	//附加信息(如鼠標點擊座標)
	);

  • cbClsExtra:類的附加內存,通常數情況下爲0。
  • cbWndExtra:窗口附加內存,通常情況下爲0。
  • hInstance:當前實例句柄,用WinMain中的形參hInstance爲其賦值。
  • hIcon:指定窗口類的圖標句柄,設置爲NULL,則使用默認圖標,也可用如下函數進行賦值:
HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
如:LoadIcon(NULL, IDI_WARNING); //第一個參數爲NULL,加載系統默認圖標
  • hCursor:指定窗口類的光標句柄,設置爲NULL,則使用默認圖標,也可用如下函數進行賦值:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
如:LoadCursor(NULL, IDC_HELP); //第一個參數爲NULL,加載系統默認光標
  • hbrBackground:指示窗口的背景顏色,可用如下函數進行賦值:
HGDIOBJ GetStockObject(int fnObject);
如:GetStockObject(WHITE_BRUSH);
  • lpszMenuName:指定菜單資源的名字。如果設置爲NULL,那麼基於這個窗口類創建的窗口將沒有默認菜單。
  • lpszClassName:指定窗口類的名字。

示例代碼

	WNDCLASS wc;	//窗口類變量
	wc.cbClsExtra = 0;	//類附加內存
	wc.cbWndExtra = 0;	//窗口附加內存
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色爲白色
	wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_HELP);	//幫助光標
	wc.hIcon = (HICON)LoadIcon(NULL, IDI_WARNING);	//警告圖標
	wc.hInstance = hInstance;	//應用程序實例,爲WinMain第1個形參
	wc.lpfnWndProc = WinProc;	//窗口過程函數名字
	wc.lpszClassName = TEXT("MyWin");	//類的名字
	wc.lpszMenuName = NULL;	//沒有菜單
	wc.style = 0;	//類的風格,填0,使用默認風格

	//註冊窗口類
	RegisterClass(&wc);

	//創建窗口
	HWND  hWnd = CreateWindow(TEXT("MyWin"), TEXT("測試"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

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

消息循環

在創建窗口、顯示窗口、更新窗口後,我們需要編寫一個消息循環,不斷地從消息隊列中取出消息,並進行響應。

消息結構體
在Windows程序中,消息是由MSG結構體來表示的。MSG結構體的定義如下:

typedef struct tagMSG {
	HWND hWnd;   
	UINT message;   
	WPARAM wParam;
	LPARAM lParam;   
	DWORD time;   
	POINT pt;
} MSG;
  • hWnd:消息所屬的窗口。我們通常開發的程序都是窗口應用程序,一個消息一般都是與某個窗口相關聯的。例如,在某個活動窗口中按下鼠標左鍵,產生的按鍵消息就是發給該窗口的。
  • message:消息的標識符,是由一個數值來表示的,不同的消息對應不同的數值。Windows將消息對應的數值定義爲WM_XXX宏(WM是Windows Message的縮寫)的形式, XXX對應某種消息的英文拼寫的大寫形式。例如,鼠標左鍵按下消息是WM_LBUTTONDOWN,鍵盤按下消息是WM_KEYDOWN,字符消息是 WM_CHAR……。
  • wParam: 指定消息的附加信息,如鍵盤按下會觸發WM_KEYDOWN消息,但是,具體按下哪個按鍵需要wParam區分。
  • lParam:指定消息的附加信息,如鼠標左擊會觸發WM_LBUTTONDOWN消息,但是,具體點擊的座標需要lParam區分。
  • time:標識一個消息產生時的時間。
  • pt:表示產生這個消息時光標或鼠標的座標。

取消息
要從消息隊列中取出消息,我們需要調用GetMessage()函數,該函數的原型聲明如下:

BOOL GetMessage(
	LPMSG lpMsg,
	HWND hWnd,
	UINT wMsgFilterMin,
	UINT wMsgFilterMax);

參數說明:

  • lpMsg:指向一個消息結構體(MSG),GetMessage從線程的消息隊列中取出的消息信息將保存在該結構體變量中。
  • hWnd:指定接收屬於哪一個窗口的消息。通常我們將其設置爲NULL,用於接收屬於調用線程的所有窗口的窗口消息。
  • wMsgFilterMin:指定消息的最小值。
  • wMsgFilterMax:指定消息的最大值。如果wMsgFilterMin和wMsgFilterMax都設置爲0, 則接收所有消息。
  • 返回值說明:GetMessage函數接收到除 WM_QUIT 外的消息均返回非零值。對於WM_QUIT消息,該函數返回零。如果出現了錯誤,該函數返回-1,例如,當參數hWnd是無效的窗口句柄或lpMsg是無效的指針時。

建立消息循環

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
  • TranslateMessage:用於翻譯、處理和轉換消息並把新消息投放到消息隊列中,並且此過程不會影響原來的消息隊列。
  • DispatechMessage:用於把收到的消息傳到窗口回調函數進行分析和處理。即將消息傳遞給操作系統,讓操作系統調用窗口回調函數,來對信息進行處理。

消息處理機制
在這裏插入圖片描述

  • 操作系統接收到應用程序的窗口消息,將消息投遞到該應用程序的消息隊列中。
  • 應用程序在消息循環中調用GetMessage函數從消息隊列中取出一條一條的消息。取出消息後,應用程序可以對消息進行一些預處理,例如,放棄對某些消息的響應,或者調用TranslateMessage產生新的消息。
  • 應用程序調用DispatchMessage,將消息回傳給操作系統。消息是由 MSG結構體對象來表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage函數總能進行正確的傳遞。
  • 系統利用WNDCLASS結構體的lpfnWndProc成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理。

窗口過程函數(消息處理函數)
在完成上述步驟後,剩下的工作就是編寫一個窗口過程函數,用於處理髮送給窗口的消息。
窗口過程函數的名字可以隨便取, 如WinProc, 但函數定義的形式必須和下面聲明的形式相同:

LRESULT CALLBACK WinProc( //CALLBACK 和WINAPI 作用一樣
	HWND hWnd,		//信息所屬的窗口句柄
	UINT uMsg,		//消息類型
	WPARAM wParam,	//附加信息(如鍵盤哪個鍵按下)
	LPARAM lParam	//附加信息(如鼠標點擊座標)
	);
  • DefWindowProc函數:DefWindowProc函數調用默認的窗口過程,對應用程序沒有處理的其他消息提供默認處理。
  • WM_CLOSE:對WM_CLOSE消息的響應並不是必須的,如果應用程序沒有對該消息進行響應,系統將把這條消息傳給DefWindowProc函數而 DefWindowProc函數則調用DestroyWindow函數來響應這條WM_CLOSE消息。
  • WM_DESTROY:DestroyWindow函數在銷燬窗口後,會給窗口過程發送 WM_DESTROY消息,我們在該消息的響應代碼中調用PostQuitMessage函數。
    PostQuitMessage函數嚮應用程序的消息隊列中投遞一條 WM_QUIT消息並返回。WinMain函數中,GetMessage 函數只有在收到WM_QUIT消息時才返回0,此時消息循環才結束,程序退出。傳遞給 PostQuitMessage函數的參數值將作爲WM_QUIT消息的wParam參數,這個值通常用做WinMain函數的返回值。

完整代碼示例

#include <Windows.h>

//處理消息
LRESULT CALLBACK Windowproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_LBUTTONDOWN://左鍵按下
    {
        int xPos, yPos;
        xPos = LOWORD(lParam);
        yPos = HIWORD(lParam);

        //打印操作
        TCHAR buf[1024];
        wsprintf(buf, TEXT("x = %d, y = %d "), xPos, yPos);
        MessageBox(hwnd, buf, TEXT("鼠標按下"), MB_OK);
    }
    break;
    case WM_KEYDOWN://按下鍵盤
        MessageBox(hwnd, TEXT("鍵盤按下"), TEXT("鍵盤消息"), MB_OK);
        break;
    case WM_PAINT://繪圖消息
    {
        PAINTSTRUCT ps;//繪圖結構體
        HDC hdc = BeginPaint(hwnd, &ps);
        //繪製文字
        TextOut(hdc, 100, 100, TEXT("hello world"), strlen("hello world"));
        EndPaint(hwnd, &ps);
    }
        break;
    default:
        break;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    //實現底層窗口步驟
    //1、設計窗口類
    //2、註冊窗口類
    //3、創建窗口類
    //4、通過循環獲取消息
    //5、處理消息(窗口過程)

    //設計窗口
    WNDCLASS wc;
    wc.cbClsExtra = 0;//額外內存爲0
    wc.cbWndExtra = 0;//窗口額外內存
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//設置背景
    wc.hCursor = LoadCursor(NULL, IDC_HAND);//設置光標
    wc.hIcon = LoadIcon(NULL, IDI_WARNING);//設置鼠標
    wc.hInstance = hInstance;//當前實例句柄.
    wc.lpfnWndProc = Windowproc;//窗口過程回調函數.自定義
    wc.lpszClassName = TEXT("WINDOW");//指定窗口類名
    wc.lpszMenuName = NULL;//菜單名
    wc.style = 0;//默認風格

    //註冊窗口類
    RegisterClass(&wc);

    //創建窗口類
    /*
    lpClassName, 類名
    lpWindowName, 窗口名
    dwStyle, 顯示風格
    x, y, 窗口起始座標
    nWidth,窗口寬度
    nHeight,高度
    hWndParent,父窗口, NULL
    hMenu, 菜單,NULL
    hInstance,實例句柄
    lpParam,其他參數
    */
    HWND hwnd = CreateWindow(wc.lpszClassName, TEXT("TEXT WINDOW"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    //顯示和更新
    ShowWindow(hwnd, SW_SHOWNORMAL);
    UpdateWindow(hwnd);

    //通過循環取消息
    MSG msg;
    while (1)
    {
        if (GetMessage(&msg, NULL, 0, 0) == FALSE)
        {
            break;
        }

        //翻譯消息
        TranslateMessage(&msg);

        //分發消息
        DispatchMessage(&msg);
    }

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