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 找不到,需要更新配置,链接:visual Studio2022 无法打开源文件 <afxwin.h>的解决办法

调整好之后编译通不过起不来,需要进行项目配置设置,如下图

(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();