MFC入门

一. WIN32的界面程序

  为了更好地使用MFC来编写界面,我们得了解win32的界面程序的执行过程,只有弄清楚"UI的创建","消息过程的响应"等相关信息,才能帮助我们更好地使用MFC去编写界面程序。

  1. win32界面

    • win32界面创建的流程:

    • 实现代码:
 1 #include "test.h"
 2 #include <windows.h>
 3 
 4 //窗口过程函数 
 5 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 6 {
 7     HDC hDC = { 0 };
 8     RECT stRect = { 0 };
 9     PAINTSTRUCT stPaint = {0};
10 
11     switch (uiMsg)
12     {
13     case WM_PAINT: //绘制
14         hDC = BeginPaint(stHwnd, &stPaint);//申请画面,锁定绘制区域
15 
16         //绘制界面,结合相应的GDI函数进行操作
17         Rectangle(hDC, 10, 10, 300, 200);
18 
19         EndPaint(stHwnd, &stPaint);
20         break;
21 
22     case WM_DESTROY:
23         PostQuitMessage(0);
24         break;
25 
26     default:
27         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
28     }
29     return 0;
30 }
31 
32 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
33 {
34     MSG stMsg = { 0 };
35     HWND stHwnd = { 0 };
36     WNDCLASS stWndClass = { 0 };
37 
38     const TCHAR szAppName[] = TEXT("windowsUI");
39     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
40     stWndClass.lpfnWndProc = pfnWndProc;
41     stWndClass.cbClsExtra = 0;
42     stWndClass.cbWndExtra = 0;
43     stWndClass.hInstance = hInstance;
44     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
45     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
46     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
47     stWndClass.lpszMenuName = nullptr;
48     stWndClass.lpszClassName = szAppName;
49 
50     if (!RegisterClass(&stWndClass))//注册窗口类
51     {
52         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
53         return 0;
54     }
55 
56     stHwnd = CreateWindow(szAppName, //创建窗口
57         TEXT("windows UI"),
58         WS_OVERLAPPEDWINDOW,
59         CW_USEDEFAULT,
60         CW_USEDEFAULT,
61         800,
62         600,
63         nullptr,
64         nullptr,
65         hInstance,
66         nullptr);
67 
68     ShowWindow(stHwnd, iCmdShow); //显示窗口
69     UpdateWindow(stHwnd);
70 
71     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
72     {
73         TranslateMessage(&stMsg);
74         DispatchMessage(&stMsg);
75     }
76 
77     return stMsg.wParam;
78 }
View Code
    • 界面展示:

  2. win32界面详细说明

      我们根据上面的实例,来慢慢说明我们创建一个UI界面,需要执行哪些操作及其中的原因。

    • 注册窗口类:WNDCLASS定义了窗口类的一些全局特性,它通常与窗口类本身相关联,而不是与特定的窗口实例相关,这些自定义的窗口类注册成功后,用户都可以使用,同时,系统也预设了很多类型的窗口类(eg,Button,Listbox类等),如下代码创建一个系统预设的子窗口(Button控件类:WC_BUTTON),如此,就不需要再去设置WNDCLASS 结构体去注册窗口类了,代码如下:
 1 #include "test.h"
 2 #include <windows.h>
 3 #include "CommCtrl.h"
 4 
 5 #define IDC_BTN_TEST (101)
 6 
 7 //窗口过程函数 
 8 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 9 {
10     HDC hDC = { 0 };
11     RECT stRect = { 0 };
12     PAINTSTRUCT stPaint = {0};
13 
14     //Button
15     HWND hBtnWnd = { 0 };
16 
17     switch (uiMsg)
18     {
19     case WM_CREATE://创建button控件
20         GetClientRect(stHwnd, &stRect);
21         hBtnWnd = CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_BTN_TEST, nullptr, nullptr);
22 
23 
24     case WM_PAINT: //绘制
25         hDC = BeginPaint(stHwnd, &stPaint);//申请画面,锁定绘制区域
26 
27         //绘制界面,结合相应的GDI函数进行操作
28         Rectangle(hDC, 10, 10, 300, 200);
29 
30         EndPaint(stHwnd, &stPaint);
31         break;
32 
33     case WM_DESTROY:
34         PostQuitMessage(0);
35         break;
36 
37     default:
38         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
39     }
40     return 0;
41 }
42 
43 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
44 {
45     MSG stMsg = { 0 };
46     HWND stHwnd = { 0 };
47     WNDCLASS stWndClass = { 0 };
48 
49     const TCHAR szAppName[] = TEXT("windowsUI");
50     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
51     stWndClass.lpfnWndProc = pfnWndProc;
52     stWndClass.cbClsExtra = 0;
53     stWndClass.cbWndExtra = 0;
54     stWndClass.hInstance = hInstance;
55     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
56     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
57     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
58     stWndClass.lpszMenuName = nullptr;
59     stWndClass.lpszClassName = szAppName;
60 
61     if (!RegisterClass(&stWndClass))//注册窗口类
62     {
63         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
64         return 0;
65     }
66 
67     stHwnd = CreateWindow(szAppName, //创建窗口
68         TEXT("windows UI"),
69         WS_OVERLAPPEDWINDOW,
70         CW_USEDEFAULT,
71         CW_USEDEFAULT,
72         800,
73         600,
74         nullptr,
75         nullptr,
76         hInstance,
77         nullptr);
78 
79     ShowWindow(stHwnd, iCmdShow); //显示窗口
80     UpdateWindow(stHwnd);
81 
82     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
83     {
84         TranslateMessage(&stMsg);
85         DispatchMessage(&stMsg);
86     }
87 
88     return stMsg.wParam;
89 }
View Code

    •  创建窗口:用户使用自定义的窗口类或系统预设的窗口类去创建一个实例窗口,这里我们比较需要注意的一点是窗口样式与控件(子窗口)样式的组合,还是如上文创建一个子窗口(BUTTON控件类)来进行说明。
