孙鑫VC++深入详解第三章学习笔记

第三章

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.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贾宝玉怒撞不周山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值