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
2
3
class CMyFrameWnd : public CFrameWnd{

};

定义自己应用程序类CMyWinApp,派生自CWinApp类,并定义构造以及重写Initnstance虚函数,在函数中创建并显示窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CMyWinApp : public CWinApp{
public:
CMyWinApp(){
}
virtual BOOL InitInstance() override{
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL,"MFCBase");
m_pMainWnd = pFrame;
pFrame-> ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
}
};

定义CMyWinApp类的对象(程序的爆破点)

1
CMyWinApp theApp;	//全局变量

入口函数

与Win32窗口程序相同,都是从WinMain入口。但是MFC库已经实现了WinMain函数,所以在程序中不需要实现。
总结:在Win32课程中WinMain由程序员自己实现,那么流程是程序员安排,但到了MFC中,由于MFC库实现WinMain,也就意味着MFC负责安排程序的流程。

MFC中三个全局变量

1
2
3
4
5
AfxGetModuleState()//获取程序模块状态信息
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;//线程状态
pThreadState->m_pCurrentWinThread = this;
AfxGetModuleThreadState()
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();//_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();

执行流程 MFC源代码(启动)

程序的启动,构造theApp对象,调用父类CWinApp的构造函数。
将theApp对象的地址保存到线程状态信息中
将theApp对象的地址保存到模块状态信息中
进入WinMain函数,调用AfxWinMain函数

  1. 将 & theApp 保存到当前程序线程状态信息中。
  2. 将 & theApp 保存到当前程序模块状态信息中。
  3. AfxGetThread ()/AfxGetApp () – 返回 & theApp

进入入口函数WinMain

获取应用程序类对象theApp的地址
利用theApp地址调用InitApplication,初始化当前应用程序的数据
利用theApp地址调用InitInstance函数初始化程序,在函数中我们创建窗口并显示。
利用theApp地址调用CWinApp的Run函数进行消息循环如果没有消息,
利用theApp地址调用OnIdle虚函数实现空闲处理程序退出
利用theApp地址调用ExitInstance虚函数实现退出前的善后处理工作

进入入口函数(WinMain)

  1. 利用 AfxGetThread ()/AfxGetApp () 获取 & theApp
  2. 利用 theApp 调用应用程序类成员虚函数InitApplication(初始化)
  3. 利用 theApp 调用应用程序类成员虚函数InitInstance(创建 / 显示窗口)
  4. 利用 theApp 调用应用程序类成员虚函数Run(消息循环)
    4.1)如果没有消息利用 theApp 调用应用程序类成员虚函数 OnIdle(空闲处理)
    4.2)如果程序退出利用 theApp 调用应用程序类成员虚函数 ExitInstance(善后处理)

如下伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// MFC 内部全局变量定义(位于 afx.h / afxwin.h)
static CWinApp* pCurrentApp = nullptr;

// AfxGetApp() 的伪实现
CWinApp* AfxGetApp()
{
return pCurrentApp; // 直接返回全局指针
}

// MFC 框架在 WinMain 中的初始化流程(伪代码)
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 1. 初始化 MFC 框架(设置线程上下文、加载资源等)
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);

// 2. 获取全局应用对象 —— 这就是你定义的 theApp
CWinApp* pApp = AfxGetApp(); // ← 此时 pCurrentApp 已被设置为 &theApp

// 3. 调用你重写的 InitInstance()
if (!pApp->InitInstance())
return FALSE;

// 4. 启动消息循环
int nResult = pApp->Run();

// 5. 清理
pApp->ExitInstance();
return nResult;
}


成员虚函数
InitApplication 利用theApp对象调用应用程序类成员虚函数 初始化
InitInstance-程序的初始化函数,完成了窗口创建等初始化处理
ExitInstance-程序退出时调用,清理资源等善后工作
Run-消息循环
OnIdle-空闲处理函数
成员变量
m_pMainWnd-当前应用程序的主窗口
重写虚函数,调用父类虚函数virtual

钩子函数 WIN32技术

创建钩子