1 CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_BTN_TEST, nullptr, nullptr);
View Code

      注:图片中的窗口及控件样式均在WinUser.h文件中,具体含义可参考:https://learn.microsoft.com/zh-cn/windows/win32/winmsg/window-styles 

    • 窗口过程:我们还是以子窗口(BUTTON控件)为例,结合代码来理解下面的说明

  1 #include "test.h"
  2 #include <windows.h>
  3 #include "CommCtrl.h"
  4 
  5 #define IDC_BTN_TEST (101)
  6 
  7 //窗口过程函数 
  8 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
  9 {
 10     HDC hDC = { 0 };
 11     RECT stRect = { 0 };
 12     PAINTSTRUCT stPaint = {0};
 13 
 14     //Button
 15     HWND hBtnWnd = { 0 };
 16 
 17     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
 18 
 19     switch (uiMsg)
 20     {
 21     case WM_CREATE://创建button控件
 22         GetClientRect(stHwnd, &stRect);
 23         hBtnWnd = CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_BTN_TEST, nullptr, nullptr);
 24 
 25 
 26     case WM_COMMAND:
 27         if ((IDC_BTN_TEST == LOWORD(wParam)) && (BN_CLICKED == HIWORD(wParam)))//button按钮测试
 28         {
 29             Print(TEXT("Button被按下"), TEXT("Button测试"));
 30         }
 31         break;
 32 
 33     case WM_NOTIFY:
 34 
 35         break;
 36 
 37     case WM_PAINT: //绘制
 38         hDC = BeginPaint(stHwnd, &stPaint);//申请画面,锁定绘制区域
 39 
 40         //绘制界面,结合相应的GDI函数进行操作
 41         Rectangle(hDC, 10, 10, 300, 200);
 42 
 43         EndPaint(stHwnd, &stPaint);
 44         break;
 45 
 46     case WM_DESTROY:
 47         PostQuitMessage(0);
 48         break;
 49 
 50     default:
 51         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 52     }
 53     return 0;
 54 }
 55 
 56 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
 57 {
 58     MSG stMsg = { 0 };
 59     HWND stHwnd = { 0 };
 60     WNDCLASS stWndClass = { 0 };
 61 
 62     const TCHAR szAppName[] = TEXT("windowsUI");
 63     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
 64     stWndClass.lpfnWndProc = pfnWndProc;
 65     stWndClass.cbClsExtra = 0;
 66     stWndClass.cbWndExtra = 0;
 67     stWndClass.hInstance = hInstance;
 68     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 69     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
 70     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 71     stWndClass.lpszMenuName = nullptr;
 72     stWndClass.lpszClassName = szAppName;
 73 
 74     if (!RegisterClass(&stWndClass))//注册窗口类
 75     {
 76         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
 77         return 0;
 78     }
 79 
 80     stHwnd = CreateWindow(szAppName, //创建窗口
 81         TEXT("windows UI"),
 82         WS_OVERLAPPEDWINDOW,
 83         CW_USEDEFAULT,
 84         CW_USEDEFAULT,
 85         800,
 86         600,
 87         nullptr,
 88         nullptr,
 89         hInstance,
 90         nullptr);
 91 
 92     ShowWindow(stHwnd, iCmdShow); //显示窗口
 93     UpdateWindow(stHwnd);
 94 
 95     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
 96     {
 97         TranslateMessage(&stMsg);
 98         DispatchMessage(&stMsg);
 99     }
100 
101     return stMsg.wParam;
102 }
View Code

    • 系统定义消息:在定义的区间内我们将消息分四大类:窗口消息,命令消息,通知消息及用户自定义消息,这些消息都会进入到窗口过程函数中去,下面的例子将进行演示。

  1 #include "test.h"
  2 #include <stdio.h>
  3 #include <windows.h>
  4 #include "CommCtrl.h"
  5 
  6 #pragma comment(lib, "comctl32.lib") 
  7 
  8 #define IDC_MY_BUTTON    (101)
  9 #define IDC_MY_LISTBOX    (102)
 10 #define IDC_MY_TREEVIEW (103)
 11 #define WM_USER_TEST (WM_USER + 10)
 12 
 13 //ListBox
 14 HWND g_hListBoxWnd = { 0 };
 15 INITCOMMONCONTROLSEX g_iccex = { 0 };
 16 
 17 //Treeview
 18 HWND g_hTreeviewWnd = { 0 };
 19 TVINSERTSTRUCT g_tvis = { 0 };
 20 INITCOMMONCONTROLSEX g_iccexTree = { 0 };
 21 
 22 void CreateListBox(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 23 {
 24     g_iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
 25     g_iccex.dwICC = ICC_LISTVIEW_CLASSES; // 初始化列表框等控件的类
 26     InitCommonControlsEx(&g_iccex);
 27 
 28     g_hListBoxWnd = CreateWindow(WC_LISTBOX, nullptr, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 10, 250, 200, 150, stHwnd, (HMENU)IDC_MY_LISTBOX, nullptr, nullptr);
 29     // 向列表框中添加一些项(可选)
 30     for (int iNum = 0; iNum < 10; ++iNum)
 31     {
 32         char buffer[32] = { 0 };
 33         sprintf_s(buffer, "Item %d", iNum);
 34         SendMessage(g_hListBoxWnd, LB_ADDSTRING, 0, (LPARAM)buffer);
 35     }
 36 }
 37 
 38 void CreateTreeview(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 39 {
 40     HTREEITEM hRoot = { 0 };
 41     HTREEITEM hChild1 = { 0 };
 42     HTREEITEM hChild2 = { 0 };
 43     HTREEITEM stItem = { 0 };
 44 
 45     g_iccexTree.dwSize = sizeof(INITCOMMONCONTROLSEX);
 46     g_iccexTree.dwICC = ICC_TREEVIEW_CLASSES; // 初始化TreeView控件
 47     InitCommonControlsEx(&g_iccexTree);
 48 
 49     g_hTreeviewWnd = CreateWindowEx(0, WC_TREEVIEW, nullptr, WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_SHOWSELALWAYS, 10, 430, 200, 300, stHwnd, (HMENU)IDC_MY_TREEVIEW, nullptr, nullptr);
 50     // 填充树视图控件
 51     g_tvis.hParent = TVI_ROOT; // 根节点句柄,或者某个子节点的句柄
 52     g_tvis.hInsertAfter = TVI_LAST; // 插入到父节点的最后一个子项之后
 53     g_tvis.item.mask = TVIF_TEXT; // 设置文本
 54     g_tvis.item.pszText = TEXT("Root"); // 文本内容
 55     // 插入根节点
 56     hRoot = TreeView_InsertItem(g_hTreeviewWnd, &g_tvis);
 57     // 为根节点添加子项
 58     g_tvis.hParent = hRoot;
 59     g_tvis.item.pszText = TEXT("Child 1");
 60     hChild1 = TreeView_InsertItem(g_hTreeviewWnd, &g_tvis);
 61     g_tvis.item.pszText = TEXT("Child 2");
 62     hChild2 = TreeView_InsertItem(g_hTreeviewWnd, &g_tvis);
 63     // 为 Child 1 添加子项
 64     g_tvis.hParent = hChild1;
 65     g_tvis.item.pszText = TEXT("Grandchild 1");
 66     TreeView_InsertItem(g_hTreeviewWnd, &g_tvis);
 67     // 为 Child 2 添加子项
 68     g_tvis.hParent = hChild2;
 69     g_tvis.item.pszText = TEXT("Grandchild 2");
 70     TreeView_InsertItem(g_hTreeviewWnd, &g_tvis);
 71 }
 72 
 73 //窗口过程函数 
 74 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 75 {
 76     HDC hDC = { 0 };
 77     RECT stRect = { 0 };
 78     PAINTSTRUCT stPaint = {0};
 79 
 80     //Button
 81     HWND hBtnWnd = { 0 };
 82 
 83     //Treeview
 84     NMHDR* pNMHDR = nullptr;
 85 
 86     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
 87 
 88     switch (uiMsg)
 89     {
 90     case WM_CREATE:
 91         //创建button控件
 92         GetClientRect(stHwnd, &stRect);
 93         hBtnWnd = CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_MY_BUTTON, nullptr, nullptr);
 94 
 95         //创建LIST_BOX用来测试WM_COMMAND消息
 96         CreateListBox(stHwnd, uiMsg, wParam, lParam);
 97 
 98         //创建treeview控件来测试WM_NOTIFY消息
 99         CreateTreeview(stHwnd, uiMsg, wParam, lParam);
100 
101         break;
102 
103     case WM_USER_TEST:
104         Print(TEXT("自定义消息测试"), TEXT("自定义消息"));
105         break;
106 
107     case WM_COMMAND:
108         if ((IDC_MY_BUTTON == LOWORD(wParam)) && (BN_CLICKED == HIWORD(wParam)))//button按钮测试
109         {
110             Print(TEXT("Button被按下"), TEXT("Button测试"));
111         }
112         else if ((IDC_MY_LISTBOX == LOWORD(wParam)) && (LBN_SELCHANGE == HIWORD(wParam)))//点击ListBox测试
113         {
114             HWND hwndListBox = (HWND)lParam;
115             int index = SendMessage(hwndListBox, LB_GETCURSEL, 0, 0);
116             if (index != LB_ERR)
117             {
118                 char buffer[256] = {0};
119                 SendMessage(hwndListBox, LB_GETTEXT, index, (LPARAM)buffer);
120                 Print(buffer, TEXT("Selected Item"));
121             }
122         }
123 
124         break;
125 
126     case WM_NOTIFY:
127         pNMHDR = (NMHDR*)lParam;
128 
129         if ((pNMHDR->hwndFrom == g_hTreeviewWnd) && (TVN_SELCHANGED == pNMHDR->code))//Treeview选中测试
130         {
131             LPNMTREEVIEW pNMTV = (LPNMTREEVIEW)lParam;
132             // pNMTV->itemNew 包含了新选中项的信息
133 
134             TVITEM item = { 0 };
135             HTREEITEM htItem = { 0 };
136             TCHAR buffer[256] = { 0 };
137 
138             htItem = TreeView_GetSelection(g_hTreeviewWnd);
139 
140             item.mask = TVIF_TEXT;
141             item.hItem = htItem;
142             item.pszText = buffer;
143             item.cchTextMax = sizeof(buffer) / sizeof(TCHAR);
144 
145             TreeView_GetItem(g_hTreeviewWnd, &item);
146             Print(buffer, TEXT("Selected"));
147 
148         }
149         break;
150 
151     case WM_PAINT: //绘制
152         hDC = BeginPaint(stHwnd, &stPaint);//申请画布,锁定绘制区域
153 
154         //绘制界面,结合相应的GDI函数进行操作
155         Rectangle(hDC, 10, 10, 300, 200);
156 
157         EndPaint(stHwnd, &stPaint);
158         break;
159 
160     case WM_DESTROY:
161         PostQuitMessage(0);
162         break;
163 
164     default:
165         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
166     }
167     return 0;
168 }
169 
170 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
171 {
172     MSG stMsg = { 0 };
173     HWND stHwnd = { 0 };
174     WNDCLASS stWndClass = { 0 };
175 
176     const TCHAR szAppName[] = TEXT("windowsUI");
177     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
178     stWndClass.lpfnWndProc = pfnWndProc;
179     stWndClass.cbClsExtra = 0;
180     stWndClass.cbWndExtra = 0;
181     stWndClass.hInstance = hInstance;
182     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
183     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
184     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
185     stWndClass.lpszMenuName = nullptr;
186     stWndClass.lpszClassName = szAppName;
187 
188     if (!RegisterClass(&stWndClass))//注册窗口类
189     {
190         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
191         return 0;
192     }
193 
194     stHwnd = CreateWindow(szAppName, //创建窗口
195         TEXT("windows UI"),
196         WS_OVERLAPPEDWINDOW,
197         CW_USEDEFAULT,
198         CW_USEDEFAULT,
199         800,
200         600,
201         nullptr,
202         nullptr,
203         hInstance,
204         nullptr);
205 
206     ShowWindow(stHwnd, iCmdShow); //显示窗口
207     UpdateWindow(stHwnd);
208 
209     //自定义消息测试
210     SendMessage(stHwnd, WM_USER_TEST, 0, 0);
211 
212     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
213     {
214         TranslateMessage(&stMsg);
215         DispatchMessage(&stMsg);
216     }
217 
218     return stMsg.wParam;
219 }
View Code

    •  窗口绘制:关于窗口的绘制,我们先了解一下窗口的绘制过程(先不考虑需要重绘的前提),系统会先绘制非客户区(标题栏,边框等,no-client,在窗口消息里一般以NC表示,eg,WM_NCPAINT),再擦除客户区的背景,最后再绘制客户区。因此,在窗口过程中,需要非客户区与客户区都需要重绘时,依次进入的窗口消息为WM_NCPAINT,WM_ERASEBKGND,WM_PAINT。

 

  1 #include "test.h"
  2 #include <stdio.h>
  3 #include <windows.h>
  4 #include "CommCtrl.h"
  5 
  6 #define IDC_MY_BUTTON    (101)
  7 
  8 
  9 //窗口过程函数 
 10 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 11 {
 12     HDC hDC = { 0 };
 13     RECT stRect = { 0 };
 14     PAINTSTRUCT stPaint = {0};
 15 
 16     //Button
 17     HWND hBtnWnd = { 0 };
 18 
 19     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
 20 
 21     switch (uiMsg)
 22     {
 23     case WM_CREATE:
 24         //创建button控件
 25         GetClientRect(stHwnd, &stRect);
 26         hBtnWnd = CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_MY_BUTTON, nullptr, nullptr);
 27 
 28         break;
 29 
 30     case WM_COMMAND:
 31         if ((IDC_MY_BUTTON == LOWORD(wParam)) && (BN_CLICKED == HIWORD(wParam)))//button按钮测试
 32         {
 33             Print(TEXT("Button被按下"), TEXT("Button测试"));
 34         }
 35 
 36         break;
 37 
 38     case WM_NCPAINT:
 39             
 40         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 41         //标题栏由系统去绘制,就算用户在这里自绘,系统也试图帮用户填充了背景,但实际上系统仍然保留了标题栏上的一些信息,如关闭按钮等。
 42         //所以,用户想在这里做到完全自主地按自己的想法自绘,几乎是不可能,开发者一般会去创建一个不带标题栏的窗口,在客户区做一个假的标题栏,以实现标题栏的重绘和美化
 43         break;
 44 
 45     case WM_ERASEBKGND:
 46         hDC = GetDC(stHwnd);
 47         if (hDC)
 48         {
 49             RECT stRect = { 0 };
 50             GetClientRect(stHwnd, &stRect);
 51 
 52             HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0)); // 红色背景
 53             FillRect(hDC, &stRect, hBrush);
 54             DeleteObject(hBrush);
 55 
 56             ReleaseDC(stHwnd, hDC);
 57         }
 58         break;
 59 
 60     case WM_PAINT: //绘制
 61         hDC = BeginPaint(stHwnd, &stPaint);//申请画布,锁定绘制区域
 62 
 63         //绘制界面,结合相应的GDI函数进行操作
 64         Rectangle(hDC, 10, 10, 300, 200);
 65 
 66         EndPaint(stHwnd, &stPaint);
 67         break;
 68 
 69     case WM_DESTROY:
 70         PostQuitMessage(0);
 71         break;
 72 
 73     default:
 74         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 75     }
 76     return 0;
 77 }
 78 
 79 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
 80 {
 81     MSG stMsg = { 0 };
 82     HWND stHwnd = { 0 };
 83     WNDCLASS stWndClass = { 0 };
 84 
 85     const TCHAR szAppName[] = TEXT("windowsUI");
 86     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
 87     stWndClass.lpfnWndProc = pfnWndProc;
 88     stWndClass.cbClsExtra = 0;
 89     stWndClass.cbWndExtra = 0;
 90     stWndClass.hInstance = hInstance;
 91     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 92     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
 93     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 94     stWndClass.lpszMenuName = nullptr;
 95     stWndClass.lpszClassName = szAppName;
 96 
 97     if (!RegisterClass(&stWndClass))//注册窗口类
 98     {
 99         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
100         return 0;
101     }
102 
103     stHwnd = CreateWindow(szAppName, //创建窗口
104         TEXT("windows UI"),
105         WS_OVERLAPPEDWINDOW,
106         CW_USEDEFAULT,
107         CW_USEDEFAULT,
108         800,
109         600,
110         nullptr,
111         nullptr,
112         hInstance,
113         nullptr);
114 
115     ShowWindow(stHwnd, iCmdShow); //显示窗口
116     UpdateWindow(stHwnd);
117 
118     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
119     {
120         TranslateMessage(&stMsg);
121         DispatchMessage(&stMsg);
122     }
123 
124     return stMsg.wParam;
125 }
View Code

    •  窗口的销毁:MFC里比较复杂的图形界面会涉及到子窗口与父窗口销毁顺序出错导致的一系列问题,所以在这里我们将理清几个关于窗口销毁相关的几个窗口消息。

 1 #include "test.h"
 2 #include <stdio.h>
 3 #include <windows.h>
 4 #include "CommCtrl.h"
 5 
 6 //窗口过程函数 
 7 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 8 {
 9     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
10 
11     switch (uiMsg)
12     {
13     case WM_SYSCOMMAND://通常与系统菜单、系统命令或控件的特定通知相关,一般用来处理标题栏上的最大,最小,还原,关闭按钮的消息事件等。
14 
15         if (SC_CLOSE == (wParam & 0xFFF0))
16         {
17             Print(TEXT("enter syscommand"), TEXT("syscommand"));
18             return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
19         }
20 
21         break;
22 
23     case WM_CLOSE:
24 
25         //DestroyWindow(stHwnd);//用户自己销毁窗口
26         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);//系统销毁窗口
27 
28         break;
29 
30     case WM_DESTROY:
31         PostQuitMessage(0);
32         break;
33 
34     case WM_NCDESTROY://这是窗口被销毁前,窗口过程处理的最后一条消息
35         //deelte this //在MFC的子窗口的销毁中,经常会用到该语句,确保窗口和窗口对象都成功销毁
36         break;
37     case WM_QUIT:
38         //在窗口过程函数中,不可能进入此逻辑
39         break;
40 
41     default:
42         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
43     }
44     return 0;
45 }
46 
47 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
48 {
49     MSG stMsg = { 0 };
50     HWND stHwnd = { 0 };
51     WNDCLASS stWndClass = { 0 };
52 
53     const TCHAR szAppName[] = TEXT("windowsUI");
54     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
55     stWndClass.lpfnWndProc = pfnWndProc;
56     stWndClass.cbClsExtra = 0;
57     stWndClass.cbWndExtra = 0;
58     stWndClass.hInstance = hInstance;
59     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
60     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
61     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
62     stWndClass.lpszMenuName = nullptr;
63     stWndClass.lpszClassName = szAppName;
64 
65     if (!RegisterClass(&stWndClass))//注册窗口类
66     {
67         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
68         return 0;
69     }
70 
71     stHwnd = CreateWindow(szAppName, //创建窗口
72         TEXT("windows UI"),
73         WS_OVERLAPPEDWINDOW,
74         CW_USEDEFAULT,
75         CW_USEDEFAULT,
76         800,
77         600,
78         nullptr,
79         nullptr,
80         hInstance,
81         nullptr);
82 
83     ShowWindow(stHwnd, iCmdShow); //显示窗口
84     UpdateWindow(stHwnd);
85 
86     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
87     {
88         TranslateMessage(&stMsg);
89         DispatchMessage(&stMsg);
90     }
91 
92     return stMsg.wParam;
93 }
View Code

 

    •  窗口的子类化:把"子类化"和"超类化"的概念放在这里讲,是因为在win32的环境下,结合代码可以更好地理解。子类化就是修改窗口过程函数,把需要特殊处理的窗口进行单独处理,不需要处理的窗口继续丢给原窗口过程函数来实现,这就有点类似之前我们讲到的函数模板的全特化。

  1 #include "test.h"
  2 #include <stdio.h>
  3 #include <windows.h>
  4 #include "CommCtrl.h"
  5 
  6 #define IDC_BTN_TEST (101)
  7 
  8 #pragma comment(lib, "comctl32.lib") 
  9 
 10 //窗口过程函数 
 11 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 12 {
 13     HDC hDC = {0};
 14     PAINTSTRUCT stPaint = {0};
 15     char acText[] = "normal dialog process";
 16 
 17     //Button
 18     RECT stRect = { 0 };
 19     HWND hBtnWnd = { 0 };
 20     
 21     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
 22 
 23     switch (uiMsg)
 24     {
 25     case WM_CREATE://创建button控件
 26       GetClientRect(stHwnd, &stRect);
 27       hBtnWnd = CreateWindow(WC_BUTTON, TEXT("创建button按钮"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, (stRect.right - stRect.left) / 2 - 60, (stRect.bottom - stRect.top) / 2 - 15, 120, 30, stHwnd, (HMENU)IDC_BTN_TEST, nullptr, nullptr);
 28       break;
 29 
 30     case WM_COMMAND:
 31         if ((IDC_BTN_TEST == LOWORD(wParam)) && (BN_CLICKED == HIWORD(wParam)))
 32         {
 33             Print(TEXT("clicked button"), TEXT("Button"));
 34         }
 35         break;
 36 
 37     case WM_PAINT:
 38         hDC = BeginPaint(stHwnd, &stPaint);
 39 
 40         TextOut(hDC, 50, 50, acText, strlen(acText));
 41 
 42         EndPaint(stHwnd, &stPaint);
 43         break;
 44 
 45     case WM_DESTROY:
 46         PostQuitMessage(0);
 47         break;
 48 
 49     default:
 50         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 51     }
 52     return 0;
 53 }
 54 
 55 LRESULT CALLBACK MySubWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, ULONG_PTR dwRefData)
 56 {
 57     HDC hDC = { 0 };
 58     PAINTSTRUCT stPaint = { 0 };
 59     char acText[] = "Dialog Subclassed";
 60 
 61     switch (uiMsg)
 62     {
 63     case WM_PAINT:
 64         hDC = BeginPaint(stHwnd, &stPaint);
 65 
 66         SetTextColor(hDC, RGB(255, 0, 255));
 67         TextOut(hDC, 50, 50, acText, strlen(acText));
 68 
 69         EndPaint(stHwnd, &stPaint);
 70         break;
 71 
 72     default:
 73         return DefSubclassProc(stHwnd, uiMsg, wParam, lParam);
 74     }
 75 
 76     return 0;    
 77 }
 78 
 79 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
 80 {
 81     MSG stMsg = { 0 };
 82     HWND stHwnd = { 0 };
 83     WNDCLASS stWndClass = { 0 };
 84 
 85     const TCHAR szAppName[] = TEXT("windowsUI");
 86     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
 87     stWndClass.lpfnWndProc = pfnWndProc;
 88     stWndClass.cbClsExtra = 0;
 89     stWndClass.cbWndExtra = 0;
 90     stWndClass.hInstance = hInstance;
 91     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 92     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
 93     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 94     stWndClass.lpszMenuName = nullptr;
 95     stWndClass.lpszClassName = szAppName;
 96 
 97     if (!RegisterClass(&stWndClass))//注册窗口类
 98     {
 99         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
100         return 0;
101     }
102 
103     stHwnd = CreateWindow(szAppName, //创建窗口
104         TEXT("windows UI"),
105         WS_OVERLAPPEDWINDOW,
106         CW_USEDEFAULT,
107         CW_USEDEFAULT,
108         800,
109         600,
110         nullptr,
111         nullptr,
112         hInstance,
113         nullptr);
114 
115     ShowWindow(stHwnd, iCmdShow); //显示窗口
116     UpdateWindow(stHwnd);
117 
118 #if 1
119     //窗口子类化
120     if (!SetWindowSubclass(stHwnd, MySubWndProc, 0, 0)) 
121     {
122         MessageBox(nullptr, TEXT("Subclassed class failure"), szAppName, MB_OK);
123         return 0;
124     }
125     RECT stRect = {0};
126     GetClientRect(stHwnd, &stRect);
127     InvalidateRect(stHwnd, &stRect, TRUE); //让客户区重绘,进入子类化窗口过程的WM_PAINT
128 #endif
129 
130 #if 0
131     //窗口子类化
132     WNDPROC wpSubProc = (WNDPROC)SetWindowLong(stHwnd, GWL_WNDPROC, (DWORD)MySubWndProc);//窗口过程里调用原函数:CallWindowProc
133 #endif
134 
135     while (GetMessage(&stMsg, nullptr, 0, 0))//消息循环
136     {
137         TranslateMessage(&stMsg);
138         DispatchMessage(&stMsg);
139     }
140 
141     //移除子类化
142     RemoveWindowSubclass(stHwnd, MySubWndProc, 0);
143 
144     return stMsg.wParam;
145 }
View Code

    • 窗口超类化:在窗口类WNDCLASS或WNDCLASSEX级别进行的改变窗口类特征,下面的例子对窗口扩展了"鼠标双击事件"消息的响应,但和我们理解的鼠标双击事件不太一样,这个在MFC的会进行详细说明和设计,这里暂时不作扩展。
  1 #include "test.h"
  2 #include <stdio.h>
  3 #include <windows.h>
  4 
  5 const char g_acAppName[] = TEXT("SuperClass");
  6 WNDCLASS g_stSuperClass = { 0 };
  7 HWND g_hSuperWnd = { 0 };
  8 
  9 LRESULT CALLBACK pfnSuperWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 10 {
 11     switch (uiMsg)
 12     {
 13     case WM_LBUTTONDOWN:
 14 
 15         MessageBox(nullptr, TEXT("SuperCalss:Double clicked"), TEXT("SuperClass"), MB_OK);
 16         break;
 17 
 18     default:
 19         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 20     }
 21 
 22     return 0;
 23 }
 24 
 25 //窗口过程函数 
 26 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 27 {
 28     HDC hDC = {0};
 29     PAINTSTRUCT stPaint = {0};
 30     char acText[] = "normal dialog process";
 31 
 32     auto Print = [](LPCTSTR strInfo, LPCTSTR strTitle){ MessageBox(nullptr, strInfo, strTitle, MB_OK); };
 33 
 34     switch (uiMsg)
 35     {
 36     case WM_LBUTTONDBLCLK:
 37 
 38         Print(TEXT("Double clicked"), TEXT("windowsUI"));
 39         break;
 40 
 41     case WM_PAINT:
 42         hDC = BeginPaint(stHwnd, &stPaint);
 43 
 44         TextOut(hDC, 50, 50, acText, strlen(acText));
 45 
 46         EndPaint(stHwnd, &stPaint);
 47         break;
 48 
 49     case WM_DESTROY:
 50         PostQuitMessage(0);
 51         break;
 52 
 53     default:
 54         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
 55     }
 56     return 0;
 57 }
 58 
 59 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
 60 {
 61     MSG stMsg = { 0 };
 62     HWND stHwnd = { 0 };
 63     WNDCLASS stWndClass = { 0 };
 64 
 65     const TCHAR szAppName[] = TEXT("windowsUI");
 66     stWndClass.style = CS_HREDRAW | CS_VREDRAW;
 67     stWndClass.lpfnWndProc = pfnWndProc;
 68     stWndClass.cbClsExtra = 0;
 69     stWndClass.cbWndExtra = 0;
 70     stWndClass.hInstance = hInstance;
 71     stWndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 72     stWndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
 73     stWndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 74     stWndClass.lpszMenuName = nullptr;
 75     stWndClass.lpszClassName = szAppName;
 76 
 77     if (!RegisterClass(&stWndClass))//注册窗口类
 78     {
 79         MessageBox(NULL, TEXT("Regiester failure"), szAppName, MB_ICONERROR);
 80         return 0;
 81     }
 82 
 83     stHwnd = CreateWindow(szAppName, //创建窗口
 84         TEXT("windows UI"),
 85         WS_OVERLAPPEDWINDOW,
 86         CW_USEDEFAULT,
 87         CW_USEDEFAULT,
 88         800,
 89         600,
 90         nullptr,
 91         nullptr,
 92         hInstance,
 93         nullptr);
 94 
 95     ShowWindow(stHwnd, iCmdShow); //显示窗口
 96     UpdateWindow(stHwnd);
 97 
 98     //=============================超类化例子==============================================
 99     GetClassInfo(nullptr, szAppName, &g_stSuperClass);
100 
101     g_stSuperClass.style |= CS_DBLCLKS;//鼠标双击事件
102     g_stSuperClass.lpfnWndProc = pfnSuperWndProc;//甚至可以替换掉窗口过程函数,注意这里和子类化的区别
103     g_stSuperClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);//这里改变一下背景,更容易分辨差别
104     g_stSuperClass.lpszClassName = g_acAppName;//类名
105 
106     if (!RegisterClass(&g_stSuperClass))
107     {
108         DWORD dwErrCode = GetLastError();
109         MessageBox(NULL, TEXT("Regiester new class failure"), g_acAppName, MB_ICONERROR);
110         return 0;
111     }
112 
113     g_hSuperWnd = CreateWindow(g_acAppName, TEXT("Super Dialog"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 400, nullptr, 0, nullptr, nullptr);
114 
115     ShowWindow(g_hSuperWnd, iCmdShow);
116     UpdateWindow(g_hSuperWnd);
117 
118     //====================消息循环=================================================
119     while (GetMessage(&stMsg, nullptr, 0, 0))
120     {
121         TranslateMessage(&stMsg);
122         DispatchMessage(&stMsg);
123     }
124 
125     return stMsg.wParam;
126 }
View Code

  3.总结

    通过上面的这些介绍,旨在用来了解窗口的创建,销毁及窗口过程的处理,只有知道了这些,我们才能更好去MFC的一些机制,本质上MFC只是帮我们把这些繁琐的代码都进行封装,并用可视化的UI界面让用户来操作,当然,MFC本身不只是用来写界面,它本身也是个强大的库,里面还对windows其他的一些API进行了封装,eg,像线程,文件操作等。上面的那些例子,很多概念都没有涉及到,像gdi编程,加载位图,双缓冲技术等,这些更适合放在MFC里面去讲。

