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 <windows.h> // 底层的窗口实现
#include <stdlib.h>// 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_DESTROYbreak;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 <afxwin.h>找不到,需要更新配置,链接:visual Studio2022 无法打开源文件 <afxwin.h>的解决办法
调整好之后编译通不过起不来,需要进行项目配置设置,如下图

(3)程序执行流程
① 程序开始时,先实例化应用程序对象(有且只有一个)
② 执行程序的入口函数InitInstance()
③ 给框架类MyFrame对象动态分配空间(自动调用它的构造函数),在其构造函数内部,通过CWnd::Create创建窗口
④ 框架类对象显示窗口CWnd::ShowWindow
⑤ 框架类对象更新窗口CWnd::UpdateWindow
⑥ 保存框架类对象指针CWinThread::m_pMainWnd
示例代码:
#include <afxwin.h>// 应用程序类
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 <afxwin.h> // 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();
