MFC基础入门

1 MFC入门

1.1 为什么学习MFC

Windows平台上做GUI开发,MFC是一个不错的选择。

学习MFC不仅可以学习到MFC本身,而且可以学习MFC框架的设计思想。

1.2 Windows消息机制

基本概念解释

SDK:软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。

API函数:Windows操作系统提供给应用程序编程的接口。Windows应用程序API是通过C语言实现的,所有主要的Windows函数都在Windows.h头文件中进行了声明。Windows操作系统提供了1000多种API函数。

窗口:一个Windows应用程序至少有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。一个应用程序窗口通常都包含标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条。窗口可分为客户区和非客户区。窗口可以有一个父窗口,有父窗口的称为子窗口。

在Windows应用程序中,窗口是通过窗口句柄来标识的。要对某个窗口进行操作,首先就要得到这个窗口的句柄。

句柄:在Windows程序中,有各种各样的资源(窗口、图标、光标、画刷等),系统在创建这些资源时会为它们分配内存,并返回这些资源的标识号,即句柄。例如,有图标句柄、光标句柄、画刷句柄。

消息与消息队列:Windows程序设计是一种完全不同于传统DOS方式的程序设计方法。它是一种事件驱动方式的程序设计模式,主要基于消息。

每一个Windows应用程序开始执行后,系统会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。

例如,用户在窗口中画图时,按下鼠标左键。此时,操作系统会感知到这一事件,于是将该事件包装成一个消息,投递到应用程序的消息队列中,等待应用程序的处理。

然后,应用程序通过一个消息循环不断从消息队列中取出消息,并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程。

WinMain函数:当Windows操作系统启动一个程序时,它调用的是该程序的WinMain函数(实际上是由插入到可执行文件中的启动代码调用的)。Winmain是Windows程序的入口点函数,与DOS程序的入口点函数main作用相当,当WinMain函数结束或返回时,Windows应用程序结束。

Windows编程模型