二. MFC界面编写

  在这里,我们还是按照win32的方式,把MFC中窗口的生成,窗口消息及窗口过程处理函数进行说明,如此,如果你只是简单地使用MFC写一些基础界面,也就没有什么问题了。

  1.窗口的创建

    为了方便讲解,我们以"对话框"进行介绍举例,让MFC帮我们去注册,创建窗口,如图:

  2.  主线程的入口函数

    我们需要先了解不同程序的入口函数:CUI(控制台)入口函数--main ;GUI(win32)入口函数--WinMain;Dll(动态库)入口函数--DllMain。MFC本质也是win32,即非控制台,那我们就需要去寻找WinMain,在主线程文件MFCTest.cpp中

    把断点设在"MFCTestAPP theApp;"这一句,通过右下方"调用堆栈"模块(不太好截图),可以追溯到入口函数,如下

1 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2     _In_ LPTSTR lpCmdLine, int nCmdShow)
3 #pragma warning(suppress: 4985)
4 {
5     // call shared/exported WinMain
6     return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
7 }
View Code

    最后,我们发现入口函数执行最终进入到了MFCTest.cpp的InitInstance,如下图:

    到这里,我们便清晰地了解到程序的入口函数最终进入到InitInstance,也就是允许用户进行操作的函数。

  3.窗口的创建 

    前面我们讲到了主线程的入口函数,我们在InitInstance中看到了窗口的创建,当然,这些都被系统执行了,不需要我们再去做,但我们还是可以进一步了解到我们熟悉的创建过程(win32),继续看InitInstance函数。   

    

    到这里,我们就看到了win32里熟悉的创建窗口的函数,类似于前面win32程序里的CreatWindow,具体意义请查寻MSDN,至此,关于窗口的创建,我们就了解清楚了。

  4.窗口消息与窗口过程

    • 窗口消息:MFC把窗口消息进行了可视化的界面封装,并为每个消息添加了响应过程函数(可重载),下图中有系统添加的窗口消息响应函数,用户在MFCTestDlg.cpp中了解详细代码。

    • 控件消息:在前面我们了解到,主窗口上的控件消息被放在了WM_COMMAND和WM_NOTIFY里,在主窗口的窗口过程函数中进行处理,在MFC中又如何呢?我们且以一个"Button"为例,进行说明。

    将焦点放在"Button"上,右键,选择"添加事件处理程序",进入如下界面:

    我们在这里添加一个"Button被点击"的事件消息的响应函数,在此,我们再回顾一下,在前面我们处理"Button"的点击事件处理过程。

