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 }
-
- 界面展示:
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 }
-
- 创建窗口:用户使用自定义的窗口类或系统预设的窗口类去创建一个实例窗口,这里我们比较需要注意的一点是窗口样式与控件(子窗口)样式的组合,还是如上文创建一个子窗口(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);
注:图片中的窗口及控件样式均在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 }
-
- 系统定义消息:在定义的区间内我们将消息分四大类:窗口消息,命令消息,通知消息及用户自定义消息,这些消息都会进入到窗口过程函数中去,下面的例子将进行演示。
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 }
-
- 窗口绘制:关于窗口的绘制,我们先了解一下窗口的绘制过程(先不考虑需要重绘的前提),系统会先绘制非客户区(标题栏,边框等,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 }
-
- 窗口的销毁: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 }
-
- 窗口的子类化:把"子类化"和"超类化"的概念放在这里讲,是因为在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 }
-
- 窗口超类化:在窗口类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 }
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 }
最后,我们发现入口函数执行最终进入到了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;
1 //MFC 2 void CMFCTestDlg::OnBnClickedBtnTest() 3 { 4 5 }
-
- 窗口过程:如上,我们通过可视化的界面,很容易地实现了控件消息的响应处理,那么,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 }
-
- 入口函数找到了,那么像win32那样的界面是怎么创建的呢?在主线程里我们继续分析,可以找到窗口生成的如下代码:
1 CMfcTestDlg dlg; 2 m_pMainWnd = &dlg; 3 INT_PTR nResponse = dlg.DoModal(); 4
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 //这里不纠结模态与非模态对面框
-
- 那么窗口过程呢?怎么找不到,我们再来对比下:
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 }
-
- 通过对比,我们知道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()
-
- 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 }