1
2
3
4
5
HHOOK SetWindowsHookEx(
int idHook,//钩子类型(WH_CBT)
HOOKPROCIpfn,//钩子处理函数
HINSTANCE hMod,//应用程序实例句柄 不设置读取所有的应用程序
DWORD dwThreadld); // 线程ID; 不设置所有的线程

钩子处理函数

1
2
3
4
5
LRESULT CALLBACK CBTPrOc(
int nCode,//钩子码(HCBT_CREATEWND) 与钩子类型相同
WPARAM wParam,//刚刚创建成功窗口句柄 指定新窗口的句柄。
LPARAM IParam //
);

更改窗口处理函数

这个函数与上面函数无关

1
2
3
4
5
LONG_PTR SetWindowLongPtr(
HWND hWnd,//窗口句柄
int nIndex, // GWLP_WNDPROC
LONG_PTR dwNewLong//新的窗口处理函数名(函数地址)
};

WH_CBT事件中,钩子函数中得到了窗口句柄;

代码书写

定义自己的框架类(CMyFrameWnd),派生自CFrameWnd
定义自己的应用程序类(CMyWinApp),派生自CWinApp,并重写父类成员虚函数InitInstance
定义全局对象 CMyWinApptheApp;(爆破点)

AfxGetlnstanceHandle获取句柄

窗口创建机制

加载菜单

MFC 框架在窗口创建前,先加载窗口关联的菜单资源,为后续窗口显示做准备。

调用 CreateEx 函数(注册窗口类 / 创建窗口)

调用 PreCreateWindow 函数(设计 / 注册窗口类)
填充 WNDCLASS 结构体,设置窗口类属性,示例代码:

1
2
3
WNDCLASS wndcls;
wndcls.lpfnWndProc = DefWindowProc; // 临时使用默认窗口过程
// ... 其他属性赋值

最终调用 Win32 API ::RegisterClass,注册一个局部窗口类,MFC 默认类名如 AfxFrameOrView42sd(数字对应 MFC 版本)。

1
---

调用 AfxHookWindowCreate 函数
利用 Win32 API ::SetWindowsHookEx,在程序中安装类型为 WH_CBT 的钩子
用于拦截窗口创建事件,在窗口创建过程中插入绑定逻辑。
new 出来的框架类对象地址 pFrame,保存到当前程序线程信息中
为后续窗口句柄与 C++ 对象的绑定做准备。

调用 Win32 API ::CreateWindowEx 创建窗口

该函数执行成功后,会立即触发 WH_CBT 钩子的处理函数。

钩子处理函数

将窗口句柄(hWnd)和框架类对象(pFrame)建立一对一的绑定关系

1
2
pFrame->m_hWnd = hWnd;          // C++对象持有窗口句柄(对象→句柄绑定)
m_permanentMap[hWnd] = pFrame; // 句柄映射表记录(句柄→对象绑定)

m_permanentMap 是 MFC 全局的永久句柄哈希表,实现 HWNDCWnd 对象的双向映射,是 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 的消息处理流程。
  • WindowProcCWnd 的虚函数,会进一步调用 OnWndMsg,匹配 ON_WM_* 消息映射宏,将消息分发到用户自定义的消息处理函数(如 OnPaintOnCommand)。
  • 未匹配的消息最终调用 DefWindowProc 执行系统默认处理。