一个简单但完整Win32程序(#include ),该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现步骤为:

  • WinMain函数的定义
int WINAPI WinMain(
	HINSTANCE hInstance,    // 应用程序实例句柄
	HINSTANCE hPrevInstace, // 上一个应用程序实例,在win32环境中,默认为NULL
	LPSTR lpCmdLine,        // 命令行参数 char * argv[]
	int nShowCmd            // 窗口显示样式
)
  • 创建一个窗口
// 1.设计窗口
WNDCLASS wc;                                            // 窗口类变量
wc.cbClsExtra = 0;                                      // 类的额外内存
wc.cbWndExtra = 0;                                      // 窗口的额外内存
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置背景
wc.hCursor = LoadCursor(NULL, IDC_HAND);                // 设置光标,若第一个参数为NULL,代表使用系统提供的光标
wc.hIcon = LoadIcon(NULL, IDI_ERROR);                   // 设置图标,若第一个参数为NULL,代表使用系统提供的光标
wc.hInstance = hInstance;                               // 应用程序实例句柄,为WinMain第1个形参
wc.lpfnWndProc = WinProc;	                            // 回调函数 窗口过程函数名字
wc.lpszClassName = TEXT("MyWin");	                    // 类的名字
wc.lpszMenuName = NULL;	                                // 没有菜单
wc.style = 0;	                                        // 显示风格,填0,使用默认风格

// 2.注册窗口类
RegisterClass(&wc);

// 3.创建窗口
HWND  hWnd = CreateWindow(TEXT("MyWin"), TEXT("测试"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

// 4.显示及更新
ShowWindow(hWnd, SW_SHOWNORMAL);

UpdateWindow(hWnd);
HWND CreateWindow(
	LPCTSTR lpClassName,   //类名
	LPCTSTR lpWindowName,  //标题名
	DWORD dwStyle,         //指定创建的窗口的样式 WS_OVERLAPPEDWINDOW
	int x,                 //默认值 CW_USEDEFAULT
	int y,                 //指定窗口左上角的x,y坐标
    int nWidth,			   //默认值 CW_USEDEFAULT
	int nHeight,           //指定窗口的宽度,高度
	HWND hWndParent,       //指定被创建窗口的父窗口句柄
	HMENU hMenu,           //指定窗口菜单的句柄
	HINSTANCE hInstance,   //窗口所属的应用程序实例的句柄
	LPVOID lpParam);       //通常设置为NULL
  • 建立消息循环
// 5.通过循环取消息
MSG msg;

while (1) {

	if (GetMessage(&msg, NULL, 0, 0) == FALSE) {

		break;
	}

	else {
		TranslateMessage(&msg);   //翻译
		DispatchMessage(&msg);    //把收到的消息传到窗口回调函数进行分析和处理
	}
}
/*
 * Message structure
 */
typedef struct tagMSG {
    HWND        hwnd;    //消息所属窗口
    UINT        message; //消息标识符
    WPARAM      wParam;  //指定消息的附加信息
    LPARAM      lParam;  //指定消息的附加信息
    DWORD       time;    //标识一个消息产生时的时间
    POINT       pt;      //表示产生这个消息时光标或鼠标的坐标
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
BOOL GetMessage(
	LPMSG lpMsg,        //指向一个消息结构体
	HWND hWnd,          //指定接收哪一个窗口的消息。设置为NULL,用于接收属于调用线程的所有窗口的窗口消息
	UINT wMsgFilterMin, //指定消息的最小值
	UINT wMsgFilterMax);//指定消息的最大值
	//如果wMsgFilterMin和wMsgFilterMax都设置为0, 则接收所有消息。
  • 编写窗口过程函数
LRESULT CALLBACK WinProc( // CALLBACK和WINAPI 作用一样
	HWND hWnd,			  // 信息所属的窗口句柄
	UINT uMsg,		      // 消息类型
	WPARAM wParam,	      // 附加信息(如键盘哪个键按下)
	LPARAM lParam	      // 附加信息(如鼠标点击坐标)
);

完整示例代码

要创建一个win32项目,在VS2019中点击Windows桌面向导

MFC基础入门_第1张图片
MFC基础入门_第2张图片
选择应用程序类型为桌面应用程序,并勾选空项目

MFC基础入门_第3张图片
注意,添加新建项是.c文件

MFC基础入门_第4张图片

# include 

LRESULT CALLBACK WinProc( // CALLBACK和WINAPI 作用一样
	HWND hWnd,		      // 消息所属的窗口句柄
	UINT uMsg,		      // 具体消息名称
	WPARAM wParam,	      // 附加信息(如键盘哪个键按下)
	LPARAM lParam	      // 附加信息(如鼠标点击坐标)
)
{
	switch (uMsg)
	{
		case WM_KEYDOWN:         //键盘按下
			MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘"), MB_OK);
			break;
		case WM_CLOSE:
			//所有以Window结尾的方法不会进入到消息队列去,而是直接执行
			DestroyWindow(hWnd); // DestroyWindow 发送另一个消息 WM_DESTROY
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_LBUTTONDOWN:     //鼠标左键按下
		{
			int xPos = LOWORD(lParam);
			int yPos = HIWORD(lParam);

			char buf[1024];
			wsprintf(buf, TEXT("x = %d, y = %d"), xPos, yPos);
			MessageBox(hWnd, buf, TEXT("鼠标左键按下"), MB_OK);
			break;
		}
		case WM_PAINT:          //绘图事件
		{
			PAINTSTRUCT ps;     //绘图结构体
			HDC hdc = BeginPaint(hWnd, &ps);

			TextOut(hdc, 100, 100, TEXT("HELLO!"), strlen("HELLO"));

			EndPaint(hWnd, &ps);
			break;
		}
		default:
			//以windows默认方式处理
			return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}


// 程序入口函数
// #define WINAPI  __stdcall 参数的传递顺序:从右到左依次入栈,并在函数返回前清空堆栈
int WINAPI WinMain(
	HINSTANCE hInstance,    // 应用程序实例句柄
	HINSTANCE hPrevInstace, // 上一个应用程序实例,在win32环境中,默认为NULL
	LPSTR lpCmdLine,        // 命令行参数 char * argv[]
	int nShowCmd            // 窗口显示样式
)

{
	// 1.设计窗口
	WNDCLASS wc;                                            // 窗口类变量
	wc.cbClsExtra = 0;                                      // 类的额外内存
	wc.cbWndExtra = 0;                                      // 窗口的额外内存
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置背景
	wc.hCursor = LoadCursor(NULL, IDC_HAND);                // 设置光标,若第一个参数为NULL,代表使用系统提供的光标
	wc.hIcon = LoadIcon(NULL, IDI_ERROR);                   // 设置图标,若第一个参数为NULL,代表使用系统提供的光标
	wc.hInstance = hInstance;                               // 应用程序实例句柄,为WinMain第1个形参
	wc.lpfnWndProc = WinProc;	                            // 回调函数 窗口过程函数名字
	wc.lpszClassName = TEXT("MyWin");	                    // 类的名字
	wc.lpszMenuName = NULL;	                                // 没有菜单
	wc.style = 0;	                                        // 显示风格,填0,使用默认风格

	// 2.注册窗口类
	RegisterClass(&wc);

	// 3.创建窗口
	HWND  hWnd = CreateWindow(TEXT("MyWin"), TEXT("测试"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

	// 4.显示及更新
	ShowWindow(hWnd, SW_SHOWNORMAL);

	UpdateWindow(hWnd);

	// 5.通过循环取消息
	MSG msg;

	while (1) {

		if (GetMessage(&msg, NULL, 0, 0) == FALSE) {

			break;
		}

		else {
			TranslateMessage(&msg);   //翻译
			DispatchMessage(&msg);    //把收到的消息传到窗口回调函数进行分析和处理
		}
	}

	return 0;
}

执行结果

MFC基础入门_第5张图片

1.3 MFC入门

微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
MFC把Windows SDK API函数包装成了几百个类,MFC给Windows操作系统提供了面向对象的接口,支持可重用性、自包含性以及其他OPP原则。MFC通过编写类来封装窗口、对话框以及其他对象,引入某些关键的虚函数(覆盖这些虚函数可以改变派生类的功能)来完成,并且MFC设计者使类库带来的总开销降到了最低。

编写第一个MFC应用

mfc.h

#include  //mfc头文件

class MyApp: public CWinApp // CWinApp应用程序类
{
public:
	virtual BOOL InitInstance();

};

class MyFrame : public CFrameWnd
{
public:
	MyFrame();

};

mfc.cpp

#include "mfc.h"

MyApp app;                            // 有且只有一个全局的应用程序类对象

BOOL MyApp::InitInstance()            // 程序入口地址
{
	MyFrame* frame = new MyFrame;     // 1.创建框架类对象

	frame->ShowWindow(SW_SHOWNORMAL); // 2.显示窗口
	frame->UpdateWindow();            // 3.更新窗口

	m_pMainWnd = frame;               // 4.保存框架类对象指针(保存指向应用程序的主窗口的指针)

	return TRUE;                      // 初始化正常返回TRUE
}

MyFrame::MyFrame()
{
	Create(NULL, TEXT("mfc"));
}

编译项目,报错

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\include\afx.h(24,1): fatal error C1189: #error:  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or do not use /MD[d]

解决方法:打开项目属性页,在MFC的使用中选择在共享DLL中使用MFC

MFC基础入门_第6张图片
成功运行

MFC基础入门_第7张图片

程序执行流程:

  1. 程序开始时,先实例化应用程序对象(有且只有一个)
  2. 执行程序的入口函数InitInstance()
  3. 给框架类MyFrame对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create创建窗口
  4. 框架类对象显示窗口CWnd::ShowWindow
  5. 框架类对象更新窗口CWnd::UpdateWindow
  6. 保存框架类对象指针CWinThread::m_pMainWnd
CFrameWnd::Create

BOOL Create(
     LPCTSTR lpszClassName,                 // 如果为NULL,使用预定义的缺省CFrameWnd属性
     LPCTSTR lpszWindowName,                // 指向代表窗口名的以空终止的字符串,用作标题条的文本。
     DWORD dwStyle = WS_OVERLAPPEDWINDOW,   // 指定窗口风格属性
     const RECT &rect = rectDefault,        // 定义窗口大小和位置
     CWnd* pParentWnd = NULL,               // 指定框架窗口的父窗口,对最高层框架窗口来说应为NULL
     LPCTSTR lpszMenuName = NULL,           // 指定与窗口一起使用的菜单资源名
     DWORD dwExStyle = 0,                   // 指定窗口扩展的风格属性
     CCreateContext* pContext = NULL        // 指向CCreateContext结构的指针
     );
CWnd::ShowWindow

BOOL ShowWindow( int nCmdShow );

//SW_SHOWNORMAL 激活并显示窗口。如果窗口是最小化或最大化的,则Windows恢复它原来的大小和位置。 
//返回值:如果窗口原来可见,则返回非零值;如果CWnd原来是隐藏的,则返回0。

消息映射机制

消息映射是一个将消息和成员函数相互关联的表。比如,框架窗口接收到一个鼠标左击消息,MFC将搜索该窗口的消息映射,如果存在一个处理WM_LBUTTONDOWN消息的处理程序,然后就调用OnLButtonDown

将消息映射添加到一个类中所做的全部工作:

  1. 所操作类中,声明消息映射宏
  2. 通过放置标识消息的宏来执行消息映射,相应的类将在对BEGIN_MESSAGE_MAPEND_MESSAGE_MAP的调用之间处理消息
  3. 对应消息处理函数分别在类中声明,类外定义

mfc.h

#include  //mfc头文件

class MyApp: public CWinApp // CWinApp应用程序类
{
public:
	virtual BOOL InitInstance();

};

class MyFrame : public CFrameWnd
{
public:
	MyFrame();

	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnChar(UINT, UINT, UINT);
	afx_msg void OnPaint();

	// 声明宏 提供消息映射机制
	DECLARE_MESSAGE_MAP()
};

mfc.cpp

#include "mfc.h"

MyApp app;                            // 有且只有一个全局的应用程序类对象

BOOL MyApp::InitInstance()            // 程序入口地址
{
	MyFrame* frame = new MyFrame;     // 1.创建框架类对象

	frame->ShowWindow(SW_SHOWNORMAL); // 2.显示窗口
	frame->UpdateWindow();            // 3.更新窗口

	m_pMainWnd = frame;               // 4.保存框架类对象指针(保存指向应用程序的主窗口的指针)

	return TRUE;                      // 初始化正常返回TRUE
}

BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd) // 开始

	ON_WM_LBUTTONDOWN()
	ON_WM_CHAR()
	ON_WM_PAINT()

END_MESSAGE_MAP()                     // 结束


MyFrame::MyFrame()
{
	Create(NULL, TEXT("mfc"));
}

void MyFrame::OnLButtonDown(UINT, CPoint point) {

	//TCHAR buf[1024];
	//wsprintf(buf, TEXT("x = %d, y = %d"), point.x, point.y);

	//MessageBox(buf);

	// MFC中使用字符串 CStirng
	CString str;
	str.Format(TEXT("x = %d, y = %d"), point.x, point.y);

	MessageBox(str);
}

void MyFrame::OnChar(UINT key, UINT, UINT) {

	CString str;
	str.Format(TEXT("按下了%c键"), key);

	MessageBox(str);
}

void MyFrame::OnPaint() {

	CPaintDC dc(this);

	dc.TextOutW(100, 100, CString("NJ"));
	// 画椭圆
	dc.Ellipse(100, 100, 150, 150);
}

运行结果

MFC基础入门_第8张图片

Windows字符集

  • 多字节字符集(8位的ANSI字符集)

在Windows98以及以前的版本使用8位ANSI字符集,它类似于我们程序员熟悉的ASCII字符集。

char sz[] = "ABCDEFG";
char *psz = "ABCDEFG";
int len = strlen(sz);
  • 宽字符集(16位的Unicode字符集)

在WindowsNT和Windows2000后开始使用16位的Unicode字符集,它是ANSI字符集的一个超集。Unicode适用于国际市场销售的应用程序,因为它包含各种各样来自非U.S.字母表的字符,比如中文,日文,韩文,西欧语言等。

//在字符串前加字母L表示将ANSI字符集转换成Unicode字符集。
wchar_t wsz[] = L"ABCDEFG"; 
wchar_t *pwsz = L"ABCDEFG";
int len = wcslen(wsz); //测试宽字节字符串的长度
  • TEXT(_T)宏

MFC中的TEXT宏可以自动适应字符类型,如果定义了预处理器程序符号_UNICODE,那么编译器将使用Unicode字符,如果没用定义该预处理器程序符号,那么编译器将使用ANSI字符。

MessageBox(TEXT("鼠标左键"));
MessageBox(_T("鼠标左键"));
  • TCHAR类型

如果定义了_UNICODE符号TCHAR将变为wchar_t类型。如果没用定义_UNICODE符号,TCHAR将变为普通古老的char类型。

char * 与 CString之间的转换

// char * -> CString
char* p = "ccc";
CString str = CString(p);

// CString -> char *
CStringA tmp;
tmp = str;
char* pp = tep.GetBuffer();

1.4 用向导生成一个MFC应用

用VS2019创建新项目时,选择MFC应用

MFC基础入门_第9张图片
MFC基础入门_第10张图片
MFC基础入门_第11张图片
MFC基础入门_第12张图片
MFC基础入门_第13张图片
MFC基础入门_第14张图片
在这里插入图片描述
项目创建好后,点击视图 → \to 类视图

MFC基础入门_第15张图片
直接运行,显示窗口

MFC基础入门_第16张图片
打开任务管理器,可以看见进程

MFC基础入门_第17张图片
双击类名,即可打开.h文件

MFC基础入门_第18张图片
双击类中成员函数,即可打开对应.cpp文件

MFC基础入门_第19张图片

文档/视图结构体系

MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据,把数据本身与它的显示分离开。

数据的存储和加载由文档类来完成,数据的显示和修改则由视类来完成MFC在类CDocumentCView中为稳定视图提供了基础结构。CWinAppCFrameWnd和其他类与CDocumentCView合作,把所有的片段连在了一起。

CView类也派生于CWnd类,框架窗口是视图窗口的一个父窗口。主框架窗口(CFrameWnd)是整个应用程序外框所包括的部分,即粗框以内的内容,而视类窗口只是主框架中空白的地方。

MFC基础入门_第20张图片

因此,框架窗口是视窗口的父窗口,那么视类窗口就应该始终覆盖在框架类窗口之上。就好比框架窗口是一面墙,视类窗口就是墙纸,它始终挡在这面墙前边。也就是说,所有操作,包括鼠标单击、鼠标移动等操作都只能由视类窗口捕获。

添加消息处理

在消息列表中找到WM_LBUTTONDOWN消息

MFC基础入门_第21张图片
那么文件中会自动发生三处改动

  • 头文件中声明消息处理函数

MFC基础入门_第22张图片

  • 对应cpp文件中添加消息宏

MFC基础入门_第23张图片

  • 添加消息处理函数实现

MFC基础入门_第24张图片

在函数定义中添加如下代码

void CmfcGuideView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CView::OnLButtonDown(nFlags, point);

	CString str;
	str.Format(TEXT("x = %d, y = %d"), point.x, point.y);

	MessageBox(str);
}

可以达到和之前一样的效果

MFC基础入门_第25张图片

MFC框架中一些重要的函数

  • CmfcGuideApp::InitInstance函数:应用程序类的一个虚函数,MFC应用程序的入口

  • CMainFrame::PreCreateWindow函数

当框架调用CreateEx函数创建窗口时,会首先调用PreCreateWindow函数。

通过修改传递给PreCreateWindow的结构体类型参数CREATESTRUCT,应用程序可以更改用于创建窗口的属性。在产生窗口之前让程序员有机会修改窗口的外观

最后再调用CreateWindowEx函数完成窗口的创建。

  • CMainFrame::OnCreate函数

OnCreate是一个消息响应函数,是响应WM_CREATE消息的一个函数,而WM_CREATE消息是由Create函数调用的。一个窗口创建(Create)之后,会向操作系统发送WM_CREATE消息,OnCreate()函数主要是用来响应此消息的。

  • CmfcGuideView::OnDraw函数

通常我们不必编写OnPaint处理函数。当在View类里添加了消息处理OnPaint()时,OnPaint()就会覆盖掉OnDraw()

拓展知识点

  • MFC中后缀名为Ex的函数都是扩展函数
  • MFC中,以Afx为前缀的函数都是全局函数,可以在程序的任何地方调用它们

2 基于对话框编程

2.1 创建基于对话框的MFC应用

MFC基础入门_第26张图片
MFC基础入门_第27张图片
MFC基础入门_第28张图片
MFC基础入门_第29张图片
MFC基础入门_第30张图片
MFC基础入门_第31张图片
MFC基础入门_第32张图片
点击完成,却报错!

MFC基础入门_第33张图片
依照博客:VS 2019 解决对COM组件的调用返回了错误HRESULT E_FAIL

以管理员身份打开Developer Command Prompt for VS 2019

MFC基础入门_第34张图片
找到VS2019安装目录

MFC基础入门_第35张图片
cd 到文件位置后,输入:

gacutil -i Microsoft.VisualStudio.Shell.Interop.11.0.dll

MFC基础入门_第36张图片
显示:程序集已成功添加到缓存中,表示已成功!

再依照上述步骤新建基于对话框的MFC应用程序即可。奈何还是无用依旧报错。在网上偶然看见说,项目名称使用全英文,试了下,不报错了。

MFC基础入门_第37张图片
双击.rc文件打开资源视图( 菜单栏 → \to 视图 → \to 其他窗口 → \to 资源视图,同样可以打开)

MFC基础入门_第38张图片
双击第二个选项IDD_CDIALOGTEST_DIALOG

MFC基础入门_第39张图片
即可看见对话框,也一般称为设计界面,打开菜单栏 → \to 视图 → \to 工具箱,可以进行选择控件拖拽至对话框中

MFC基础入门_第40张图片

点击运行便弹出程序窗口

MFC基础入门_第41张图片

另外,打开类视图,可以看见一共有三个类

类名 作用
CAboutDlg 版本信息对话框,从CDialogEx继承过来
CCDialogTestApp 应用程序类,从CWinApp继承过来
CCDialogTestDlg 对话框类,从CDialogEx继承过来

MFC基础入门_第42张图片
其中对话框类中有两个重要的方法函数

  • DoDataExchange:该函数主要完成对话框数据的交换和校验
  • OnInitDialog:相当于对对话框进行初始化处理

2.3 模态对话框

选中对话框,右击属性,可以修改对话框的标题栏等。ctrl a + delete可选中对话框中所有控件并删除

菜单栏 → \to 视图 → \to 工具箱,拖拽1个按钮进对话框

MFC基础入门_第43张图片

如何修改按钮的文字?方法一:属性中修改;方法二:直接选中但不双击,直接打字进行修改。(下图为方法一)

MFC基础入门_第44张图片

接下来,实现点击按钮,弹出一个对话框

但此时没有多余对话框,咋办?当然先创建出一个对话框:右击Dialog,点击插入Dialog

MFC基础入门_第45张图片
修改其IDIDD_EXEC,注意一般为大写

MFC基础入门_第46张图片
此时,模态对话框是有了。可以在模态对话框中删除默认控件,并添加一个按钮以显示模态对话框弹出。

但是在主对话框中点击按钮是没有反应的。点击模态对话框 → \to 右击 → \to 添加类

MFC基础入门_第47张图片

MFC基础入门_第48张图片
如此,类视图中多了一个自定义类CDlgExec

MFC基础入门_第49张图片
方式一:在主对话框中添加按钮处理函数:创建对话框,并以模态方式运行

MFC基础入门_第50张图片
方式二:选中模态对话框按钮控件,右击选择添加事件处理程序

MFC基础入门_第51张图片
方式三:直接双击模态对话框按钮控件(因此,和Qt中双击是修改控件文字的操作是不一样的)

添加头文件

#include "CDlgExec.h"
// 模态对话框按钮点击事件
void CCDialogTestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码

	CDlgExec dlg;
	dlg.DoModal(); //以模态方式运行
}

MFC基础入门_第52张图片

2.4 非模态对话框

与上文类似,先在主对话框中拖入一个按钮,并修改其文字为:非模态对话框

再插入一个对话框,并更改其IDIDD_SHOW,并拖入一个按钮,修改文字:非模态对话框弹出

只有对话框可不行,还得为对话框添加类,CDlgShow

MFC基础入门_第53张图片
当然,你也就会发现,类试图中多了一个类(其实每个窗口都会对应一个类)

在这里插入图片描述
在主对话框中,双击非模态对话框控件,进入该按钮的点击事件处理函数,编写程序

#include "CDlgShow.h"
// 非模态对话框点击事件
void CCDialogTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码

	CDlgShow dlg;
	dlg.Create(IDD_SHOW);
	dlg.ShowWindow(SW_SHOWNORMAL);
}

运行程序,会发现对话框一闪而过。原因:dlg是局部变量,函数运行完就会消失。

解决办法:将dlg变量放在类CCDialogTestDlg的私有变量中,成为成员变量即可。(记得,添加头文件)

MFC基础入门_第54张图片

// 非模态对话框点击事件
void CCDialogTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码

	//CDlgShow dlg;
	dlg.Create(IDD_SHOW);
	dlg.ShowWindow(SW_SHOWNORMAL);
}

MFC基础入门_第55张图片
关闭非模态对话框后,当在主对话框中再次点击非模态对话框按钮时,会报错

MFC基础入门_第56张图片
原因:Create函数只能创建一次,再次创建就会出错。

解决办法:将创建窗口的语句放在OnInitDialog下,可以保证只创建一次非模态对话框的窗口

MFC基础入门_第57张图片
如此一来,在主窗口初始化时便创建了非模态对话框,点击按钮只是显示出来

3 常用控件

3.1 静态文本框 CStaticText

在同一解决方案下继续新建MFC项目,创建好后设定启动项

MFC基础入门_第58张图片
MFC基础入门_第59张图片
静态文本框是最简单的控件,它主要用来显示文本信息,不能接受用户输入,一般不需要连接变量,也不需要处理消息。

MFC中的静态文本框等同于Qt中的QLabel。它与上述按钮控件一样,同样有两种方式修改文本,不再赘述。

MFC基础入门_第60张图片
再拖两个按钮控件上去

  • 点击按钮1,可将静态文本框设置为:呵呵
  • 点击按钮2,可弹出显示窗口,显示静态文本框中内容

MFC基础入门_第61张图片
Qt中任何控件都有ObjectName,但MFC中不存在,但是MFC可以关联变量。选中控件,右击 → \to 添加变量

由于XXX_STATIC静态ID是不能关联变量,故需把ID修改后,再关联变量

MFC基础入门_第62张图片
修改ID后就可以添加为控件添加变量了

MFC基础入门_第63张图片
修改访问类变量访问权限为private,添加类变量名称为m_text,点击完成

MFC基础入门_第64张图片
创建好控件对应的类变量后,自然可以在按钮1的点击处理函数中编程

// 点击按钮,使得哈哈哈哈变为呵呵
void CCStaticTextTestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	
	// 设置文本
	m_text.SetWindowTextW(TEXT("呵呵"));
}

运行程序

MFC基础入门_第65张图片
在按钮2的点击处理函数中编程

// 点击按钮,获取文本中内容,并且弹出
void CCStaticTextTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码

	CString str;
	m_text.GetWindowTextW(str);
	MessageBox(str);
}