1     //win32
2     case WM_COMMAND:
3         if ((IDC_BTN_TEST == LOWORD(wParam)) && (BN_CLICKED == HIWORD(wParam)))//button按钮测试
4         {
5             Print(TEXT("Button被按下"), TEXT("Button测试"));
6         }
7         break;
View Code
1 //MFC
2 void CMFCTestDlg::OnBnClickedBtnTest()
3 {
4 
5 }
View Code
    • 窗口过程:如上,我们通过可视化的界面,很容易地实现了控件消息的响应处理,那么,MFC内部又是如何实现的呢?这里简单地对流程进行说明,有兴趣地可以深入地去阅读内部代码。

    控件消息的响应函数注册:

    在窗口过程函数中调用Button点击事件的响应函数:(CMFCTestDlg继承了CWnd类,所以我们对CWnd类的窗口过程WndProc进行分析)

  5. 窗口的绘制

    • 绘制界面:如下图

     

    • 资源rc与Resource.h:资源rc中的资源ID都包含在Resource.h中,因此,在操作资源rc时,不能打开Resource.h(文件的原子操作),或者,两者不能同时打开,否则,会出现如下情况:

    • 添加资源:在"资源视图"下,右键"添加资源",出现如下界面。    

    

   6. 总结

     到此,在MFC中,对于窗口的创建,窗口消息及窗口过程我们都进行了说明,关于每个控件的具体使用,及子窗口生成,销毁等都没有进行详细介绍,正应了标题,只是简单的MFC入门,能正常使用MFC编写简单的界面,关于控件的详细使用,请参见MFC所有控件的详细介绍(基于VS2013)

 

 

 

    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  1. 同样实现一个带按钮并点击事件的界面(代码就不贴了,比较简单),生成两个重要文件MfcTest.cpp和MfcTestDlg.cpp

  2. 代码入口在哪?也就是WinMain函数在哪?我要怎么写界面?

    • CUI(控制台)入口函数:main   GUI(win32)入口函数:WinMain          Dll(动态库)入口函数:DllMain;
    • MFC除了对原生界面进行封装外,还对线程,文件,网络,字符串,STL等都进行了封装,但本质是win32程序;
    • MfcTest.cpp主线程类,MfcTestDlg.cpp主窗口类;

   3. MfcTest.cpp主线程类分析:

    • 通过“调用堆栈”可以追溯到WinMain函数,通过一步步调用,最后调用到MfcTest.cpp的InitInstance函数,那我们就可以认为InitInstance == WinMain
