MFC开发1
安装工具集
C# 桌面开发(Windows 窗体 / WPF)
| 分类 | 组件名称 | 是否勾选 |
|---|---|---|
| 必备 | C# 和 Visual Basic Roslyn 编译器 | ✅ |
| 必备 | .NET 桌面开发工具 | ✅ |
| 必备 | Windows 10 SDK | ✅ |
| 可选 | 实时可视化树 | ✅(调试界面用) |
| 可选 | 实时托管资源 | ✅ |
| 不选 | 所有 Azure / 云 / Linux /iOS/ Android | ❌ |
WIN32 纯 Win32 API 开发(C++)
| 分类 | 组件名称 | 是否勾选 |
|---|---|---|
| 必备 | Windows 10 SDK(推荐最新版) | ✅ |
| 必备 | VC++ 2017 v14.16 最新工具集 | ✅ |
| 必备 | Visual C++ 运行时 | ✅ |
| 必备 | C++ 核心功能 | ✅ |
| 必备 | 调试工具 | ✅ |
| 不选 | MFC / ATL | ❌(纯 WIN32 不需要) |
| 不选 | Spectre 库 | ❌ |
MFC 开发(你现在用的,最重要)
| 分类 | 组件名称 | 是否勾选 | 说明 |
|---|---|---|---|
| 必备 | Windows 10 SDK | ✅ | MFC 必须依赖 |
| 必备 | VC++ 2017 v14.16 最新工具集 | ✅ | 编译器 |
| 必备 | MFC 和 ATL 开发工具 | ✅ 最重要! | |
| 必备 | C++ 核心功能 | ✅ | |
| 必备 | 资源编辑器 | ✅ | 编辑对话框 / 控件 |
| 必备 | C++ 调试工具 | ✅ | |
| 必备 | Help Viewer(帮助文档) | ✅ | F1 查看 MFC 说明 |
| 可选 | 类设计器 | ✅ | 看类继承关系 |
| 不选 | Spectre 缓解库 | ❌ | |
| 不选 | 旧版编译器 v14.11~v14.15 | ❌ |
设置把帮助文档
MFC六大机制
| 机制名称 | 核心作用 | 关键宏 / 类 | 典型用途 |
|---|---|---|---|
| 窗口机制 | 封装 HWND 窗口,建立 MFC 对象与窗口句柄的映射,自动管理窗口创建、销毁、子类化 | CWnd、Attach/Detach、CreateEx | 窗口、控件、对话框的封装与管理 |
| 消息映射机制 | 替代 switch-case,用宏表绑定消息与处理函数,自动分发消息 | DECLARE_MESSAGE_MAPBEGIN_MESSAGE_MAPEND_MESSAGE_MAP | 鼠标、键盘、按钮、自定义消息处理 |
| 运行时类信息机制(RTTI) | MFC 内置运行时类型识别,判断对象所属类、继承关系 | DECLARE_DYNAMICIMPLEMENT_DYNAMIC | 类型判断、安全转换、框架内部识别 |
| 动态创建机制 | 根据类名字符串动态构造对象,无需 new | DECLARE_DYNCREATEIMPLEMENT_DYNCREATE | 文档 / 视图结构自动创建视图、窗口 |
| 序列化机制(持久化) | 对象数据与文件相互转换,简化读写保存 | CArchive、Serialize、CObject | 文档保存 / 加载、配置持久化 |
| 命令传递机制(命令路由) | 菜单、工具栏等命令按固定路线传递,寻找处理函数 | ON_COMMAND、UPDATE_COMMAND_UI | 菜单点击、按钮状态更新、命令统一分发 |
MFC框架和头文件
“afx.h” MFC中绝大部分类的声明
afxwin.h 包含了 afx.h 和windows.h
afxext.h 包含了扩展 窗口类的声明
单文档视图架构程序
CWinApp-应用程序类,负责管理应用程序的流程
CFrameWnd-框架窗口类,负责管理框架窗口,负责所有子管理框架窗口
CView-视图窗口类,负责显示数据和窗口操作
CDocument-文档类,负责管理数据(提取/转换/存储等数据处理)
每个窗口负责单独事情,分模块做不同事情,核心是文档和视图
多文档视图架构程序
CWinApp-应用程序类
CMDIFrameWnd-多文档主框架窗口类,负责所有子管理框架窗口,主框架窗口操作
CMDIChildWnd-多文档子框架窗口类,子框架窗口各种操作
CView-视图窗口类,显示数据
CDocument-文档类,管理数据
对话框应用程序
CWinApp-应用程序类
CDialog-对话框窗口类
CObject类
MFC类库中绝大部分类的父类,提供了MFC类库中一些基本的机制。
对运行时类信息的支持对动态创建的支持
对序列化的支持:(运行时类信息机制,动态创建机制 序列化机制)
CWinApp类 应用程序类,封装了应用程序、线程等信息。
CDocument类,文档类,管理数据
Frame Windows,框架窗口类,封装了窗口程序组成的各种 框架窗口
CSplitterWnd-用来完成拆分窗口的类
Control Bars-控件条类
Dialog Boxes-对话框类,封装了各种对话框,通用的对话框
Views-视图类,封装了各种显示窗口
Controls-控件类,封装了各种常用的控件
Exceptions-异常处理类,封装了MFC中常用的各种异常File-文件类,各种文件的I/O操作等绘图类,包括CDC类和CGdiObject类。
数据集合类,CArray/Clist/CMap,封装了相应的数据结构的管理
CFile及其子类-文件操作类,封装了关于各种文件的读写操作
CWnd-所有窗口类的最基类
Frame Windows-框架窗口类,负责管理各种框架窗口。
Dialog Boxes- 对话框窗口类,封装了对各种对话框的操作
CCmdTarget 消息映射机制的最基类
Controls- 控件窗口类,封装了对各种控件窗口的操作
CDC/CGdi0bject及其子类-封装关于绘图操作
CArrary/CList/CMap及其子类- 封装C++语法中相应数据结构
非CObject类的子类
CPoint, CTime, CString行提供了各种数据结构相关的管理,
创建程序
先创建一个工程
定义自己的框架类CMyFrameWnd,派生自CFrameWnd类.
1 | class CMyFrameWnd : public CFrameWnd{ |
定义自己应用程序类CMyWinApp,派生自CWinApp类,并定义构造以及重写Initnstance虚函数,在函数中创建并显示窗口
1 | class CMyWinApp : public CWinApp{ |
定义CMyWinApp类的对象(程序的爆破点)
1 | CMyWinApp theApp; //全局变量 |
入口函数
与Win32窗口程序相同,都是从WinMain入口。但是MFC库已经实现了WinMain函数,所以在程序中不需要实现。
总结:在Win32课程中WinMain由程序员自己实现,那么流程是程序员安排,但到了MFC中,由于MFC库实现WinMain,也就意味着MFC负责安排程序的流程。
MFC中三个全局变量
1 | AfxGetModuleState()//获取程序模块状态信息 |
执行流程 MFC源代码(启动)
程序的启动,构造theApp对象,调用父类CWinApp的构造函数。
将theApp对象的地址保存到线程状态信息中
将theApp对象的地址保存到模块状态信息中
进入WinMain函数,调用AfxWinMain函数
- 将 & theApp 保存到当前程序线程状态信息中。
- 将 & theApp 保存到当前程序模块状态信息中。
- AfxGetThread ()/AfxGetApp () – 返回 & theApp
进入入口函数WinMain
获取应用程序类对象theApp的地址
利用theApp地址调用InitApplication,初始化当前应用程序的数据
利用theApp地址调用InitInstance函数初始化程序,在函数中我们创建窗口并显示。
利用theApp地址调用CWinApp的Run函数进行消息循环如果没有消息,
利用theApp地址调用OnIdle虚函数实现空闲处理程序退出
利用theApp地址调用ExitInstance虚函数实现退出前的善后处理工作
进入入口函数(WinMain)
- 利用 AfxGetThread ()/AfxGetApp () 获取 & theApp
- 利用 theApp 调用应用程序类成员虚函数InitApplication(初始化)
- 利用 theApp 调用应用程序类成员虚函数InitInstance(创建 / 显示窗口)
- 利用 theApp 调用应用程序类成员虚函数Run(消息循环)
4.1)如果没有消息利用 theApp 调用应用程序类成员虚函数 OnIdle(空闲处理)
4.2)如果程序退出利用 theApp 调用应用程序类成员虚函数 ExitInstance(善后处理)
如下伪代码
1 | // MFC 内部全局变量定义(位于 afx.h / afxwin.h) |
成员虚函数
InitApplication 利用theApp对象调用应用程序类成员虚函数 初始化
InitInstance-程序的初始化函数,完成了窗口创建等初始化处理
ExitInstance-程序退出时调用,清理资源等善后工作
Run-消息循环
OnIdle-空闲处理函数
成员变量
m_pMainWnd-当前应用程序的主窗口
重写虚函数,调用父类虚函数virtual
钩子函数 WIN32技术
创建钩子
1 | HHOOK SetWindowsHookEx( |
钩子处理函数
1 | LRESULT CALLBACK CBTPrOc( |
更改窗口处理函数
这个函数与上面函数无关
1 | LONG_PTR SetWindowLongPtr( |
WH_CBT事件中,钩子函数中得到了窗口句柄;
代码书写
定义自己的框架类(CMyFrameWnd),派生自CFrameWnd
定义自己的应用程序类(CMyWinApp),派生自CWinApp,并重写父类成员虚函数InitInstance
定义全局对象 CMyWinApptheApp;(爆破点)
AfxGetlnstanceHandle获取句柄
窗口创建机制
加载菜单
MFC 框架在窗口创建前,先加载窗口关联的菜单资源,为后续窗口显示做准备。
调用 CreateEx 函数(注册窗口类 / 创建窗口)
调用 PreCreateWindow 函数(设计 / 注册窗口类)
填充 WNDCLASS 结构体,设置窗口类属性,示例代码:
1 | WNDCLASS wndcls; |
最终调用 Win32 API ::RegisterClass,注册一个局部窗口类,MFC 默认类名如 AfxFrameOrView42sd(数字对应 MFC 版本)。
1 | --- |
调用 AfxHookWindowCreate 函数
利用 Win32 API ::SetWindowsHookEx,在程序中安装类型为 WH_CBT 的钩子
用于拦截窗口创建事件,在窗口创建过程中插入绑定逻辑。
将 new 出来的框架类对象地址 pFrame,保存到当前程序线程信息中
为后续窗口句柄与 C++ 对象的绑定做准备。
调用 Win32 API ::CreateWindowEx 创建窗口
该函数执行成功后,会立即触发 WH_CBT 钩子的处理函数。
钩子处理函数
将窗口句柄(hWnd)和框架类对象(pFrame)建立一对一的绑定关系
1 | pFrame->m_hWnd = hWnd; // C++对象持有窗口句柄(对象→句柄绑定) |
m_permanentMap 是 MFC 全局的永久句柄哈希表,实现 HWND 与 CWnd 对象的双向映射,是 GetDlgItem()、FromHandlePermanent() 等函数的底层基础。
利用 Win32 API SetWindowLong,将窗口处理函数更改为 AfxWndProc
- 替换掉注册时临时使用的
DefWindowProc,将窗口过程接管为 MFC 全局入口AfxWndProc,为后续消息路由做准备。 AfxWndProc是 MFC 所有窗口消息的统一入口,负责将系统消息分发到对应CWnd对象。
消息的处理
获取与消息的窗口句柄绑定在一起的框架类对象地址 pFrame
AfxWndProc 收到系统消息后,通过消息携带的 hWnd,在 m_permanentMap 中查找到对应的 CWnd 派生类(框架类)对象指针 pFrame。
利用 pFrame 调用 WindowProc 成员虚函数,完成消息的处理
AfxWndProc调用pFrame->WindowProc(),进入CWnd的消息处理流程。WindowProc是CWnd的虚函数,会进一步调用OnWndMsg,匹配ON_WM_*消息映射宏,将消息分发到用户自定义的消息处理函数(如OnPaint、OnCommand)。- 未匹配的消息最终调用
DefWindowProc执行系统默认处理。
完整流程链路(串联版)
1 | 1. 加载菜单资源 |
| 关键环节 | 核心作用 |
|---|---|
WH_CBT 钩子 |
在窗口创建过程中插入绑定逻辑,解决 HWND 与 CWnd 对象的绑定时机问题 |
m_permanentMap 双向映射 |
实现 Win32 系统句柄与 MFC C++ 对象的一一对应,是 MFC 面向对象封装的核心 |
AfxWndProc 全局窗口过程 |
MFC 消息路由的总入口,负责消息分发,连接系统消息与用户代码 |
WindowProc 虚函数 |
实现消息的多态分发,让不同 CWnd 派生类可以自定义消息处理逻辑 |
消息映射
类必须派生自CCmdTatget,可以间接的派生
类内必须添加声明宏 DECLARE_MESSAGE_MAP();
1 | // struct AFX_MSGMAP_ENTRY(静态数组每个元素的类型) |
静态成员无法继承。
c++中一个类中有静态成员函数,用这个类创建多个成员,是不是使用同一个静态空间。
不管你用这个类 new 出 1 个、10 个、100 个对象,它们的 静态成员变量、静态成员函数,永远只有 1 份!所有对象共用同一份!
静态成员属于类,不属于对象
普通成员:每个对象一份
静态成员:整个类只有一份,所有对象共享
无论创建多少对象,静态成员永远只有一份
所有对象共用同一块内存
MFC 控件千万不能定义成 static
类外必须添加实现宏
1 | BEGIN_MESSAGE_MAP(本类名(theclass),父类名(baseclass)) |
消息映射就是创建了一个单项链表,每个链表中有两个变量,一个保存了本类中的所有消息处理函数数组地址,另一个保存了父类消息隐射的首地址,展开宏就可以看出来
示例
1 | LRESULT CMyFrameWnd::funcname(WPARAM wParam, LPARAM lParam) |
消息映射机制速度慢,消息映射是一个链表结构;afx_msg 站位符,可以区分是消息还是别的处理函数
1 | ON_WN_CREATE();//消息映射 |
ON_WM_CREATE():在窗口(包括主框架、对话框、控件等)的物理句柄创建成功后、但尚未显示出来之前,执行一次性的初始化工作。
用户自定义消息
#define WM_MYMESSAGE WM_USER+N <= 31743
ON_MESSAGE 可以发任何自定义消息
1 | #define WM_MYMESSAGE WM_USER+2000 |
命令消息(WM_COMMAND)
1 | ON_COMMAND(命令ID/控件ID,处理函数) |
通知消息(EN_CHANGE)
1 | ON_EN_CHANGE() |
命令消息(WM_COMMAND)处理顺序
视图类 文档类 框架类pFrame中先找该消息,如果找不到再去应用程序类中寻找,这是默认执行顺序;可以修改这个顺序,因为处理的函数是虚函数
菜单项的状态ON_WM_INITMENUPOPUP
::CheckMenuItem / ::EnableMenuItem
CMenu::CheckMenuItem / CMenu::EnableMenuItem
| 特性 | 命令消息 (Command Message) | 用户自定义消息 (User-defined Message) |
|---|---|---|
| 消息ID | WM_COMMAND |
WM_USER + n |
| 处理方式 | 路由 (Routing) | 直接投递 (Direct Dispatch) |
| 查询范围 | 沿预设路径(框架->视图->文档->应用)依次查询 | 仅在目标窗口对应的 CWnd 派生类中查询 |
| 典型用途 | 菜单、工具栏、加速键等用户界面命令 | 窗口内部或特定窗口间的自定义通信 |
窗口处理函数
WndProc()
MFC菜单
WIN32 — HMENU(菜单句柄)
MFC — CMenu类对象
CheckMenuItem
右键菜单(上下文菜单)
ON_WM_CONTEXTMENU
::TrackPopupMenu/CMenu::TrackPopupMenu
CMenu::GetSubMenu 获取某个顶层菜单项的下拉菜单
Accelerator 加速键
和菜单绑定使用,加速键就是快捷按钮,只要句柄或者叫ID相同就算绑定了。
加载加速键 PreTranslateMessage(虚函数)
virtual BOOL PreTranslateMessage(MSG* pMsg);
相关类
CMenu — 父类为CObject,封装了操作菜单种API函数,不是CCmdTaget,无法处理消息映射
工具栏
工具栏都是一些按钮,如果还有别的就是对话框,不是工具栏;工具栏是一个容器,包含了一些按钮
CToolBarCtrl - 父类为CWnd,封装了关于工具栏控件的操作,这是一个容器
CToolBar - 父类为CControlBar,封装了工具栏和框架窗口之间关系停靠关系,还封装了工具栏窗口的创建
工具栏使用 afxext.h
添加工具栏资源
只要ID一样,就能把工具栏图标绑定按钮
工具栏的使用
添加工具栏资源
创建工具栏
CToolBar::Create / CToolBar::CreateEx
加载工具栏资源 窗口UI的ID号
CToolBar::LoadToolBar
都需要绑定
工具栏的停靠()
工具栏准备停靠位置 CToolBat::EnableDocking
框架窗口允许停靠位置 CFrameWnd::EnableDocking
工具栏临时停靠的位置 CFrameWnd::DockControlBar
工具栏显示和隐藏
CFrameWnd::ShowControlBar
CWnd::IsWindowVisible()
工具栏
1 | //工具栏显示打勾,不显示不打勾 菜单 |
状态栏
相关类
CStatusBar - 父类为CControlBar,封装了关于状态栏的操作,包含状态栏的创建
状态栏的使用
创建状态栏
CStatusBar ::Create / CStatusBar ::CreateEx
设置指示器(指示器设置的前提是设置 string table(设置字符表),才能显示指示器)
指示器表示可以显示多少个不同类的数据
CStatusBar::Setlndicators
设置指示器的宽度和风格
CStatusBar::SetPaneInfo
设置指示器的文本内容
CStatusBar : SetPaneText
05A 1.0
窗口:父窗口和子窗口,客户区和非客户区
句柄:结构体变量,控件的标志,窗口句柄:HWND, 图标句柄:HICO
SendMsg 不进入消息队列直接执行
PostMsg 进入消息队列
1)有且只有一个全局的应用程序类对象
2)在程序入口函数实现功能Initlnstance ()
A)给框架类MyFrame对象动态分配空间(自动调用它的构造函数)
a)框架类MyFrame 对象构造函数函数里创建窗口CWnd::Create
B)框架类对象显示窗CWnd::ShowWindow
C框架类对象更新窗口CWnd::UpdateWindow
D)保存框架类对象指针 CWinThread::m pMainWnd
几个比较重要的函数
应用程序类CWinApp::InitInstance(),程序的入口地址
框架类cFrameWnd:
PreCreateWindow()创建窗口之前调用
OnCreate ()创建窗口后,触发WM_CREATE,它是WM_CREATE消息的处理函数
文档视图结构
文档:是一个类,专门来存储数据,没有消息
视图:是一个类,专门来显示和修改数据
应用程序类:InitInstance() 程序入口地址;没有消息
框架调用顺序
1 | 重要的函数 |
视图类CView
1 | 重要函数 绘图 |
WM_PAINT消息处理函数OnPaint()内部调用OnDraw()
OnDraw 与 OnPaint 同时存在时,只有OnPaint(ON_WM_PAINT) 有效;OnPaint 内部会调用OnDraw 函数,
OnDraw 是由 MFC 框架(CView 基类)在收到 WM_PAINT 消息时自动调用的。
1 | Windows 系统 → 发 WM_PAINT 消息 → CView::OnPaint() → 自动调用 → 你的 OnDraw() |
事件的添加和删除
框架和视图的区别
选择所需类 添加消息
框架相当于容器,容器放视图和文档、目录等
视图相当于壁纸,左键事件被视图获取了,在有视图中,右键事件需要添加到视图中才能处理,
视图把框架盖住了,所以事件需要在框架中添加,画图需要在视图中画图
处理按键事件
1 | void CMFCSdiView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) |
MessageBox
这个函数只能在CWnd类和子类中使用,如果不是这个其中的类,只能使用如下
afx_xxx:全局函数,不属于某个类的特有的
扩展
xxxxEx:xxxW, 扩展函数
绘图
画直线
需要指定在那个视图或者窗口画图
CPaintDC:只能在 OnPaint中使用
CClientDC :可以在任意地方使用,画家对象
起点和终点
CDC::MoveTo
CDC::LineTo
画笔 画刷的使用
定义画家
定义画笔(画刷) CPen CBrush CBitmap CRgn
画笔交给画家CDC::SelectObject
画刷:单色,样式, 位图
写字
写字 CFont的使用
位图
只能使用BMP图片
1 | //定义一个位图对象 |
绘制图片
1 | // 头文件加 |
插入符
文本编辑器
创建插入符 CWnd::CreateSolidCaret()
- 创建 CWnd::CreateSolidCaret()
- 显示 CWnd::ShowCaret()
- 插入符的高度根据字体高度来确定 获取字体信息CDC::GetTextMetrics()
- 设置插入符位置CWnd::SetCaretPos()
在字符消息处理函数中写子
写字CDC::TextOutW()
获取字符串的尺寸信息CDC::GetTextExtent()
获取背景颜色 CClientDC::GetBkColor
截取字符串(CString)左边指定长度的字符 CString str1 = str.left(str.GetLength()-1);
1 | //视图中 窗口创建后自动调用 |
定时器
设置定时器CWnd::SetTimer()
关闭定时器CWnd::KillTimer()
定时器消息WM_TIMER
视图类中的OnDraw()中 ,写字CDC::TextOutW()
指定区域写字 CDC::DrawText()
CClientDC::SetTextColor()直接通过RGB设置文字颜色
让窗口失效,产生WM_PINT,间接调用OnDraw()函数 无效的整个工作区 CWnd::Invalidate()
1 | void CMFCSdiView::OnTimer(UINT_PTR nIDEvent) |
更新数据
1 | UpdateData(true); |