MFC基础入门_第66张图片
再拖入一个静态文本框控件。右击 → \to 添加变量,别忘了改一下控件ID

在主对话框类CCStaticTextTestDlgOnInitDialog初始化函数中添加代码

//设置静态控件窗口风格为位图居中显示
m_pic.ModifyStyle(0xf, SS_BITMAP | SS_CENTERIMAGE);

//通过路径获取bitmap句柄
#define HBMP(filepath,width,height) (HBITMAP)LoadImage(AfxGetInstanceHandle(),filepath,IMAGE_BITMAP,width,height,LR_LOADFROMFILE|LR_CREATEDIBSECTION)

//宽高设置 按照控件大小设置
CRect rect;
m_pic.GetWindowRect(rect);

//静态控件设置bitmap
m_pic.SetBitmap(HBMP(TEXT("./1.bmp"), rect.Width(), rect.Height()));

MFC基础入门_第67张图片

另外,按钮控件不但可以处理点击事件,也可以像静态文本框控件这样去关联变量,并且可以不用修改其ID。关联之后在窗口类中就存在一个私有变量,从而可以借助该变量去设置按钮的文本、属性等。常用接口如下:

接口 功能
CWnd::SetWindowTextW 设置控件内容
CWnd::GetWindowTextW 获取控件内容
CWnd::EnableWindow 设置控件是否变灰
void CCStaticTextTestDlg::OnBnClickedButton3()
{
	// TODO: 在此添加控件通知处理程序代码

	m_btn.SetWindowTextW(TEXT("o_o"));

	CString str;
	m_btn.GetWindowTextW(str);
	MessageBox(str);

	m_btn.EnableWindow(FALSE);
}

