【问题描述】MFC是C++的经典框架,基于消息响应机制。网上介绍MFC消息响应的文章很多。而我认为,是否理解MFC,有一点很重要,那就是看能否脱离界面编辑器,编写对话框的代码。本文介绍两个方面:
(1)如何利用代码编写对话框;
(2)消息映射如何实现。
【解析】
1 利用代码编写对话框
先看代码:
main.cpp
- #include "stdafx.h"
- #include "resource.h"
- #include "tchar.h"
- #define MAX_LOADSTRING 100
- // Global Variables:
- HINSTANCE hInst; // current instance
- TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
- TCHAR szWindowClass[MAX_LOADSTRING]; // The title bar text
- // Foward declarations of functions included in this code module:
- ATOM MyRegisterClass(HINSTANCE hInstance);
- BOOL InitInstance(HINSTANCE, int);
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
- int APIENTRY WinMain(HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR lpCmdLine,
- int nCmdShow)
- {
- // TODO: Place code here.
- MSG msg;
- HACCEL hAccelTable;
- // Initialize global strings
- LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
- LoadString(hInstance, IDC_BTNTEST, szWindowClass, MAX_LOADSTRING);
- MyRegisterClass(hInstance);
- // Perform application initialization:
- if (!InitInstance (hInstance, nCmdShow))
- {
- return FALSE;
- }
- hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_BTNTEST);
- // Main message loop:
- while (GetMessage(&msg, NULL, 0, 0))
- {
- if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
- return msg.wParam;
- }
- //
- // FUNCTION: MyRegisterClass()
- //
- // PURPOSE: Registers the window class.
- //
- // COMMENTS:
- //
- // This function and its usage is only necessary if you want this code
- // to be compatible with Win32 systems prior to the 'RegisterClassEx'
- // function that was added to Windows 95. It is important to call this function
- // so that the application will get 'well formed' small icons associated
- // with it.
- //
- ATOM MyRegisterClass(HINSTANCE hInstance)
- {
- WNDCLASSEX wcex;
- wcex.cbSize = sizeof(WNDCLASSEX);
- wcex.style = CS_HREDRAW | CS_VREDRAW;
- wcex.lpfnWndProc = (WNDPROC)WndProc;
- wcex.cbClsExtra = 0;
- wcex.cbWndExtra = 0;
- wcex.hInstance = hInstance;
- wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_BTNTEST);
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
- wcex.lpszMenuName = (LPCSTR)IDC_BTNTEST;
- wcex.lpszClassName = szWindowClass;
- wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
- return RegisterClassEx(&wcex);
- }
- //
- // FUNCTION: InitInstance(HANDLE, int)
- //
- // PURPOSE: Saves instance handle and creates main window
- //
- // COMMENTS:
- //
- // In this function, we save the instance handle in a global variable and
- // create and display the main program window.
- //
- BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
- {
- HWND hWnd;
- hInst = hInstance; // Store instance handle in our global variable
- //menu
- static HMENU hMenu,hMenuPop1,hMenuPop2;
- hMenu = CreateMenu();
- hMenuPop1 = CreateMenu();
- hMenuPop2 = CreateMenu();
- AppendMenu(hMenuPop1, MF_STRING, IDM_TEST, "&测试");
- AppendMenu(hMenuPop2, MF_STRING, IDM_ABOUT, "&关于");
- AppendMenu(hMenuPop2, MF_STRING, IDM_EXIT, "&退出");
- AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPop1, "&菜单1");
- AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPop2, "&菜单2");
- hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, hMenu, hInstance, NULL);
- if (!hWnd)
- {
- return FALSE;
- }
- ShowWindow(hWnd, nCmdShow);
- UpdateWindow(hWnd);
- return TRUE;
- }
- //
- // FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
- //
- // PURPOSE: Processes messages for the main window.
- //
- // WM_COMMAND - process the application menu
- // WM_PAINT - Paint the main window
- // WM_DESTROY - post a quit message and return
- //
- //
- #define btn1 1
- #define btn2 2
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int wmId, wmEvent;
- PAINTSTRUCT ps;
- HDC hdc;
- TCHAR szHello[MAX_LOADSTRING];
- LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
- switch (message)
- {
- case WM_CREATE:
- {
- //button
- HWND hButton1 = CreateWindow(_T("button"), _T("Btn1"),
- WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 100, 100, 100, 30, hWnd, (HMENU)btn1, hInst, NULL);
- HWND hButton2 = CreateWindow(_T("button"), _T("Btn2"),
- WS_CHILD|WS_VISIBLE|BS_BITMAP, 100, 200, 100, 30, hWnd, (HMENU)btn2, hInst, NULL);
- HBITMAP hbmp=LoadBitmap(hInst,MAKEINTRESOURCE(IDB_Btn2));
- SendMessage(hButton2, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbmp);
- }
- break;
- case WM_COMMAND:
- wmId = LOWORD(wParam);
- wmEvent = HIWORD(wParam);
- // Parse the menu selections:
- switch (wmId)
- {
- case btn1:
- MessageBox(hWnd, _T("Button1 press!"), _T("Message"), MB_OK|MB_ICONINFORMATION);
- break;
- case btn2:
- MessageBox(hWnd, _T("Button2,press!"), _T("Message"), MB_OK|MB_ICONINFORMATION);
- break;
- case IDM_TEST:
- MessageBox(hWnd, _T("菜单测试"), _T("菜单测试"), MB_OK|MB_ICONINFORMATION);
- break;
- case IDM_ABOUT:
- DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
- break;
- case IDM_EXIT:
- DestroyWindow(hWnd);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- break;
- case WM_PAINT:
- hdc = BeginPaint(hWnd, &ps);
- // TODO: Add any drawing code here...
- RECT rt;
- GetClientRect(hWnd, &rt);
- DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
- EndPaint(hWnd, &ps);
- break;
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
- // Mesage handler for about box.
- LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
- {
- switch (message)
- {
- case WM_INITDIALOG:
- return TRUE;
- case WM_COMMAND:
- if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
- {
- EndDialog(hDlg, LOWORD(wParam));
- return TRUE;
- }
- break;
- }
- return FALSE;
- }
resource.h
- //{{NO_DEPENDENCIES}}
- // Microsoft Developer Studio generated include file.
- // Used by Test2.rc
- //
- #define IDS_APP_TITLE 1
- #define IDC_BTNTEST 2
- #define IDS_HELLO 3
- #define IDI_BTNTEST 101
- #define IDI_SMALL 102
- #define IDD_ABOUTBOX 103
- #define IDR_MENU1 110
- #define IDB_Btn2 201
- #define IDM_ABOUT 1001
- #define IDM_EXIT 1002
- #define IDM_TEST 1003
- // Next default values for new objects
- //
- #ifdef APSTUDIO_INVOKED
- #ifndef APSTUDIO_READONLY_SYMBOLS
- #define _APS_NEXT_RESOURCE_VALUE 113
- #define _APS_NEXT_COMMAND_VALUE 40001
- #define _APS_NEXT_CONTROL_VALUE 1000
- #define _APS_NEXT_SYMED_VALUE 101
- #endif
- #endif
实现步骤如下所述:
(1)设计窗口类
- WNDCLASSEX wcex;
- wcex.cbSize = sizeof(WNDCLASSEX);
- wcex.style = CS_HREDRAW | CS_VREDRAW;
- wcex.lpfnWndProc = (WNDPROC)WndProc;
- wcex.cbClsExtra = 0;
- wcex.cbWndExtra = 0;
- wcex.hInstance = hInstance;
- wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_BTNTEST);
- wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
- wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
- wcex.lpszMenuName = (LPCSTR)IDC_BTNTEST;
- wcex.lpszClassName = szWindowClass;
- wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
最为重要的一行代码:
- wcex.lpfnWndProc = (WNDPROC)WndProc;
该行代码传递了消息响应处理函数。
(2)注册窗口类
- return RegisterClassEx(&wcex);
(3) 添加菜单
- static HMENU hMenu,hMenuPop1,hMenuPop2;
- hMenu = CreateMenu();
- hMenuPop1 = CreateMenu();
- hMenuPop2 = CreateMenu();
- AppendMenu(hMenuPop1, MF_STRING, IDM_TEST, "&测试");
- AppendMenu(hMenuPop2, MF_STRING, IDM_ABOUT, "&关于");
- AppendMenu(hMenuPop2, MF_STRING, IDM_EXIT, "&退出");
- AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPop1, "&菜单1");
- AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPop2, "&菜单2");
(4)创建窗口
- hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, hMenu, hInstance, NULL);
- if (!hWnd)
- {
- return FALSE;
- }
(5)显示和更新窗口
- ShowWindow(hWnd, nCmdShow);
- UpdateWindow(hWnd);
(6)创建消息循环
- while (GetMessage(&msg, NULL, 0, 0))
- {
- if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
获取的消息是虚键消息。TranslateMessage的功能是将虚拟键消息转换为字符消息。DispatchMessage将消息分发给窗口程序。
(7)实现消息响应函数
- #define btn1 1
- #define btn2 2
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int wmId, wmEvent;
- PAINTSTRUCT ps;
- HDC hdc;
- TCHAR szHello[MAX_LOADSTRING];
- LoadString(hInst, IDS_HELLO, szHello, MAX_LOADSTRING);
- switch (message)
- {
- case WM_CREATE:
- {
- //button
- HWND hButton1 = CreateWindow(_T("button"), _T("Btn1"),
- WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 100, 100, 100, 30, hWnd, (HMENU)btn1, hInst, NULL);
- HWND hButton2 = CreateWindow(_T("button"), _T("Btn2"),
- WS_CHILD|WS_VISIBLE|BS_BITMAP, 100, 200, 100, 30, hWnd, (HMENU)btn2, hInst, NULL);
- HBITMAP hbmp=LoadBitmap(hInst,MAKEINTRESOURCE(IDB_Btn2));
- SendMessage(hButton2, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbmp);
- }
- break;
- case WM_COMMAND:
- wmId = LOWORD(wParam);
- wmEvent = HIWORD(wParam);
- // Parse the menu selections:
- switch (wmId)
- {
- case btn1:
- MessageBox(hWnd, _T("Button1 press!"), _T("Message"), MB_OK|MB_ICONINFORMATION);
- break;
- case btn2:
- MessageBox(hWnd, _T("Button2,press!"), _T("Message"), MB_OK|MB_ICONINFORMATION);
- break;
- case IDM_TEST:
- MessageBox(hWnd, _T("菜单测试"), _T("菜单测试"), MB_OK|MB_ICONINFORMATION);
- break;
- case IDM_ABOUT:
- DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
- break;
- case IDM_EXIT:
- DestroyWindow(hWnd);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- break;
- case WM_PAINT:
- hdc = BeginPaint(hWnd, &ps);
- // TODO: Add any drawing code here...
- RECT rt;
- GetClientRect(hWnd, &rt);
- DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
- EndPaint(hWnd, &ps);
- break;
- case WM_DESTROY:
- PostQuitMessage(0);
- break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
- // Mesage handler for about box.
- LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
- {
- switch (message)
- {
- case WM_INITDIALOG:
- return TRUE;
- case WM_COMMAND:
- if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
- {
- EndDialog(hDlg, LOWORD(wParam));
- return TRUE;
- }
- break;
- }
- return FALSE;
在WM_CREATE中添加按钮实现代码,在WM_COMMAND中检测按钮响应事件。
示例代码的实现效果如下图所示:
2 消息响应如何实现
在MFC代码中,没有看到WinMain,也没有看到消息响应函数。但可以发现,利用界面设计器添加的类代码都被包含在一对宏定义中。
- BEGIN_MESSAGE_MAP(CMayMoodDlg, CDialog)
- //{{AFX_MSG_MAP(CMayMoodDlg)
- ON_WM_SYSCOMMAND()
- ON_WM_PAINT()
- ON_WM_QUERYDRAGICON()
- ON_BN_CLICKED(IDC_EXIT, OnExit)
- ON_BN_CLICKED(IDC_BTN_ENCRY, OnBtnEncry)
- ON_BN_CLICKED(IDC_BTN_DECRY, OnBtnDecry)
- ON_BN_CLICKED(IDC_BTN_HELP, OnBtnHelp)
- ON_BN_CLICKED(IDC_BTN_SET, OnBtnSet)
- ON_BN_CLICKED(IDC_BTN_ABOUT, OnBtnAbout)
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
BEGIN_MESSAGE_MAP()宏展开如下:
- #ifdef _AFXDLL
- #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
- const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
- { return &baseClass::messageMap; } \
- const AFX_MSGMAP* theClass::GetMessageMap() const \
- { return &theClass::messageMap; } \
- AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
- { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
- AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
- { \
- #else
- #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
- const AFX_MSGMAP* theClass::GetMessageMap() const \
- { return &theClass::messageMap; } \
- AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
- { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
- AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
- { \
- #endif
END_MESSAGE_MAP()宏展开如下:
- #define END_MESSAGE_MAP() \
- {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
- }; \
实际上,这段代码完成了一件事:
在你的类中生成了一个名为_messageEntries的数组,将消息和此消息对应的处理函数填入该数组中。应用程序框架生成的WindowProc接收到一个消息后,会按照一定的原则轮询个各类(CView、CDocument、CFrameWnd、CWinApp)的messageEntries数组,检查该数组中有没有对应的消息,如果有,就调用相应的响应函数;如果没有,就换下一个类继续检查。当所有的有关的类都被检查完后仍未发现响应函数时,便将此消息丢给DefWindowProc处理。
这其实,说明了一件事:通过上述宏定义完成了对象与消息的连接。这个过程类似于Qt的信号与槽机制。
关于WinMain的解释网上很多,此处不再赘述。