完整流程链路(串联版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 加载菜单资源

2. CreateEx 窗口创建流程
2.1 PreCreateWindow → 注册临时窗口类(DefWindowProc)
2.2 AfxHookWindowCreate → 安装WH_CBT钩子,保存pFrame到线程状态
2.3 CreateWindowEx → 创建窗口,触发WH_CBT钩子

3. 钩子处理函数
3.1 双向绑定:pFrame<->hWnd,写入m_permanentMap
3.2 SetWindowLong → 替换窗口过程为AfxWndProc,卸载钩子

4. 消息处理流程
4.1 系统消息 → AfxWndProc(全局入口)
4.2 通过hWnd查m_permanentMap,获取pFrame
4.3 调用pFrame->WindowProc() → 进入MFC消息映射
4.4 匹配ON_WM_*宏,执行用户自定义处理函数
4.5 未匹配消息调用DefWindowProc
关键环节 核心作用
WH_CBT 钩子 在窗口创建过程中插入绑定逻辑,解决 HWNDCWnd 对象的绑定时机问题
m_permanentMap 双向映射 实现 Win32 系统句柄与 MFC C++ 对象的一一对应,是 MFC 面向对象封装的核心
AfxWndProc 全局窗口过程 MFC 消息路由的总入口,负责消息分发,连接系统消息与用户代码
WindowProc 虚函数 实现消息的多态分发,让不同 CWnd 派生类可以自定义消息处理逻辑

消息映射

类必须派生自CCmdTatget,可以间接的派生

类内必须添加声明宏 DECLARE_MESSAGE_MAP();

1
2
3
4
5
6
7
8
9
10
// struct AFX_MSGMAP_ENTRY(静态数组每个元素的类型)
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // 消息ID
UINT nCode; // 通知码 控件才有
UINT nID; // 命令ID(菜单 加速)/控件ID
UINT nLastID; // 最后一个控件ID 可以多个控件对应一个处理
UINT nSig; // 消息处理函数的类型(确定调用联合体那个成员来执行pfn)
AFX_PMSG pfn; // 消息处理函数的地址(指针)
};

静态成员无法继承。

c++中一个类中有静态成员函数,用这个类创建多个成员,是不是使用同一个静态空间。

不管你用这个类 new 出 1 个、10 个、100 个对象,它们的 静态成员变量、静态成员函数,永远只有 1 份!所有对象共用同一份!

静态成员属于类,不属于对象

普通成员:每个对象一份

静态成员:整个类只有一份,所有对象共享

无论创建多少对象,静态成员永远只有一份

所有对象共用同一块内存

MFC 控件千万不能定义成 static

类外必须添加实现宏

1
2
BEGIN_MESSAGE_MAP(本类名(theclass),父类名(baseclass))
END_MESSAGE_MAP()

消息映射就是创建了一个单项链表,每个链表中有两个变量,一个保存了本类中的所有消息处理函数数组地址,另一个保存了父类消息隐射的首地址,展开宏就可以看出来

示例

1
2
3
4
5
6
7
8
9
10
LRESULT CMyFrameWnd::funcname(WPARAM wParam, LPARAM lParam)
{

}

BEGIN_MASSAGE_MAP(CMyFrameWnd,CFrameWnd)
ON_MASSAGE(WM_CREATE,funcname) //ID 和函数连接,funcname在类中声明,
END_MESSAGE_MAP()


消息映射机制速度慢,消息映射是一个链表结构;afx_msg 站位符,可以区分是消息还是别的处理函数

1
2
3
4
5
6
7
8
9
10
11
12
ON_WN_CREATE();//消息映射
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
//自己创建需要调父类
}
---
ON_WM_PAINT()
afx_msg void OnPaint()
{
//绘图
//不用吊父类
}

ON_WM_CREATE():在窗口(包括主框架、对话框、控件等)的物理句柄创建成功后、但尚未显示出来之前,执行一次性的初始化工作。

用户自定义消息

#define WM_MYMESSAGE WM_USER+N <= 31743

ON_MESSAGE 可以发任何自定义消息

1
2
3
4
5
6
7
8
9
#define WM_MYMESSAGE WM_USER+2000 
::PostMessage(this->m_hWnd,WM_MYMESSAGE,1,2);//发消息

ON_MESSAGE(WM_MYMESSAGE,OnMyMessage)

LRESULT 类名:OnMyMessage( WPARAM para1, LPARAM para2)
{

}

命令消息(WM_COMMAND)

1
ON_COMMAND(命令ID/控件ID,处理函数)

通知消息(EN_CHANGE)

1
2
3
4
5
ON_EN_CHANGE()
void 类名::OnEnChange()
{

}