MFC基础入门_第68张图片

3.2 编辑框 CEdit

继续新建项目,CEditCtrlTest,在对话框中拖入控件Edit Control

MFC基础入门_第69张图片
运行程序后,在编辑框中输入按回车,会自动退出对话框。若是想设置为多行编辑框,右击 → \to 属性

MFC基础入门_第70张图片
MFC基础入门_第71张图片
另外,该控件可以设置属性中Auto VScroll垂直滚动达到类似txt上下滚动的效果

MFC基础入门_第72张图片
若是想再实现一个一模一样的编辑框,按住ctrl拖动一下即可。在工具箱中拖入两个按钮以实现功能。

MFC基础入门_第73张图片
为两个编辑框分别添加变量:m_edit1m_edit2

MFC基础入门_第74张图片

当对话框中控件较多时,同时添加变量较多时,该如何方便得查看类中的所有变量呢?右击对话框 → \to 类向导 → \to 成员变量

MFC基础入门_第75张图片

先在编辑框中添加一下默认文本内容,在OnInitDialog函数中编写初始化程序

// TODO: 在此添加额外的初始化代码
m_edit1.SetWindowTextW(TEXT("MFC"));

copy按钮添加处理事件程序

void CCEditCtrlTestDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	CString str;
	m_edit1.GetWindowText(str);
	m_edit2.SetWindowTextW(str);
}

