1、首先,创建一个基于对话框的MFC工程(注:我使用的是VS2010):
2、MFC完全兼容Win32 SDK编程,也就是说,直接用系统API是完全可以的。
3、MFC不仅封装了窗口类,还封装了其它的许多类。
4、现在来介绍MFC对话框程序的基本逻辑。
5、可以看到,向导生成的工程有三个类,即CAboutDlg、CxxxApp和CxxxDlg。其中CAboutDlg是关于对话框的,它是独立的,作用就是弹出一个关于对话框,本文就将其忽略了。剩下的就是App类和Dlg类。
6、程序的流程一般有两条线:一是初始化,即程序的执行过程(从何处开始执行,执行函数的调用关系);二是消息响应。
7、消息有两种:一种比较直观,譬如鼠标点击、键盘点击等普通事件的消息;另一种就是定时器消息,这种消息比较隐蔽。
8、实际上,程序定义了一个全局的App,如下图:
9、有了这个App对象后,就会调用初始化函数InitInstance,初始化实例:
// CMFC_TESTApp 初始化
BOOL CMFC_TESTApp::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
AfxEnableControlContainer();
// 创建 shell 管理器,以防对话框包含
// 任何 shell 树视图控件或 shell 列表视图控件。
CShellManager *pShellManager = new CShellManager;
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
CMFC_TESTDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
// TODO: 在此放置处理何时用
// “取消”来关闭对话框的代码
}
// 删除上面创建的 shell 管理器。
if (pShellManager != NULL)
{
delete pShellManager;
}
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
// 而不是启动应用程序的消息泵。
return FALSE;
}
10、开头几行代码,设置对话框程序可以加载一些控件,暂时可以不管;
11、然后调用基类的初始化函数:CWinApp::InitInstance();
12、SetRegistryKey(_T("应用程序向导生成的本地应用程序"));与注册表有关,暂不介绍;
13、接着创建了一个Dlg类的对象:CMFC_TESTDlg dlg;,并将指针赋给了App类下的一个成员变量:m_pMainWnd = &dlg;
14、所以theApp和m_pMainWnd这两个变量很重要,是贯穿整个程序的。
15、全局变量的构造函数比程序的winmain函数先执行。
16、另外,在MFC中winmain函数被封装了,在App和Dlg类中没有WinMain函数,通过断点以及调用堆栈可以看到WinMain函数:
17、我们可以看到,在_tWinMain中其实调用了一个全局的函数AfxWinMain,转到这个函数的定义,实际上定义在一个cpp中,如下:
winmain.cpp
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
#include "stdafx.h"
#include "sal.h"
/
// Standard WinMain implementation
// Can be replaced as long as 'AfxWinInit' is called first
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
/
18、我们创建的CxxApp是派生于CWinApp的,这行代码:CWinApp* pApp = AfxGetApp();其实是用父类的指针指向子类。AfxGetApp返回的就是前面说的那个全局的theApp。
19、接着,pThread->InitInstance(),调用了初始化函数,实际上就是前面贴出的App类的InitInstance函数。也就是基类的一个函数,在子类中实现类,那么就调用子类中的这个函数。
20、接13,创建窗口后调用DoModel显示窗口:INT_PTR nResponse = dlg.DoModal();
21、OK,对话框程序的初始化过程即是如此。
22、总结一下程序的执行流程:
(1)定义一个全局的App对象theApp;
(2)调用CxxxApp的构造函数,初始化theApp;(注:这个构造函数是第一个执行的函数)
(3)接着(第二个执行的函数)就是程序的Main函数了,所有其它的函数都是在Main函数之下调用的;
(4)譬如InitInstance函数都是在WinMain函数之后。
(5)在InitInstance函数中首先调用基类ACinApp的InitInstance函数,接着创建一个CxxxDlg的对象,并将地址赋给一个m_pMainWnd(主窗口的指针变量);
(6)接着,将刚刚创建的CxxxDlg对象DoModel显示出来,即是我们看到的那个对话框 。
23、前面总结的第(5)条只是一掠而过,实际上,CxxxDlg对象的创建,以及创建后的消息响应是十分复杂的。就创建一个CxxxDlg对象来说,必须调用该类的构造函数:
CMFC_TESTDlg::CMFC_TESTDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CMFC_TESTDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
24、加载一个图标:m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
25、CxxxDlg对象构造之后,DoModel执行后会发送第一个消息,调用CxxxDlg类的OnInitDialog函数:
// CMFC_TESTDlg 消息处理程序
BOOL CMFC_TESTDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
26、在MFC中前面带On的函数,要么直接就是消息映射函数,要么就是被消息映射函数所调用的函数,所以都与消息相关。并且很可能是虚函数,也就是说,消息映射在基类中处理了,然后调用子类中的一个函数。
27、如下,是一个系统命令的消息响应函数:
void CMFC_TESTDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
28、接着是一个绘图消息的响应函数:
void CMFC_TESTDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
29、最后,是一个图标拖动的消息响应函数:
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFC_TESTDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}
30、OK,MFC基于对话框的程序基本运行逻辑就是这样。另外,剩下一个最重要的消息循环,因为比较复杂难懂,所以会另写一篇文章。