命令消息(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//工具栏显示打勾,不显示不打勾 菜单
//点击菜单 → 先触发这个消息 → 你改菜单 → 再显示菜单
//这是 MFC 里动态更新菜单唯一正确的地方
ON_WN_INITMENUPOPUP() //菜单即将弹出、但还没显示出来的那一瞬间

ON_COMMAND(ID_TOOL,OnTool)


afx_msg void OnInitMenuPopup(CMenu *pPopup,UINT nPos,BOOL i)
{
CFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);

if(toolbar.isWindowVisible())//toolbar 是工具栏 是否显示出来
{
pPopup->CheckMenuItem(ID_TOOL,MF_CHECKED);//菜单项打勾
}
else
{
pPopup->CheckMenuItem(ID_TOOL,MF_UNCHECKED);//菜单项不打勾
}
}


afx_msg void OnTool()
{
//如果工具栏隐藏,那么就显示;如果工具栏显示,那么就隐藏
if(toolbar.isWindowVisible())
{
ShowControlBar(&toolbar,FALSH,FALSH);//框架类函数CFrameWnd
}
else
{
ShowControlBar(&toolbar,TRUE,FALSH);
}
}

状态栏

相关类

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
2
3
4
重要的函数
1. BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs); 在没有创建窗口前调用
2. Create(...); 创建窗口后,触发WM_CREATE()消息后,调用下面函数
3. int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct);

视图类CView

1
2
重要函数 绘图
void CMFCSdiView::OnDraw(CDC* /*pDC*/)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void CMFCSdiView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
TCHAR BUF[1024] = { 0 };
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nChar)
{
case VK_LEFT:
wsprintf(BUF,TEXT("LEFT 重复次数:%d 扫描代码:%d"),nRepCnt,nFlags);
MessageBox(BUF);
break;

case VK_UP:
wsprintf(BUF, TEXT("UP 重复次数:%d 扫描代码:%d"), nRepCnt, nFlags);
MessageBox(BUF);
break;

case VK_RIGHT:
wsprintf(BUF, TEXT("RIGHT 重复次数:%d 扫描代码:%d"), nRepCnt, nFlags);
MessageBox(BUF);
break;

case VK_DOWN:
wsprintf(BUF, TEXT("DOWN 重复次数:%d 扫描代码:%d"), nRepCnt, nFlags);
MessageBox(BUF);
break;

default:
wsprintf(BUF, TEXT("其他按键:%d 重复次数:%d 扫描代码:%d"), nChar, nRepCnt, nFlags);
MessageBox(BUF);
break;
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}


void CMFCSdiView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
TCHAR ch = (TCHAR)nChar;
CString str;
str.Format(TEXT("%c"),ch);
MessageBox(str);
CView::OnChar(nChar, nRepCnt, nFlags);
}

MessageBox

这个函数只能在CWnd类和子类中使用,如果不是这个其中的类,只能使用如下

afx_xxx:全局函数,不属于某个类的特有的

扩展

xxxxEx:xxxW, 扩展函数

绘图

画直线

需要指定在那个视图或者窗口画图

CPaintDC:只能在 OnPaint中使用

CClientDC :可以在任意地方使用,画家对象

起点和终点

CDC::MoveTo

CDC::LineTo

画笔 画刷的使用

定义画家

定义画笔(画刷) CPen CBrush CBitmap CRgn

画笔交给画家CDC::SelectObject

画刷:单色,样式, 位图

写字

写字 CFont的使用

位图

只能使用BMP图片

1
2
3
4
5
6
7
//定义一个位图对象
CBitmap bitmap;
bitmap.LoadBitmapW(IDB_BITMAP1);//加载位图
CBrush brush2(&bitmap);
pb = pDC->SelectObject(&brush2);
pDC->Ellipse(400, 400, 800, 600);
pDC->SelectObject(&pb);

绘制图片

1
2
3
4
5
6
7
8
9
10
11
12
// 头文件加
#include <atlimage.h>
CImage m_Image;

// 初始化加载
m_Image.LoadFromResource(AfxGetResourceHandle(), IDB_PNG1);