MFC基础入门_第76张图片
close按钮添加事件处理程序

void CCEditCtrlTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码

	// 退出程序
	exit(0);
}

但是一般而言,我们只希望关闭当前对话框,不希望关闭整个程序

void CCEditCtrlTestDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码

	// 退出当前对话框
	CDialog::OnOK();
}

再回过头解决一下小bug:单行编辑框点击回车就退出的问题。

解决办法:重写OnOk,注释掉其中代码

MFC基础入门_第77张图片
为了介绍编辑框另一个使用方法——关联基本类型变量,再拖入一个编辑框和两个按钮。并为编辑框添加变量,但此时注意,类别选择Value

MFC基础入门_第78张图片
添加设置内容控件的点击处理函数

void CCEditCtrlTestDlg::OnBnClickedButton3()
{
	// TODO: 在此添加控件通知处理程序代码

	m_text = L"hhh";
	UpdateData(FALSE);
}

MFC基础入门_第79张图片

添加获取内容控件的点击处理函数

void CCEditCtrlTestDlg::OnBnClickedButton4()
{
	// TODO: 在此添加控件通知处理程序代码
	
	UpdateData(TRUE);
	MessageBox(m_text);
}

MFC基础入门_第80张图片

3.4 组合框 CComboBox

继续在同一解决方案下创建新项目,ComboBoxCtrl。在对话框中拖入Combo Box控件,经如下设置后运行程序,可显示下拉框的效果

