MFC简单入门学习 - 指南
1. 基本概念
(1) SDK 和 API
SDK:软件开发工具包(Software Development Kit),第三方写好了写好的东西,花点儿钱你去使用就好了
API: 程序接口(Application Programming Interface),我们工作基本上都是些API
(2)窗口和句柄
窗口是Windows应用程序中非常重要的一个元素,一个Windows应用程序至少要有一个窗口,称为主窗口。
句柄(HANDLE)是Windows应用程序中一个非常重要的概念,在Windows程序中,有各种各样的资源(窗口、图标、光标、画刷),系统在创建这些资源时会为他们分配内存,并返回标识这些资源的标识号,即句柄。比如有图标句柄(HICO)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)。在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。
(3)消息和消息队列
例:用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到一件事,于是将这个事件包装成一个消息,投递到应用程序的消息队列中,等待应用程序的处理。
注意:所有以xxxWindows结尾的函数发出来的消息都不会进入到消息队列,而是直接走到窗口过程中。所以如果一个函数发出了消息不会回到消息队列中,而是会直接进行④处理相应的窗口过程。
(4)WinMain函数
WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain 函数结束或返回时,Windows应用程序结束。
2. Windows编程模型
(1)创建项目(visual studio 2022)
(2)WinMain函数的定义
define WINAPI __stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回前清空堆栈
(3)创建一个窗口
① 设计窗口 ② 注册窗口 ③ 创建窗口 ④ 显示和更新窗口 ⑤ 通过循环取消息 ⑥ 处理消息(窗口过程)
示例代码:
#include // 底层的窗口实现
#include
// 6 . 处理窗口函数
// define CALLBACK __stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回前清空堆栈
LRESULT CALLBACK Wndproc(
HWND hwnd, // 消息所属的窗口句柄
UINT uMsg, // 具体的消息名称 WM_XXXX消息名
WPARAM wParm, // 附加参数, 键盘附加消息
LPARAM lParm // 鼠标附加消息
)
{
switch (uMsg)
{
case WM_CLOSE:
// 所有以xxxWindows结尾的函数都不会进入到消息队列,而是直接走到窗口过程中
DestroyWindow(hwnd); //DestroyWindow 执行后会发送一个新的消息,WM_DESTROY
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN: // 鼠标左键按下
{
int xPos = LOWORD(lParm);
int yPos = HIWORD(lParm);
WCHAR buf[1024] = { 0 };
wsprintf(buf,TEXT("x = %d,y = %d"),xPos,yPos);
MessageBox(hwnd, buf, TEXT("鼠标左键按下"), MB_OK);
break;
}
case WM_KEYDOWN: // 键盘
{
MessageBox(hwnd, TEXT("键盘按下"), TEXT("键盘按下标题"), MB_OK);
break;
}
case WM_PAINT: // 绘图
{
PAINTSTRUCT ps;// 绘图结构体
HDC hdc = BeginPaint(hwnd,&ps);
TextOut(hdc, 100, 100, TEXT("hello world"), strlen("hello world"));
EndPaint(hwnd, &ps);
}
}
// 返回值使用默认处理方式
return DefWindowProc(hwnd, uMsg, wParm, lParm);
}
// 程序入口函数
// define WINAPI __stdcall 参数的传递顺序,从右到左依次入栈,并且在函数返回前清空堆栈
int WINAPI WinMain(
HINSTANCE hInstance, // 应用程序的实例句柄
HINSTANCE hPrevInstance, // 上一个应用程序句柄,在win32环境下,参数一般都为NULL,不起作用了
LPSTR lpCmdLine, // char* argv[] 命令行参数
int nShowCmd) // 显示命令,比如最大化,最小化,正常
{
/* 创建窗口的步骤
// 1. 设计窗口
// 2. 注册窗口
// 3. 创建窗口
// 4. 显示和更新
// 5. 通过循环取消息
// 6. 处理消息(窗口过程)
*/
// 1. 设计窗口
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0; // 类的额外内存
wc.cbWndExtra = 0; // 窗口的额外内存
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); // 设置背景,
wc.hCursor = LoadCursor(NULL, IDC_HAND); // 设置光标,如果parm1为NULL,代表使用系统提供的光标
wc.hIcon = LoadIcon(NULL, IDI_ERROR);// 图标,如果parm1为NULL,代表使用系统提供的图标
wc.hInstance = hInstance; // 应用程序实例句柄,传入WinMain中的形参即可
wc.lpfnWndProc = Wndproc;// 窗口过程,回调函数
wc.lpszClassName = TEXT("WIN");// 指定窗口的类名称
wc.lpszMenuName = NULL;// 菜单名称
wc.style = 0;// 显示风格,0代表默认风格
// 2. 注册窗口
RegisterClass(&wc);
// 3. 创建窗口
/*
lpClassName, # 类名
lpWindowName, 标题名
dwStyle, 风格,WS_OVERLAPPEDWINDOW混合风格
x, y, 窗口的显示坐标,CW_USEDEFAULT默认值
nWidth, nHeight, 窗口的宽高 CW_USEDEFAULT默认值
hWndParent, 父窗口
hMenu, 菜单
hInstance, 实例句柄
lpParam 附加值,
*/
HWND hwnd = CreateWindow(wc.lpszClassName, TEXT("windows"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, NULL);
// 4. 显示和更新
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
// 5. 通过循环取消息
MSG msg;
/*
HWND hwnd; 主窗口句柄
UINT message; 具体的消息名称
WPARAM wParam; 附加消息,键盘消息
LPARAM lParam; 附加消息,鼠标消息,判断左右键中键
DWORD time; 消息产生的时间
POINT pt; 附加消息,鼠标消息 x,y位置信息
*/
while (1)
{
/*
_Out_ LPMSG lpMsg, 消息
_In_opt_ HWND hWnd, 捕获窗口,填NULL捕获所有的窗口
_In_ UINT wMsgFilterMin, // 最小和最大的过滤消息,一般填0
_In_ UINT wMsgFilterMax // 填0表示捕获所有的消息
*/
if (FALSE == GetMessage(&msg, NULL, 0, 0)) // 如果点 x 则退出
{
break;
}
else
{
// 翻译消息(比如有组合键Ctrl + C的时候,如果需要翻译,然后取消息队列再取消息,在分发)
TranslateMessage(&msg);
// 分发消息
DispatchMessage(&msg);
}
}
// 6. 处理窗口的过程
return 0;
}
3. MFC入门
(1)MFC基本概念
MFC:微软基础类库(英语:Microsoft Foundation Classes),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
(2)第一个MFC应用程序
注:如果visual studio2022中#include
调整好之后编译通不过起不来,需要进行项目配置设置,如下图
(3)程序执行流程
① 程序开始时,先实例化应用程序对象(有且只有一个)
② 执行程序的入口函数InitInstance()
③ 给框架类MyFrame对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create创建窗口
④ 框架类对象显示窗口CWnd::ShowWindow
⑤ 框架类对象更新窗口CWnd::UpdateWindow
⑥ 保存框架类对象指针CWinThread::m_pMainWnd
示例代码:
#include
// 应用程序类
class MyApp : public CWinApp
{
public:
// 重写MFC的程序入口函数
virtual BOOL InitInstance() override;
};
// 框架类
class MyFrame : public CFrameWnd
{
public:
MyFrame();
};
#include "mfc.h"
MyApp app; // 全局应用程序对象
BOOL MyApp::InitInstance()
{
// 创建窗口
MyFrame* myFrame = new MyFrame;
// 显示和更新
myFrame->ShowWindow(SW_SHOWNORMAL);
myFrame->UpdateWindow();
this->m_pMainWnd = myFrame;
return TRUE;
}
MyFrame::MyFrame()
{
Create(NULL, TEXT("MFC"));
}
(4)消息映射
消息映射是一个将消息和成员函数相互关联的表。比如,框架窗口接收到一个鼠标左击消息,MFC将搜索该窗口的消息映射,如果存在一个处理WM_LBUTTONDOWN消息的处理程序,然后就调用OnLButtonDown。
操作步骤:
① 声明消息映射宏(写在CFrameWnd的派生类中)
② 标识宏(BEGIN_MESSAGE_MAP和END_MESSAGE_MAP的调用之间处理消息)
③ 对应消息处理函数分别在类中声明,类外定义:
代码示例:
#include // mfc头文件
// 应用程序类
class MyApp:public CWinApp // CWinApp应用程序类
{
public:
// 程序入口
virtual BOOL InitInstance() override;
};
// 框架类
class MyFrame :public CFrameWnd // 窗口框架类
{
public:
MyFrame();
afx_msg void OnLButtonUp(UINT, CPoint);
afx_msg void OnLButtonDown(UINT, CPoint);
afx_msg void OnChar(UINT, UINT, UINT);
DECLARE_MESSAGE_MAP()
};
#include "mfc.h"
MyApp app; // 全局应用程序对象
BOOL MyApp::InitInstance()
{
// 创建窗口
MyFrame* myFrame = new MyFrame;
// 显示和更新
myFrame->ShowWindow(SW_SHOWNORMAL);
myFrame->UpdateWindow();
this->m_pMainWnd = myFrame;
return TRUE;
}
BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd)
ON_WM_LBUTTONUP()
ON_WM_LBUTTONDOWN()
ON_WM_CHAR()
END_MESSAGE_MAP()
MyFrame::MyFrame()
{
Create(NULL,TEXT("MFC"));
}
void MyFrame::OnLButtonUp(UINT, CPoint)
{
MessageBox(TEXT("松开鼠标左键"));
}
void MyFrame::OnLButtonDown(UINT, CPoint point)
{
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);
}
(5)Windows字符集
① 多字节字符集
一个字符代表一个字节,比如ASCII字符集
② 宽字符集(Unicode字符集)
UTF8:一个字符占3个字节
GBK:一个字符占2个字节
a. 多字节转宽字节,在字符串前加L
b. 关于MFC中的TEXT函数(自适应字符集,无论系统设置的事多字符集还是宽字符集TEXT函数有个宏可以自动适应)
c. 统计宽字符的长度
比如多字节:char * str = "abc", strlen(str)就是字符串长度了
宽字节:wchar_t str = L"abc", wcslen(str)就是宽字节下的字符串长度
d. char* 与 CString之间的转换
char* -> CString: char* p = "abc";CString str(p);
CString -> char*: CstringA tmp; tmp = str;char* pp = tmp.GetBuffer();