// 绘图
void CMFCSdiView::OnDraw(CDC* pDC)
{
m_Image.Draw(pDC->m_hDC, 20, 20, 20+m_Image.GetWidth(), 20+ m_Image.GetHeight());
}

插入符

文本编辑器

创建插入符 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//视图中 窗口创建后自动调用
int CMFCSdiView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: 在此添加您专用的创建代码
//获取字体信息
CClientDC dc(this);
TEXTMETRIC tm; //字体信息结构体
dc.GetTextMetrics(&tm);
//继承CWnd
this->CreateSolidCaret(tm.tmAveCharWidth/8,tm.tmHeight); //创建插入符
this->ShowCaret();//显示

return 0;
}


void CMFCSdiView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
SetCaretPos(point);
//保存坐标
m_point = point;

caret_str.Empty();

CView::OnLButtonDown(nFlags, point);
}

//点击键盘,启动调用
void CMFCSdiView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//caret_str.Insert(caret_str.GetLength(),);

CClientDC dc(this);
if (nChar == VK_RETURN) //换行
{
caret_str.Empty();
//获取字体信息
TEXTMETRIC tm; //字体信息结构体
dc.GetTextMetrics(&tm);
m_point.y += tm.tmHeight;
}
else if (nChar == VK_BACK) //退格
{
//获取背景色,
COLORREF color = dc.GetBkColor();
//设置写字的颜色
COLORREF oldcolor = dc.SetTextColor(color);

//白色重写一次 覆盖在之前写的字体上
dc.TextOutW(m_point.x, m_point.y, caret_str);

//去掉最后一个字符
caret_str = caret_str.Left(caret_str.GetLength() - 1);

//颜色恢复,重写一次
dc.SetTextColor(oldcolor);
//dc.TextOutW(m_point.x, m_point.y, caret_str);

}
else if (nChar == VK_CLEAR) //清除
{

}
else
{
caret_str += (TCHAR)nChar;
}

CSize size = dc.GetTextExtent(caret_str);//获取字符串长度
int x = m_point.x + size.cx;
int y = m_point.y;
SetCaretPos(CPoint(x,y));

dc.TextOutW(m_point.x, m_point.y, caret_str); //这里坐标不能变化,都是根据这个坐标点来编写
CView::OnChar(nChar, nRepCnt, nFlags);
}

定时器

设置定时器CWnd::SetTimer()

关闭定时器CWnd::KillTimer()

定时器消息WM_TIMER

视图类中的OnDraw()中 ,写字CDC::TextOutW()

指定区域写字 CDC::DrawText()

CClientDC::SetTextColor()直接通过RGB设置文字颜色

让窗口失效,产生WM_PINT,间接调用OnDraw()函数 无效的整个工作区 CWnd::Invalidate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void CMFCSdiView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值

if (nIDEvent == timer_1)
{
//static int i = 0;
//i++;
//CString str;
//str.Format(TEXT("%d"), i);
//MessageBox(str);

static int w = 0;
w += 5;

CString str = TEXT("在此添加消息处理程序代码和/或调用默认值");
//获取字符串的长度(宽度)
CClientDC dc(this);
CSize size = dc.GetTextExtent(str);

int x = 400;
int y = 20;

//如果宽度大于字符串的宽度,重新开始
if (w > (size.cx+10))
{
w = 0;
////获取背景色
//COLORREF color = dc.GetBkColor();
////设置写字的颜色
//COLORREF oldcolor = dc.SetTextColor(color);

////白色重写一次 覆盖在之前写的字体上
//dc.TextOutW(x, y, str);

//重新刷新窗口
Invalidate();

}
//黑色写一次
dc.TextOutW(x, y, str);
//设置字体颜色
dc.SetTextColor(RGB(255, 0, 0));
CRect rec(x, y, x + w, y + size.cy);
dc.DrawText(str, rec, DT_LEFT);

}

CView::OnTimer(nIDEvent);
}

更新数据

1
2
3
UpdateData(true);
//修改数据
UpdateData(false);