MFC基础入门_第81张图片

MFC基础入门_第82张图片
但是一般在开发中,不会通过属性去设置。组合框常用接口如下:

接口 功能
CComboBox::AddString 组合框添加一个字符串
CComboBox::SetCurSel 设置当前选择项(当前显示第几项),下标从0开始
CComboBox::GetCurSel 获取组合框中当前选中项的下标
CComboBox::GetLBText 获取指定位置的内容
CComboBox::DeleteString 删除指定位置的字符串
CComboBox::InsertString 在指定位置插入字符串

使用接口自然需要关联变量,为下拉框控件关联变量

MFC基础入门_第83张图片
在主对话框类的OnInitdialog函数中添加下拉框初始化代码

// TODO: 在此添加额外的初始化代码
m_cbx.AddString(TEXT("韩立"));
m_cbx.AddString(TEXT("墨居仁"));
m_cbx.AddString(TEXT("银月"));
m_cbx.AddString(TEXT("紫灵"));

m_cbx.SetCurSel(2);

m_cbx.InsertString(3, TEXT("小瓶"));

m_cbx.DeleteString(1);

MFC基础入门_第84张图片
组合框常用的事件为:CBN_SELCHANGE,当选择组合框某一项时,自动触发此事件。

在控件属性中选择该事件,或者双击控件进入事件处理函数

