![cover](https://img-blog.csdnimg.cn/9f0d47aefd394fecbee0925050f00314.png)
MFC基础入门
VS2019中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 <windows.h>),该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现步骤为:
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桌面向导
选择应用程序类型为桌面应用程序,并勾选空项目
注意,添加新建项是.c
文件
# include <windows.h>
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;
}
执行结果
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 <afxwin.h> //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
成功运行
程序执行流程:
- 程序开始时,先实例化应用程序对象(有且只有一个)
- 执行程序的入口函数
InitInstance()
- 给框架类
MyFrame
对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create
创建窗口 - 框架类对象显示窗口
CWnd::ShowWindow
- 框架类对象更新窗口
CWnd::UpdateWindow
- 保存框架类对象指针
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
。
将消息映射添加到一个类中所做的全部工作:
- 所操作类中,声明消息映射宏
- 通过放置标识消息的宏来执行消息映射,相应的类将在对
BEGIN_MESSAGE_MAP
和END_MESSAGE_MAP
的调用之间处理消息 - 对应消息处理函数分别在类中声明,类外定义
mfc.h
#include <afxwin.h> //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);
}
运行结果
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应用
项目创建好后,点击视图
→
\to
→类视图
直接运行,显示窗口
打开任务管理器,可以看见进程
双击类名,即可打开.h
文件
双击类中成员函数,即可打开对应.cpp
文件
文档/视图结构体系
MFC
应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据,把数据本身与它的显示分离开。
数据的存储和加载由文档类来完成,数据的显示和修改则由视类来完成。 MFC
在类CDocument
和CView
中为稳定视图提供了基础结构。CWinApp
、CFrameWnd
和其他类与CDocument
和CView
合作,把所有的片段连在了一起。
CView
类也派生于CWnd
类,框架窗口是视图窗口的一个父窗口。主框架窗口(CFrameWnd
)是整个应用程序外框所包括的部分,即粗框以内的内容,而视类窗口只是主框架中空白的地方。
因此,框架窗口是视窗口的父窗口,那么视类窗口就应该始终覆盖在框架类窗口之上。就好比框架窗口是一面墙,视类窗口就是墙纸,它始终挡在这面墙前边。也就是说,所有操作,包括鼠标单击、鼠标移动等操作都只能由视类窗口捕获。
添加消息处理
在消息列表中找到WM_LBUTTONDOWN
消息
那么文件中会自动发生三处改动
- 头文件中声明消息处理函数
- 对应cpp文件中添加消息宏
- 添加消息处理函数实现
在函数定义中添加如下代码
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框架中一些重要的函数
-
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应用
点击完成,却报错!
依照博客:VS 2019 解决对COM组件的调用返回了错误HRESULT E_FAIL
以管理员身份打开Developer Command Prompt for VS 2019
找到VS2019
安装目录
cd 到文件位置后,输入:
gacutil -i Microsoft.VisualStudio.Shell.Interop.11.0.dll
显示:程序集已成功添加到缓存中,表示已成功!
再依照上述步骤新建基于对话框的MFC应用程序即可。奈何还是无用依旧报错。在网上偶然看见说,项目名称使用全英文,试了下,不报错了。
双击.rc
文件打开资源视图( 菜单栏
→
\to
→视图
→
\to
→其他窗口
→
\to
→资源视图,同样可以打开)
双击第二个选项IDD_CDIALOGTEST_DIALOG
即可看见对话框,也一般称为设计界面,打开菜单栏
→
\to
→视图
→
\to
→工具箱,可以进行选择控件拖拽至对话框中
点击运行便弹出程序窗口
另外,打开类视图,可以看见一共有三个类
类名 | 作用 |
---|---|
CAboutDlg | 版本信息对话框,从CDialogEx继承过来 |
CCDialogTestApp | 应用程序类,从CWinApp继承过来 |
CCDialogTestDlg | 对话框类,从CDialogEx继承过来 |
其中对话框类中有两个重要的方法函数:
DoDataExchange
:该函数主要完成对话框数据的交换和校验OnInitDialog
:相当于对对话框进行初始化处理
2.3 模态对话框
选中对话框,右击属性,可以修改对话框的标题栏等。ctrl a + delete
可选中对话框中所有控件并删除
菜单栏 → \to →视图 → \to →工具箱,拖拽1个按钮进对话框
如何修改按钮的文字?方法一:属性中修改;方法二:直接选中但不双击,直接打字进行修改。(下图为方法一)
接下来,实现点击按钮,弹出一个对话框。
但此时没有多余对话框,咋办?当然先创建出一个对话框:右击Dialog
,点击插入Dialog
修改其ID
为IDD_EXEC
,注意一般为大写
此时,模态对话框是有了。可以在模态对话框中删除默认控件,并添加一个按钮以显示模态对话框弹出。
但是在主对话框中点击按钮是没有反应的。点击模态对话框 → \to →右击 → \to →添加类
如此,类视图中多了一个自定义类CDlgExec
方式一:在主对话框中添加按钮处理函数:创建对话框,并以模态方式运行
方式二:选中模态对话框按钮控件,右击选择添加事件处理程序
方式三:直接双击模态对话框按钮控件(因此,和Qt中双击是修改控件文字的操作是不一样的)
添加头文件
#include "CDlgExec.h"
// 模态对话框按钮点击事件
void CCDialogTestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CDlgExec dlg;
dlg.DoModal(); //以模态方式运行
}
2.4 非模态对话框
与上文类似,先在主对话框中拖入一个按钮,并修改其文字为:非模态对话框
再插入一个对话框,并更改其ID
为IDD_SHOW
,并拖入一个按钮,修改文字:非模态对话框弹出
只有对话框可不行,还得为对话框添加类,CDlgShow
当然,你也就会发现,类试图中多了一个类(其实每个窗口都会对应一个类)
在主对话框中,双击非模态对话框控件,进入该按钮的点击事件处理函数,编写程序
#include "CDlgShow.h"
// 非模态对话框点击事件
void CCDialogTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CDlgShow dlg;
dlg.Create(IDD_SHOW);
dlg.ShowWindow(SW_SHOWNORMAL);
}
运行程序,会发现对话框一闪而过。原因:dlg
是局部变量,函数运行完就会消失。
解决办法:将dlg
变量放在类CCDialogTestDlg
的私有变量中,成为成员变量即可。(记得,添加头文件)
// 非模态对话框点击事件
void CCDialogTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
//CDlgShow dlg;
dlg.Create(IDD_SHOW);
dlg.ShowWindow(SW_SHOWNORMAL);
}
关闭非模态对话框后,当在主对话框中再次点击非模态对话框按钮时,会报错
原因:Create
函数只能创建一次,再次创建就会出错。
解决办法:将创建窗口的语句放在OnInitDialog
下,可以保证只创建一次非模态对话框的窗口
如此一来,在主窗口初始化时便创建了非模态对话框,点击按钮只是显示出来
3 常用控件
3.1 静态文本框 CStaticText
在同一解决方案下继续新建MFC项目,创建好后设定启动项
静态文本框是最简单的控件,它主要用来显示文本信息,不能接受用户输入,一般不需要连接变量,也不需要处理消息。
MFC中的静态文本框等同于Qt中的QLabel
。它与上述按钮控件一样,同样有两种方式修改文本,不再赘述。
再拖两个按钮控件上去
- 点击按钮1,可将静态文本框设置为:呵呵
- 点击按钮2,可弹出显示窗口,显示静态文本框中内容
在Qt
中任何控件都有ObjectName
,但MFC
中不存在,但是MFC
可以关联变量。选中控件,右击
→
\to
→添加变量
由于XXX_STATIC
静态ID
是不能关联变量,故需把ID
修改后,再关联变量
修改ID
后就可以添加为控件添加变量了
修改访问类变量访问权限为private
,添加类变量名称为m_text
,点击完成
创建好控件对应的类变量后,自然可以在按钮1的点击处理函数中编程
// 点击按钮,使得哈哈哈哈变为呵呵
void CCStaticTextTestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
// 设置文本
m_text.SetWindowTextW(TEXT("呵呵"));
}
运行程序
在按钮2的点击处理函数中编程
// 点击按钮,获取文本中内容,并且弹出
void CCStaticTextTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CString str;
m_text.GetWindowTextW(str);
MessageBox(str);
}
再拖入一个静态文本框控件。右击
→
\to
→添加变量,别忘了改一下控件ID
在主对话框类CCStaticTextTestDlg
的OnInitDialog
初始化函数中添加代码
//设置静态控件窗口风格为位图居中显示
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()));
另外,按钮控件不但可以处理点击事件,也可以像静态文本框控件这样去关联变量,并且可以不用修改其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);
}
3.2 编辑框 CEdit
继续新建项目,CEditCtrlTest
,在对话框中拖入控件Edit Control
运行程序后,在编辑框中输入按回车,会自动退出对话框。若是想设置为多行编辑框,右击
→
\to
→属性
另外,该控件可以设置属性中Auto VScroll
和垂直滚动
达到类似txt
上下滚动的效果
若是想再实现一个一模一样的编辑框,按住ctrl
拖动一下即可。在工具箱中拖入两个按钮以实现功能。
为两个编辑框分别添加变量:m_edit1
,m_edit2
当对话框中控件较多时,同时添加变量较多时,该如何方便得查看类中的所有变量呢?右击对话框 → \to →类向导 → \to →成员变量
先在编辑框中添加一下默认文本内容,在OnInitDialog
函数中编写初始化程序
// TODO: 在此添加额外的初始化代码
m_edit1.SetWindowTextW(TEXT("MFC"));
为copy
按钮添加处理事件程序
void CCEditCtrlTestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CString str;
m_edit1.GetWindowText(str);
m_edit2.SetWindowTextW(str);
}
为close
按钮添加事件处理程序
void CCEditCtrlTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
// 退出程序
exit(0);
}
但是一般而言,我们只希望关闭当前对话框,不希望关闭整个程序
void CCEditCtrlTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
// 退出当前对话框
CDialog::OnOK();
}
再回过头解决一下小bug
:单行编辑框点击回车就退出的问题。
解决办法:重写OnOk
,注释掉其中代码
为了介绍编辑框另一个使用方法——关联基本类型变量,再拖入一个编辑框和两个按钮。并为编辑框添加变量,但此时注意,类别选择Value
添加设置内容控件的点击处理函数
void CCEditCtrlTestDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
m_text = L"hhh";
UpdateData(FALSE);
}
添加获取内容控件的点击处理函数
void CCEditCtrlTestDlg::OnBnClickedButton4()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
MessageBox(m_text);
}
3.4 组合框 CComboBox
继续在同一解决方案下创建新项目,ComboBoxCtrl
。在对话框中拖入Combo Box
控件,经如下设置后运行程序,可显示下拉框的效果
但是一般在开发中,不会通过属性去设置。组合框常用接口如下:
接口 | 功能 |
---|---|
CComboBox::AddString | 组合框添加一个字符串 |
CComboBox::SetCurSel | 设置当前选择项(当前显示第几项),下标从0开始 |
CComboBox::GetCurSel | 获取组合框中当前选中项的下标 |
CComboBox::GetLBText | 获取指定位置的内容 |
CComboBox::DeleteString | 删除指定位置的字符串 |
CComboBox::InsertString | 在指定位置插入字符串 |
使用接口自然需要关联变量,为下拉框控件关联变量
在主对话框类的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);
组合框常用的事件为:CBN_SELCHANGE
,当选择组合框某一项时,自动触发此事件。
在控件属性中选择该事件,或者双击控件进入事件处理函数
void CComboBoxCtrlDlg::OnCbnSelchangeCombo1()
{
// TODO: 在此添加控件通知处理程序代码
int index = m_cbx.GetCurSel();
CString str;
m_cbx.GetLBText(index, str);
MessageBox(str);
}
3.5 列表控件 CListCtrl
继续新建项目,ListCtrl
。在工具箱中拖入一个列表控件,并设置属性:view
→
\to
→Report
添加变量
列表控件常用接口如下
接口 | 功能 |
---|---|
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]);
}
可以通过代码继续设置列表属性
m_list.SetExtendedStyle(m_list.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
3.6 树控件 CTreeCtrl
继续新建MFC
应用,CTreeCtrl
。在主对话框中拖入控件CTreeCtrl
,先设置一下属性
接着给树控件关联变量
该控件常用接口如下
接口 | 功能 |
---|---|
AfxGetApp() | 获取应用程序对象指针 |
CWinApp::LoadIcon | 加载自定义图标 |
CImageList::Create | 创建图像列表 |
CImageList::Add | 图像列表追加图标 |
CTreeCtrl::SetImageList | 设置图形状态列表 |
CTreeCtrl::InsertItem | 插入节点 |
CTreeCtrl::SelectItem | 设置默认选中项 |
CTreeCtrl::GetSelectedItem | 获取选中项 |
CTreeCtrl::GetItemText | 获取某项内容 |
编程前的准备工作:把ico资源文件放在项目res文件夹中
将ico
资源文件放好位置后,在资源视图
→
\to
→Icon
→
\to
→添加资源。注意,VS2019
需要将资源文件改成.bmp
后缀的才可以进行添加
在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);
树控件常用事件为:TVN_SELCHANGED
,当选择某个节点时,自动触发此事件
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);
}
3.7 标签页的使用
新建MFC
项目,CtabCtrl
。将默认控件删除,并拖入CTabCtrl
控件。
把 TabSheet.h
和TabSheet.cpp
放在项目文件同级目录,并且添加到工程目录中。
给ui
上 Tab Control
关联Control
类型(CTabSheet
)
两个标签页都需要各自添加对话框,并设置相应属性
自定义类:点击对话框模板
→
\to
→ 右击
→
\to
→ 添加类(MyDlg1
、MyDlg2
)
主对话框类中,定义自定义类对象,需要相应头文件
主对话框类中 OnInitDialog()
做初始化工作
// TODO: 在此添加额外的初始化代码
m_tab.AddPage(TEXT("系统管理"), &dlg1, IDD_DIALOG1);
m_tab.AddPage(TEXT("系统设置"), &dlg2, IDD_DIALOG2);
m_tab.Show();
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)