1 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2     _In_ LPTSTR lpCmdLine, int nCmdShow)
3 #pragma warning(suppress: 4985)
4 {
5     // call shared/exported WinMain
6     return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
7 }
View Code
    • 入口函数找到了,那么像win32那样的界面是怎么创建的呢?在主线程里我们继续分析,可以找到窗口生成的如下代码:
1     CMfcTestDlg dlg;
2     m_pMainWnd = &dlg;
3     INT_PTR nResponse = dlg.DoModal();
4     
View Code

  4. MfcTestDlg.cpp主窗口类分析:

    • 窗口是怎么生成的?我们看下代码对比:
 1 //win32
 2     if (!RegisterClass(&stWndClass))//注册窗口类
 3     {
 4         MessageBox(NULL, _T("Regiester failure"), szAppName, MB_ICONERROR);
 5         return 0;
 6     }
 7 
 8     stHwnd = CreateWindow(szAppName, //创建窗口
 9         _T("windows UI"),
10         WS_OVERLAPPEDWINDOW,
11         CW_USEDEFAULT,
12         CW_USEDEFAULT,
13         800,
14         600,
15         NULL,
16         NULL,
17         hInstance,
18         NULL);
19 
20     ShowWindow(stHwnd, iCmdShow); //显示窗口
21     UpdateWindow(stHwnd);
22 
23 ... ...
24 
25 //MFC
26     CMfcTestDlg dlg;
27     m_pMainWnd = &dlg;
28     INT_PTR nResponse = dlg.DoModal();
29 
30 //这里不纠结模态与非模态对面框
View Code
    • 那么窗口过程呢?怎么找不到,我们再来对比下:
 1 //窗口过程函数 
 2 LRESULT CALLBACK pfnWndProc(HWND stHwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
 3 {
 4     switch (uiMsg)
 5     {
 6     case WM_CREATE:
 7         OnInitDialog();//CMfcTestDlg
 8         break;
 9     case WM_COMMAND:
10         OnSysCommand();//CMfcTestDlg,先不作判断,只是简单对比
11         break;
12     case WM_PAINT:
13         OnPaint();//CMfcTestDlg
14         break;
15     case WM_QUERYDRAGICON:
16         OnQueryDragIcon();//CMfcTestDlg
17         break;
18     case WM_DESTROY:
19         PostQuitMessage(0);
20         break;
21     default:
22         return DefWindowProc(stHwnd, uiMsg, wParam, lParam);
23     }
24 
25     return 0;
26 }
View Code
    • 通过对比,我们知道MFC只是对win32的窗口生成和窗口消息进行了二次封装,最后我们再分析下消息列表是怎么回事:
1 BEGIN_MESSAGE_MAP(CMfcTestDlg, CDialogEx)
2     ON_WM_SYSCOMMAND()
3     ON_WM_PAINT()
4     ON_WM_QUERYDRAGICON()
5     ON_BN_CLICKED(IDC_BTN_TEST1, &CMfcTestDlg::OnBnClickedBtnTest)
6     ON_BN_CLICKED(IDC_BTN_TEST2, &CMfcTestDlg::OnBnClickedBtnTest2)
7     ON_MESSAGE(WM_USER_BTN, &CMfcTestDlg::OnUserBtnTest)
8 END_MESSAGE_MAP()
View Code
    • F12跟进里面各种宏头都看晕了,但是说白了就是将消息ID与消息处理函数绑定起来,想想C11里的std::bind性能或者委托模式的CDelegate实现;

  5. 窗口消息之间的相互传递

    • 在这里只说下自定义消息的传递SendMessage(同步)和PostMessage(异步),代码如下:
 1 //自定义消息
 2 #define WM_USER_BTN (WM_USER + 100)
 3 
 4 ... ...
 5 
 6 BEGIN_MESSAGE_MAP(CMfcTestDlg, CDialogEx)
 7         ... ...
 8     ON_MESSAGE(WM_USER_BTN, &CMfcTestDlg::OnUserBtnTest)
 9 END_MESSAGE_MAP()    
10 
11 ... ... 
12 
13 LRESULT CMfcTestDlg::OnUserBtnTest(WPARAM wParam, LPARAM lParam)
14 {
15     MessageBox(_T("收到自定义消息"));
16 
17     return NULL;
18 }
View Code    
posted @ 2021-05-25 10:26  夜不眠  阅读(458)  评论(0编辑  收藏  举报