第三章
3.1创建MFC AppWizard
如何利用vs2019创建MFC应用见参考文献[1]
需要注意的地方有
[1] 创建MFC单文档应用程序
[2]开启类视图窗口
3.2基于MFC的程序框架剖析
在MFC中,类的命名都以C开头;对于单文档应用程序,都有:
- CAboutDlg帮助类,同于说明这个工程的开发信息;
- CMainFrame主框架类;
- C工程名App应用程序入口;
- C工程名Doc文档类,用来管理、存放数据;
- C工程名View用来将文档中的数据可视化。
CMainFrame类和CTestView类都有一个共同的基类:Cwnd类,其封装了与窗口相关的操作。
3.2.1 MFC中的WinMain函数
文件路径(在安装路径下直接搜索MFC,找到mfc):D:\Program Files (x86)\visualstudio\VC\Tools\MSVC\14.29.30037\atlmfc\src,打开appmodul.cpp 查找WinMain
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, //右键_tWinMain,单击转到定义:#define _tWinMain WinMain
_In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
3.2.1.1 theApp全局对象
以简单的C++源程序为例,在入口函数main()加载之前,就已经为全局变量(对象)分配了内存空间,并为其赋了初值。对于一个全局对象来说,此时就会调用该对象的构造函数构造该对象并进行初始化操作,然后才是进入main()函数。(P72例3-4已标出先后顺序)
对于MFC来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类;每个MFC程序实例有且仅有一个该派生类的实例化对象,即theApp全局对象,theApp表示了该应用程序本身。theApp的定义如程序3.1所示,在test.cpp中查看。
程序3.1 theApp全局对象
/*test.cpp*/
// 唯一的 CtestApp 对象
CtestApp theApp; //theApp是CtestApp的一个对象,注意其是一个全局对象
/*test.h*/
class CtestApp : public CWinApp //CtestApp继承于CWinApp,后者表示应用程序类
{
......
}
文件路径(在安装路径下直接搜索MFC,找到mfc):D:\Program Files (x86)\visualstudio\VC\Tools\MSVC\14.29.30037\atlmfc\src,打开appcore.cpp 查找CWinApp(184行)
其中,
程序3.2
CWinApp::CWinApp(LPCTSTR lpszAppName) //注意此处有参数
{ ...
pModuleState->m_pCurrentWinApp = this; //此处this代表子类CTestApp的对象,即theApp
...
}
程序3.3
class CWinApp : public CWinThread
{...
explicit CWinApp(LPCTSTR lpszAppName = NULL); // app name defaults to EXE name;构造函数形参有默认值默认值
...
};
补充:如果某个函数的参数有默认值,那么在调用该函数时可以传参,也可以不传参直接使用默认值。
由程序3.3可见,来CWinApp类的定义时,CWinApp的构造函数的形参有默认值NULL。因此,在调用CWinApp类的构造函数时,不用显式地传参。
3.2.1.2 AfxWinMain函数
WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
其中,Afx前缀的函数代表应用程序框架函数,辅助我们生成应用程序框架的应用模型。在MFC中,Afx为前缀的函数都是全局函数,可以在程序的任何位置调用。
在AfxWinMain函数的定义中,有
程序3.4
/*pThread和pApp这两个指针是一致的,这两个指针都指向CTestApp类的对象,即theApp全局对象*/
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
/*MFC内部管理所调用的函数*/
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
/*调用的是子类InitInstance():因为在父类CWinApp中的InitInstance()是虚函数*/
if (!pThread->InitInstance()){}
/*消息循环*/
nReturnCode = pThread->Run();
3.2.1.3 InitInstance函数
见程序3.4的第九行。
3.2.2 MFC框架窗口
3.2.2.1 设计和注册窗口
窗口类的注册是由wincore.cpp的AfxEndDeferRegisterClass函数完成的。AfxEndDeferRegisterClass函数预定义了几种缺省的窗口类,首先判断窗口类的类型,再赋予其相应的类名。部分代码如书本p79所示。
接着调用AfxRegisterClass函数注册从窗口类,该函数首先获得窗口类信息,窗口已经注册,返回真。否则注册该窗口类。
注意:AfxRegisterClass实际上就是AfxEndDeferRegisterClass(宏定义);
3.2.2.2 创建窗口
窗口的创建是由CWnd类中的CreateEx函数完成的。定义:afxwin.h,实现:wincore.cpp。(以Ex结尾的函数表示扩展函数。)
CreateEx函数不是虚函数,CFrameWnd类的Create函数内调用的实际上就是CWnd类的CreatEx函数。
CreateEx函数内部调用的PreCreateWindow函数是一个虚函数,在产生窗口之前有机会修改窗口外观。
3.2.2.3 显示和更新窗口
CTestApp中名为m_pMainWnd的成员变量保存了应用程序框架窗口(CMainFrame)对象的指针,在InitInstance函数(初始化工作:注册、显示、更新)内部:
程序3.5
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);//显示
m_pMainWnd->UpdateWindow();//更新
3.2.3 消息循环
见程序3.4的第12行。
在thrdcore.cpp中/*消息循环*/ nReturnCode = pThread->Run();
书本p85例3-16,该函数主要结构是一个for循环,在收到WM_QUIT时退出。在循环中的PumpMessage()与第一章的SDK编程的消息处理代码一致。
3.2.4 MFC运行过程梳理
3.3 窗口类、窗口对象与窗口
3.3.1 三者之间的关系
:: 前面没有东西,表示所使用的函数是一个全局函数。如果当前定义的成员函数与内部调用的API函数重名,后者必须加 :: ,否则报错。
C++窗口类对象与窗口并不是一回事,他们之间唯一的关系是C++窗口类对象内部定义了一个窗口句柄变量,保存了与这个C++窗口类对象相关的那个窗口的句柄。窗口销毁时,与之对应的C++窗口类对象销毁与否要看其生命周期是否结束。但C++窗口类对象销毁时,与之相关的窗口也将销毁。
换句话说,在窗口销毁后,CWnd的成员变量m_hWnd设为NULL,并没有被销毁(也有可能被销毁:对象生命周期结束(函数运行到右大括号“}”));而在C++窗口类对象析构时,窗口被销毁。
在系统文件afxwin.h中,CWnd已有一个用于保存句柄的成员变量m_hWnd,ShowWindow()和UpdateWindow()不需要再传递这个句柄,因为它已经是成员变量。
程序3.6
/*afxwin.h*/
class CWnd : public CCmdTarget
{
DECLARE_DYNCREATE(CWnd)
protected:
static const MSG* PASCAL GetCurrentMessage();
// Attributes
public:
HWND m_hWnd; // must be first data member
......
};
/*lesson3:\\wainmain.cpp*/
int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
//设计窗口类
...
//注册窗口类
...
//创建窗口类
CWnd wnd;
wnd.CreateEx(...);
wnd.ShowWindow(SW_SHOWNORMAL);
wnd.UpdateWindow();
/*
对比第一章:
创建窗口
HWND hwnd;
hwnd = CreateWindowEx();
显示及刷新窗口
::ShowWindow(hwnd, SW_SHOWNORMAL);
::UpdateWindow(hwnd);
注意ShowWindow和UpdateWindow的参数,原因是:CWnd类定义过了一个HWND类型的成员变量m_hWnd用于保存这个窗口的句柄,
在调用CWnd类中的ShowWindow显示窗口时,就不在需要传递这个句柄了,因为它已经是成员变量了,该函数可以直接使用它。
在窗口销毁后,CWnd的成员变量m_hWnd设为NULL,并没有被销毁;而在C++窗口类对象析构时,窗口被销毁。
*/
//消息循环
...
return 0;
}
3.3.2 在窗口中显示按钮
CButton的Create函数声明:
BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
- lpszCaption:按钮文本;
- dwStyle:按钮风格+窗口风格;
- rect:定义一个矩形区域;
- pParentWnd:指定父窗口。MFC不再通过窗口句柄,而是通过一个与窗口相关的C++窗口类对象指针来传递窗口对象。
- nID:按钮控件标识。可取整数随机值。在框架窗口产生之后,再创建该标识,否则没地方放置。
如果将按钮创建在CMainFrame::OnCreate()函数内,按钮的父窗口是主框架窗口,此时按钮遮挡住了保存等按钮。
改为在CTestView.cpp中创建button,首先在testView.cpp中创建OnCreate函数,步骤如问题及反思[3]所示。运行结果如下。
而将m_btn.Create()中的this改为GetParent(),运行结果变为
可见,按钮的位置与其父窗口有关,与创建它的代码所在的类无关。
将按钮窗口销毁,m_btn并没有销毁,因为m_btn是CTestView类的一个成员变量,其生命周期与CTestView对象一致。
课后程序
/*testview.h*/
class CtestView : public CView
{
...
private:
CButton m_btn; //在定义类的成员变量时都以"m_"为前缀,表明这个变量是类的成员变量
};
/*testview.cpp*/
int CtestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
/*CButton的Create函数声明:BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );*/
// m_btn.Create(_T("button"), WS_CHILD | BS_DEFPUSHBUTTON, CRect(0, 0, 100, 100), this, 123);
// m_btn.ShowWindow(SW_SHOWNORMAL);//窗口显示
m_btn.Create(_T("button"), WS_CHILD | BS_DEFPUSHBUTTON |WS_VISIBLE , CRect(0, 0, 100, 100), this, 123);
// m_btn.Create(_T("button"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, CRect(0, 0, 100, 100), GetParent(), 123);
/*
"按钮":名称; CRect(0,0,100,100):矩形区域; 123:ID号。
*
WS_CHILD(窗口风格):The window is a child window. A window with this style cannot have a menu bar.
BS_DEFPUSHBUTTON(按钮风格):下按按钮风格
WS_VISIBLE:The window is initially visible.
*
this指针(代表对象本身)
GetParent():Call this function to get a pointer to a child window's parent window
*/
/*
C2664 “BOOL CButton::Create(LPCTSTR,DWORD,const RECT &,CWnd *,UINT)”: 无法将参数 1 从“const char [7]”转换为“LPCTSTR” test E:\VCProject\Lesson3\test\test\MainFrm.cpp 68
解决方法:
方法1、"button"改为_T("button")[2]
方法2、调试>>XXX调试属性>>配置属性>>高级>>高级属性>>字符集,改为:使用多字节字符集[3]
*/
return 0;
}
运行结果:
问题及反思
[1]如何利用vs2019创建MFC应用见参考文献[1]
[2]C2664 “BOOL CButton::Create(LPCTSTR,DWORD,const RECT &,CWnd *,UINT)”: 无法将参数 1 从“const char [7]”转换为“LPCTSTR”
解决方法:
方法1、"button"改为_T(“button”)
方法2、调试>>XXX调试属性>>配置属性>>高级>>高级属性>>字符集,改为:使用多字节字符集
[3]vs2019为一个类添加某函数的方法如下所示。第四步单击最右侧向下小三角,选择Add OnCreate。
参考文献
[1] <https://blog.csdn.net/m0_37062716/article/details/113827243?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control >.安装MFC,创建MFC工程文件
[2]<https://blog.csdn.net/huijie4728/article/details/50487315> . 问题及反思[2]
[3]<https://blog.csdn.net/feilong911hao/article/details/39231533?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control> . 问题及反思[2]
[4]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 63-99.