void CComboBoxCtrlDlg::OnCbnSelchangeCombo1()
{
	// TODO: 在此添加控件通知处理程序代码

	int index = m_cbx.GetCurSel();

	CString str;
	m_cbx.GetLBText(index, str);
	MessageBox(str);
}

MFC基础入门_第85张图片

3.5 列表控件 CListCtrl

继续新建项目,ListCtrl。在工具箱中拖入一个列表控件,并设置属性:view → \to Report

MFC基础入门_第86张图片
添加变量

MFC基础入门_第87张图片
列表控件常用接口如下

接口 功能
CListCtrl::SetExtendedStyle 设置列表风格
CListCtrl::GetExtendedStyle 获取列表风格
CListCtrl::InsertColumn 插入某列内容,主要用于设置标题
CListCtrl::InsertItem 在某行插入新项内容
CListCtrl::SetItemText 设置某行某列的子项内容
CListCtrl::GetItemText 获取某行某列的内容

去窗口初始化函数中添加初始化代码

// TODO: 在此添加额外的初始化代码

CString str[] = { TEXT("姓名"), TEXT("性别"), TEXT("年龄") };

for (int i = 0; i < 3; i++) {
	// 设置表头 参数1->索引  2->内容  3->对齐方式 4->列宽  
	m_list.InsertColumn(i, str[i], LVCFMT_LEFT, 150);
}

CString name[] = { L"韩立", L"紫灵", L"元瑶", L"冰风", L"巧倩" };
CString gender[] = { L"男", L"女", L"女" , L"女" , L"女" };
CString age[] = { L"24", L"22", L"23" , L"24" , L"25" };

// 设置正文 表头不算正文
for (int i = 0; i < 5; i++) {
	int j = 0;
	m_list.InsertItem(i, name[i]);
	// 设置这个item其他列的数据
	m_list.SetItemText(i, ++j, gender[i]);
	m_list.SetItemText(i, ++j, age[i]);
}

MFC基础入门_第88张图片
可以通过代码继续设置列表属性

m_list.SetExtendedStyle(m_list.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);

MFC基础入门_第89张图片

3.6 树控件 CTreeCtrl

继续新建MFC应用,CTreeCtrl。在主对话框中拖入控件CTreeCtrl,先设置一下属性

MFC基础入门_第90张图片
接着给树控件关联变量

MFC基础入门_第91张图片
该控件常用接口如下

接口 功能
AfxGetApp() 获取应用程序对象指针
CWinApp::LoadIcon 加载自定义图标
CImageList::Create 创建图像列表
CImageList::Add 图像列表追加图标
CTreeCtrl::SetImageList 设置图形状态列表
CTreeCtrl::InsertItem 插入节点
CTreeCtrl::SelectItem 设置默认选中项
CTreeCtrl::GetSelectedItem 获取选中项
CTreeCtrl::GetItemText 获取某项内容

编程前的准备工作:把ico资源文件放在项目res文件夹中

MFC基础入门_第92张图片
ico资源文件放好位置后,在资源视图 → \to Icon → \to 添加资源。注意,VS2019需要将资源文件改成.bmp后缀的才可以进行添加

MFC基础入门_第93张图片

OnInitDialog函数中添加控件初始化代码

// TODO: 在此添加额外的初始化代码

// 树控件使用
// 1.设置图标
// 准备HICON图标
HICON icons[4];
icons[0] = AfxGetApp()->LoadIconW(IDI_ICON1);
icons[1] = AfxGetApp()->LoadIconW(IDI_ICON2);
icons[2] = AfxGetApp()->LoadIconW(IDI_ICON3);
icons[3] = AfxGetApp()->LoadIconW(IDI_ICON4);

// CImageList list; // 这个需要写到类成员变量中,否则出函数变量会被释放
//30, 30: 图片的宽度和高度	ILC_COLOR32:样式	3, 3:有多少图片写多少
list.Create(30, 30, ILC_COLOR32, 4, 4);
// 添加具体图片
for (int i = 0; i < 4; i++) {

	list.Add(icons[i]);
}

m_tree.SetImageList(&list, TVSIL_NORMAL);

// 2.设置节点
HTREEITEM root = m_tree.InsertItem(TEXT("根节点"), 0, 0, NULL);
HTREEITEM parent = m_tree.InsertItem(TEXT("父节点"), 1, 1, root);
HTREEITEM sub1 = m_tree.InsertItem(TEXT("子节点1"), 2, 2, parent);
HTREEITEM sub2 = m_tree.InsertItem(TEXT("子节点2"), 3, 3, parent);

MFC基础入门_第94张图片
树控件常用事件为:TVN_SELCHANGED,当选择某个节点时,自动触发此事件

MFC基础入门_第95张图片

void CCTreeCtrlDlg::OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	*pResult = 0;

	HTREEITEM selItem;
	//获得选择项
	selItem = m_tree.GetSelectedItem();
	//获取选中的内容
	CString cs = m_tree.GetItemText(selItem);
	MessageBox(cs);
}

MFC基础入门_第96张图片

3.7 标签页的使用

新建MFC项目,CtabCtrl。将默认控件删除,并拖入CTabCtrl控件。

TabSheet.hTabSheet.cpp放在项目文件同级目录,并且添加到工程目录中。

MFC基础入门_第97张图片
uiTab Control 关联Control类型(CTabSheet

MFC基础入门_第98张图片

两个标签页都需要各自添加对话框,并设置相应属性

MFC基础入门_第99张图片
MFC基础入门_第100张图片
自定义类:点击对话框模板 → \to 右击 → \to 添加类(MyDlg1MyDlg2)

主对话框类中,定义自定义类对象,需要相应头文件

MFC基础入门_第101张图片
主对话框类中 OnInitDialog() 做初始化工作

// TODO: 在此添加额外的初始化代码

m_tab.AddPage(TEXT("系统管理"), &dlg1, IDD_DIALOG1);
m_tab.AddPage(TEXT("系统设置"), &dlg2, IDD_DIALOG2);

m_tab.Show();

MFC基础入门_第102张图片

你可能感兴趣的:(Qt/MFC,mfc,windows,c++)