MFC 入门:从空窗口到一个能交互的小程序

本文从一个空窗口开始,逐步加入输入框、按钮和结果文字,再实现缩放布局、配色和按钮事件。读者只需要 C/C++ 基础,无需提前了解 Windows 窗口编程。

最终程序的操作很简单:输入一段文字,点击"显示文字",窗口显示输入结果;拖动窗口边框时,输入框随窗口变宽,按钮保持在右侧。

示例运行于 Windows,需要支持 MFC 的 C++ 编译工具链、MFC 库和 Windows SDK。本文直接编辑代码和资源文件,重点说明窗口、控件和消息的工作过程。

目录

  1. 创建并运行空窗口
  2. 加入输入框、按钮和结果文字
  3. 让控件随窗口调整布局
  4. 设置窗口背景与控件颜色
  5. 让按钮读取输入并显示结果
  6. 完整代码与阶段工程
  7. 线程、定时器与界面更新
  8. 常用类与常用方法

各阶段沿用同一个工程。文中的"替换"表示用新实现替换原有代码;"新增"表示保留已有代码并插入指定位置。配套目录"分阶段示例"保存了每一步的完整工程,可以单独编译。

一、创建并运行空窗口

1.1 准备环境和文件

先准备一个支持 MFC 的 Windows 桌面程序工程,采用 Unicode 字符集。配套示例已经包含相应配置,也可以在已有的空工程中加入以下三个文件:

文件 本阶段的作用
resource.h 给对话框资源定义一个编号
main.rc 描述窗口标题、字体、大小和样式
main.cpp 创建窗口对象,启动对话框

1.2 resource.h:给窗口一个资源编号

在 resource.h 中写入完整内容:

cpp 复制代码
#ifndef MFC_HELLO_RESOURCE_H
#define MFC_HELLO_RESOURCE_H

#define IDD_DEMO 100

#endif

#define 是 C/C++ 预处理指令。这里把 IDD_DEMO 定义为整数 100,资源文件和 C++ 文件通过同一个编号引用该窗口模板。100 是本例选定的编号,没有"第 100 个窗口"这样的含义。IDD_ 是对话框资源常用的命名前缀。

#ifndef、#define、#endif 组成头文件包含保护,避免同一头文件重复展开。

1.3 main.rc:描述一个空窗口

在 main.rc 中写入完整内容:

rc 复制代码
#pragma code_page(65001)
#include <afxres.h>
#include "resource.h"

IDD_DEMO DIALOGEX 0, 0, 300, 125
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "MFC 入门示例"
FONT 10, "Microsoft YaHei UI"
BEGIN
END

这是资源脚本,由资源编译器读取,编译后的资源会链接进 EXE。程序运行时读取 EXE 内的资源,不需要另外加载这个 .rc 文件。

从 IDD_DEMO DIALOGEX 开始,是编号为 IDD_DEMO 的对话框模板。0, 0, 300, 125 依次表示资源中的 X、Y、宽、高,单位为 DLU(对话框单位),会按字体换算成实际尺寸。顶层窗口最终放到屏幕上的位置,还与创建方式和窗口样式有关。

CAPTION 是标题,FONT 10 指定 10 磅字体。BEGIN 和 END 之间放子控件;本阶段留空,因此窗口内没有输入框或按钮。

STYLE 中的常量用按位或 | 组合:DS_SETFONT 使用模板字体,WS_POPUP 创建顶层弹出式窗口,WS_CAPTION 提供标题栏,WS_SYSMENU 提供系统菜单,WS_MINIMIZEBOX 提供最小化按钮,WS_THICKFRAME 提供可拖动的缩放边框。EXSTYLE WS_EX_APPWINDOW 设置任务栏相关的扩展样式。

#pragma code_page(65001) 指定资源文本编码为 UTF-8。afxres.h 提供 MFC/Windows 资源常量,resource.h 提供本例的编号。

1.4 main.cpp:创建并显示对话框

在 main.cpp 中写入完整内容:

cpp 复制代码
/** @file main.cpp @brief 第一阶段:创建空窗口。 */
#include <afxwin.h>
#include "resource.h"

/** @brief 仅创建一个空对话框。 */
class DemoDialog : public CDialog
{
public:
    DemoDialog() : CDialog(IDD_DEMO) {}
};

/** @brief 应用类,MFC 启动时调用 InitInstance。 */
class DemoApp : public CWinApp
{
public:
    /** @brief 显示主对话框,关闭后结束应用。 */
    BOOL InitInstance() override
    {
        CWinApp::InitInstance();
        DemoDialog dialog;
        m_pMainWnd = &dialog;
        dialog.DoModal();
        m_pMainWnd = nullptr;
        return FALSE;
    }
};

DemoApp the_app;

这个文件中有两个类。DemoDialog 继承 CDialog,表示本例的窗口;DemoApp 继承 CWinApp,负责应用启动。

创建 DemoDialog 对象时,C++ 调用构造函数。初始化列表中的 CDialog(IDD_DEMO) 把对话框资源编号交给父类,此时主要是在准备 C++ 对象。实际窗口在后面的 dialog.DoModal() 中创建。

MFC 程序的 Windows 入口由框架提供。本例定义全局应用对象 the_app,MFC 启动流程会调用这个对象重写的虚函数 InitInstance()。override 让编译器检查虚函数重写是否匹配。

沿 InitInstance() 的函数体往下读:

  1. CWinApp::InitInstance() 显式调用父类的初始化实现,:: 指定所属类。
  2. DemoDialog dialog; 构造窗口的 C++ 对象。
  3. m_pMainWnd = &dialog; 把它登记为应用主窗口,& 取得对象地址;m_pMainWnd 是从 CWinApp 继承的成员。
  4. dialog.DoModal() 创建对话框及其子控件,显示窗口,并在内部处理消息。它在对话框关闭之后返回;这里的"模态"表示调用代码等待该对话框结束。
  5. 返回后将 m_pMainWnd 清空,避免保存即将失效的局部对象地址。return FALSE 告诉 MFC 本次初始化后不再进入应用的主消息循环,程序结束;这时窗口已经关闭。

因此,启动过程是:MFC 入口 → InitInstance() → 构造 dialog → DoModal() 创建窗口。后面添加的初始化、绘制、事件函数,分别在这个窗口的生命周期中被调用。

1.5 编译与运行

把 main.cpp 和 main.rc 加入工程,resource.h 由这两个文件包含。C++ 编译器处理窗口类和应用启动代码,资源编译器处理对话框模板,链接器将它们与 MFC 库组合成可执行程序。

工程使用 Windows GUI 子系统、Unicode 字符集,源码保存为 UTF-8。本文的 main.cpp 直接包含 afxwin.h,没有使用预编译头。配套工程已完成这些设置,生成后运行其中的可执行程序即可。

运行后应出现标题为"MFC 入门示例"的空窗口,可以拖动边框、最小化、点击右上角关闭。后续每次修改后,先保存文件、关闭正在运行的程序,再重新编译和运行。

二、加入输入框、按钮和结果文字

这一阶段先把控件显示出来,并给输入框设置初始内容。按钮暂时只具有普通按钮的外观和按下效果,显示结果的逻辑在第五节添加。

2.1 添加控件编号

用下面内容替换 resource.h:

cpp 复制代码
/** @file resource.h @brief 定义窗口与控件的唯一编号。 */
#ifndef MFC_HELLO_RESOURCE_H
#define MFC_HELLO_RESOURCE_H

#define IDD_DEMO 100
#define IDC_INPUT 1001
#define IDC_SHOW 1002
#define IDC_RESULT 1003

#endif

三个 IDC_ 编号用于区分三个控件。1001、1002、1003 是本例选定的整数,不是控件类型码。同一对话框里需要分别识别的控件使用不同编号,并避开 IDOK、IDCANCEL 等已有约定。

2.2 在 main.rc 中放入控件

用下面内容替换 main.rc:

rc 复制代码
#pragma code_page(65001)
#include <afxres.h>
#include "resource.h"

// 这里的坐标使用对话框单位 DLU,运行后布局由 main.cpp 调整。
IDD_DEMO DIALOGEX 0, 0, 300, 125
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "MFC 入门:输入文字,点击显示"
FONT 10, "Microsoft YaHei UI"
BEGIN
    LTEXT "输入文字后点击按钮,也可以拖动窗口边框。", -1, 10, 10, 280, 20
    EDITTEXT IDC_INPUT, 10, 35, 200, 20, ES_AUTOHSCROLL | WS_TABSTOP
    PUSHBUTTON "显示文字(&S)", IDC_SHOW, 220, 35, 70, 20, WS_TABSTOP
    LTEXT "这里显示结果", IDC_RESULT, 10, 75, 280, 35
END

控件写在对话框模板的 BEGIN 与 END 之间。EDITTEXT 创建编辑框,PUSHBUTTON 创建按钮,LTEXT 创建左对齐的静态文字。

例如 EDITTEXT IDC_INPUT, 10, 35, 200, 20:资源 ID 是 IDC_INPUT,左上角是 (10, 35),宽为 200、高为 20,均为 DLU。ES_AUTOHSCROLL 允许输入较长文字时水平滚动,WS_TABSTOP 让 Tab 键能切换焦点到该控件。

按钮行先写标题,再写 ID、位置和尺寸。显示文字(&S) 中的 &S 设置访问键,通常可以用 Alt+S 操作。第一行说明文字的 ID 为 -1,表示不需要单独识别;结果文字需要被代码修改,所以使用 IDC_RESULT。

使用可视化资源编辑器时,可以拖入编辑框、按钮和静态文字,再设置 ID、标题和位置;编辑器会生成相应的 .rc 描述。本文直接展示资源代码,便于对照控件与资源之间的关系。

2.3 把实际控件关联到 C++ 成员

在 main.cpp 中,保留 DemoApp 和末尾的 the_app,将整个 DemoDialog 类替换为:

cpp 复制代码
/** @brief 第二阶段:显示并关联三个控件。 */
class DemoDialog : public CDialog
{
public:
    DemoDialog() : CDialog(IDD_DEMO) {}

protected:
    /** @brief MFC 在对话框初始化时调用。 */
    BOOL OnInitDialog() override
    {
        CDialog::OnInitDialog();
        m_input.SetWindowText(_T("你好,MFC"));
        return TRUE;
    }

    /** @brief 将实际控件关联到成员对象;exchange 由 MFC 提供。 */
    void DoDataExchange(CDataExchange *exchange) override
    {
        CDialog::DoDataExchange(exchange);
        DDX_Control(exchange, IDC_INPUT, m_input);
        DDX_Control(exchange, IDC_SHOW, m_show);
        DDX_Control(exchange, IDC_RESULT, m_result);
    }

private:
    CEdit m_input;    ///< 输入框包装对象。
    CButton m_show;   ///< 按钮包装对象。
    CStatic m_result; ///< 结果文字包装对象。
};

先区分三样东西:资源模板描述"这里有一个输入框";Windows 根据模板创建实际输入框,它有自己的窗口句柄 HWND;CEdit m_input 是用于操作它的 C++ 包装对象。

CEdit、CButton、CStatic 分别包装编辑框、按钮和静态控件。声明成员对象不会在界面上再生成三个控件。

DoDataExchange 是 MFC 数据交换流程调用的虚函数。参数 exchange 指向框架提供的 CDataExchange 对象,里面携带当前对话框和交换方向等信息。函数先调用父类,再调用 DDX_Control(exchange, IDC_INPUT, m_input):在当前对话框中按 ID 找到实际输入框,并把它关联到 m_input。

关联后,调用 m_input 的方法就会操作实际输入框。按钮和结果文字的两行 DDX_Control 作用相同,只是编号和包装对象不同。

OnInitDialog 也是虚函数。DoModal() 创建实际对话框后,初始化消息经过 MFC 到达这里。先调用 CDialog::OnInitDialog(),父类初始化过程中进行初始数据交换,执行上述关联,然后才调用 m_input.SetWindowText(...) 设置文字。

SetWindowText 是控件继承的库方法,参数是要显示的字符串。_T("你好,MFC") 在本例的 Unicode 配置下生成宽字符字面量,不执行运行时编码转换。return TRUE 表示由对话框管理器按默认规则设置初始焦点。

重新编译运行,应看到输入框内已有"你好,MFC",右边是按钮,下方是结果文字。输入框可以编辑,按钮还没有业务处理代码。

补充:DDX_Control 关联的是控件对象。另一种 DDX_Text 用于控件文字与 CString 等值变量之间的数据交换,通过 UpdateData(TRUE) 读入、UpdateData(FALSE) 写回。本文读取文字时直接调用 GetWindowText,不混用这两条路径。

三、让控件随窗口调整布局

3.1 先确定布局效果

资源文件提供初始位置。窗口创建后,还可以调用 MoveWindow(x, y, width, height) 重新设置控件位置和尺寸。对于子控件,X、Y 相对于父窗口客户区左上角,向右和向下为正;客户区不含标题栏与外边框。这里使用运行时窗口坐标中的像素,与 .rc 的 DLU 不同。

本例的布局规则是:左右各留 16 像素;按钮宽 100;输入框与按钮间隔 16;输入框和结果区域随窗口变宽。

3.2 新增布局函数

在 main.cpp 的 DemoDialog 类中,找到 private:,在三个控件成员声明前插入这个完整函数:

cpp 复制代码
void layout_controls(int width)
    {
        m_input.MoveWindow(16, 50, width - 148, 30);
        m_show.MoveWindow(width - 116, 50, 100, 30);
        m_result.MoveWindow(16, 105, width - 32, 60);
    }

layout_controls 是用户自定义成员函数,由本例的初始化函数和尺寸消息处理函数显式调用。参数 width 表示当前客户区宽度,单位为像素。

每个 MoveWindow 的前两个参数确定左上角,后两个参数确定宽高:

  • 输入框:X=16,Y=50,宽=width - 148,高=30。
  • 按钮:X=width - 116,Y=50,宽=100,高=30。
  • 结果文字:X=16,Y=105,宽=width - 32,高=60。

148 是左边距 16、间隔 16、按钮宽 100、右边距 16 的总和;116 是按钮宽与右边距之和;32 是左右边距之和。例如客户区宽 500 时,输入框宽 352,按钮 X=384,结果区宽 468。

MoveWindow 还有一个控制是否重绘的参数,默认 TRUE,本例省略。布局只使用客户区宽度,所以纵向拉高窗口时,控件的 Y 和高度保持不变。

3.3 初始化时做第一次布局

在 DemoDialog 类的 protected: 中,用以下完整实现替换原来的 OnInitDialog():

cpp 复制代码
BOOL OnInitDialog() override
    {
        // 先执行父类初始化,其中完成初始数据交换与控件关联。
        CDialog::OnInitDialog();
        m_input.SetWindowText(_T("你好,MFC"));
        CRect area;
        GetClientRect(&area);
        layout_controls(area.Width());
        return TRUE;
    }

MFC 仍负责调用 OnInitDialog;我们在函数体内增加三行布局代码。CRect area 声明矩形对象,GetClientRect(&area) 把客户区矩形写进这个对象,参数是输出地址。area.Width() 返回 right - left,再传给 layout_controls。

父类初始化完成后,三个控件已关联,才能安全地通过成员对象移动它们。

3.4 窗口变化时重新布局

仍在 DemoDialog 类的 protected: 中,放在已有函数之后、private: 之前,新增两个完整消息处理函数和消息映射声明:

cpp 复制代码
afx_msg void OnSize(UINT type, int width, int height)
    {
        CDialog::OnSize(type, width, height);
        if (type != SIZE_MINIMIZED && m_input.GetSafeHwnd() != nullptr)
        {
            layout_controls(width);
        }
    }

afx_msg void OnGetMinMaxInfo(MINMAXINFO *info)
    {
        CDialog::OnGetMinMaxInfo(info);
        info->ptMinTrackSize = CPoint(440, 230);
    }

DECLARE_MESSAGE_MAP()

窗口大小改变时,Windows 发送 WM_SIZE,MFC 根据下一段消息映射调用 OnSize。type 是调整类型,如最小化;width、height 是新的客户区宽高,单位为像素。

CDialog::OnSize(...) 显式调用父类处理。实际创建窗口的早期也可能收到尺寸消息,这时 m_input 尚未关联实际控件,因此通过 GetSafeHwnd() 检查窗口句柄。满足"没有最小化、控件已关联"两个条件,才调用布局函数。

OnGetMinMaxInfo 在系统查询窗口尺寸限制时进入。参数 info 指向系统提供的约束结构,我们把它的 ptMinTrackSize 设置为 (440, 230),表示用户拖动时窗口外框的最小宽、高,单位为像素。这样可以避免窗口过小后布局公式产生不合适的宽度。

接着,在 main.cpp 中找到 DemoDialog 类末尾的 };。在它之后、class DemoApp 之前新增完整消息映射:

cpp 复制代码
BEGIN_MESSAGE_MAP(DemoDialog, CDialog)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
END_MESSAGE_MAP()

DECLARE_MESSAGE_MAP() 是类内声明。BEGIN_MESSAGE_MAP 与 END_MESSAGE_MAP 在类外定义 DemoDialog 的映射关系,参数分别是当前类和父类;ON_WM_SIZE、ON_WM_GETMINMAXINFO 登记对应消息的处理入口。

这些宏编译时展开为类成员声明、成员定义和映射数据。类外定义仍属于 DemoDialog,类似 C++ 中在类内声明方法、在类外写 DemoDialog::方法();它们不是在全局位置依次执行的函数调用。afx_msg 是消息处理函数的标记,本身不会建立映射。

本例有两个布局调用位置:初始化时的 OnInitDialog,以及尺寸变化时的 OnSize。这不表示总共只执行两次,拖动窗口时可以连续收到多次尺寸消息。

重新编译运行,横向拉宽窗口:按钮应靠右移动,宽度保持 100;输入框和结果区变宽。纵向拉高时,它们仍位于原来的高度。

3.5 MFC 有没有自动布局机制

MFC 提供 Dynamic Layout,可通过资源编辑器或 CMFCDynamicLayout 配置控件的移动、伸缩比例。本例的规则可以表达为:输入框水平伸缩 100%,按钮水平移动 100%,结果区水平伸缩 100%。父窗口加宽 200 像素,100% 水平伸缩就让控件加宽 200 像素。

它主要按父窗口尺寸的变化量移动、拉伸已有控件。类似 Qt Layout 的嵌套盒式、网格式排列,以及根据内容自动分配空间,MFC 没有完全相同的内置机制,通常需要额外布局代码或布局库。使用动态布局后,应替换同一控件的手工 MoveWindow 规则。

固定像素示例还没有完整处理高 DPI、多语言和长文字。实际项目可用 MapDialogRect(&rect) 将 DLU 矩形换算为当前对话框像素尺寸,并结合 DPI 变化重新计算。

参考:微软 MFC 动态布局。

四、设置窗口背景与控件颜色

本阶段让窗口背景变为浅灰色、输入框变为淡黄色、按钮变为蓝色。窗口和控件各自参与绘制,配色需要进入对应的绘制流程。

4.1 保存背景画刷

在 main.cpp 的 DemoDialog 类中,在 private: 下的控件成员后新增:

cpp 复制代码
CBrush m_background;      // 窗口和静态文字的背景。
CBrush m_edit_background; // 编辑区域的背景。

再用下面实现替换类内原来的构造函数:

cpp 复制代码
DemoDialog()
    : CDialog(IDD_DEMO),
      m_background(RGB(245, 247, 250)),
      m_edit_background(RGB(255, 255, 235))
{
}

构造函数由 C++ 对象构造过程调用,初始化列表创建两把纯色 CBrush。RGB 按红、绿、蓝三个 0~255 的分量生成 Windows 颜色值。成员画刷随对话框对象存在,在对象析构时释放。

画刷用于填充区域。后面的处理函数会把画刷句柄交给系统,所以不能临时创建局部画刷后返回一个已经失效的句柄。

4.2 设置窗口、静态文字和输入框颜色

在 DemoDialog 类的 protected: 中、DECLARE_MESSAGE_MAP() 之前新增完整函数:

cpp 复制代码
afx_msg HBRUSH OnCtlColor(CDC *dc, CWnd *window, UINT type)
    {
        HBRUSH brush = CDialog::OnCtlColor(dc, window, type);

        // 按 ID 识别输入框,也覆盖只读输入框走 STATIC 消息的情况。
        if (window != nullptr && window->GetDlgCtrlID() == IDC_INPUT)
        {
            dc->SetTextColor(RGB(35, 50, 70));
            dc->SetBkColor(RGB(255, 255, 235));
            return static_cast<HBRUSH>(m_edit_background.GetSafeHandle());
        }

        // 当前示例没有列表框,此分支说明标准编辑 / 列表区的处理方式。
        if (type == CTLCOLOR_EDIT || type == CTLCOLOR_LISTBOX)
        {
            dc->SetTextColor(RGB(35, 50, 70));
            dc->SetBkColor(RGB(255, 255, 235));
            return static_cast<HBRUSH>(m_edit_background.GetSafeHandle());
        }

        if (type == CTLCOLOR_DLG || type == CTLCOLOR_STATIC)
        {
            dc->SetBkMode(TRANSPARENT);
            dc->SetTextColor(RGB(35, 80, 150));
            return static_cast<HBRUSH>(m_background.GetSafeHandle());
        }
        return brush;
    }

OnCtlColor 是消息处理函数。系统准备绘制相应控件或对话框时发送颜色消息,由 MFC 分派到这里。三个参数均由框架提供:dc 指向本次绘图上下文 CDC;window 指向需要配色的窗口或控件包装对象;type 表示对话框、静态文字、编辑框等绘制类别。函数返回 HBRUSH,即 Windows 画刷句柄。

先调用 CDialog::OnCtlColor 取得父类默认处理结果,未处理的情况继续返回它。输入框分支通过 window->GetDlgCtrlID() 取得控件编号,和 IDC_INPUT 比较;按 ID 判断也覆盖只读编辑框使用 CTLCOLOR_STATIC 消息的情况。

分支中的 SetTextColor 设置文字颜色,SetBkColor 设置文字背景色;返回的画刷填充控件背景。两种背景使用相同 RGB 值,文字后方和控件空白处才能保持一致。GetSafeHandle() 取得画刷底层句柄,static_cast<HBRUSH> 是显式类型转换。

CTLCOLOR_EDIT、CTLCOLOR_LISTBOX 分支展示标准编辑区和列表框的处理方式,本例没有列表框。CTLCOLOR_DLG 处理对话框背景,CTLCOLOR_STATIC 处理静态文字区域。SetBkMode(TRANSPARENT) 表示绘制文字时不额外填充文字背景,并不使整个窗口透明。这里的背景处理作用于客户区,不包括系统标题栏。

最后,找到类外的消息映射,用下面这段替换;原有的布局映射也一起保留:

cpp 复制代码
BEGIN_MESSAGE_MAP(DemoDialog, CDialog)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

ON_WM_CTLCOLOR() 建立颜色消息到 OnCtlColor 的连接。我们不在初始化函数中手动调用 OnCtlColor,它在相应绘制发生时由框架调用。

重新编译运行,应看到浅灰背景和淡黄色输入框。按钮仍使用原有外观,下一步单独处理它。

4.3 给按钮绘制背景和文字

普通系统主题按钮的表面不按编辑框的方式应用背景画刷。这里使用 owner draw,由自己的 ColorButton 类绘制按钮。

第一步:在 main.rc 中,找到 IDC_SHOW 那一行,替换为:

rc 复制代码
PUSHBUTTON "显示文字(&S)", IDC_SHOW, 220, 35, 70, 20, BS_OWNERDRAW | WS_TABSTOP

BS_OWNERDRAW 让按钮在需要重绘时产生自绘通知。第二步:在 main.cpp 中,放在头文件包含之后、class DemoDialog 之前,新增完整类:

cpp 复制代码
class ColorButton : public CButton
{
protected:
    /** @brief 绘制背景、文字和焦点框;item 由框架传入。 */
    void DrawItem(LPDRAWITEMSTRUCT item) override
    {
        CDC dc;
        dc.Attach(item->hDC); // 临时借用系统提供的绘图句柄。
        CRect area(item->rcItem);

        const bool disabled = (item->itemState & ODS_DISABLED) != 0;
        const bool pressed = (item->itemState & ODS_SELECTED) != 0;
        COLORREF background = RGB(35, 100, 190);
        if (disabled)
        {
            background = RGB(210, 215, 222);
        }
        else if (pressed)
        {
            background = RGB(25, 75, 145);
        }

        dc.FillSolidRect(&area, background);
        dc.SetBkMode(TRANSPARENT);
        dc.SetTextColor(disabled ? RGB(110, 115, 125) : RGB(255, 255, 255));

        CFont *font = GetFont();
        CFont *old_font = nullptr;
        if (font != nullptr)
        {
            old_font = dc.SelectObject(font);
        }

        CString caption;
        GetWindowText(caption);
        dc.DrawText(caption, &area, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        if ((item->itemState & ODS_FOCUS) != 0)
        {
            CRect focus_area(area);
            focus_area.DeflateRect(3, 3);
            dc.DrawFocusRect(&focus_area);
        }

        if (old_font != nullptr)
        {
            dc.SelectObject(old_font);
        }
        dc.Detach(); // 归还句柄,不能销毁系统这次提供的 HDC。
    }
};

第三步:在 DemoDialog 的 private: 中,把原来的 CButton m_show; 替换为 ColorButton m_show;。DoDataExchange 中关联 IDC_SHOW 和 m_show 的那一行继续保留。

DrawItem 重写 CButton 的绘制虚函数。按钮需要重绘时,Windows 发出绘制通知,MFC 将其反射给按钮包装对象,调用这里的实现。item 指向框架提供的绘制结构:hDC 是本次绘图句柄,rcItem 是绘制矩形,itemState 是按钮状态位。

函数体按绘制顺序执行:

  1. CDC dc 创建包装对象,Attach(item->hDC) 临时关联本次绘图句柄,CRect area(item->rcItem) 保存按钮区域。
  2. 通过按位与 & 检查 ODS_DISABLED、ODS_SELECTED,分别判断禁用和按下状态,再选择背景 RGB 值。
  3. FillSolidRect(&area, background) 填充矩形,两个参数为区域地址与颜色。随后设置文字背景模式和文字颜色;三元表达式根据禁用状态选择灰字或白字。
  4. GetFont() 取得按钮字体。SelectObject(font) 将它选入绘图上下文,并返回原字体,保存在 old_font,供绘制结束后恢复。
  5. GetWindowText(caption) 把按钮标题写入 CString。DrawText(caption, &area, flags) 在矩形内绘制标题,三个标志分别表示水平居中、垂直居中、单行。
  6. 有 ODS_FOCUS 时,复制绘制矩形,DeflateRect(3, 3) 把左右、上下边界各向内缩 3 像素,再用 DrawFocusRect 画出键盘焦点框。
  7. 恢复原字体,Detach() 解除包装对象与借用句柄的关联。系统提供的这次绘图句柄由系统管理,不能由临时 CDC 对象销毁。

GetFont()、GetWindowText() 在这个类中省略了调用对象,操作的是当前按钮。窗口类中的 GetClientRect() 同理,操作当前对话框。

重新编译后,按钮正常时为蓝色,按下时为深蓝色。用 Tab 切换焦点到按钮时,应能看到焦点框。此绘制示例覆盖正常、按下、禁用和焦点状态,悬停动画、高对比度等完整主题适配需要进一步设计。

4.4 其他控件怎样配色

配色方法可以用于各类控件,但入口并不相同。添加其他控件后,先关联相应成员,再按以下方式选择接口:

对象 常用处理方式 可以设置的范围
对话框背景 OnCtlColor 的 CTLCOLOR_DLG 分支返回背景画刷 客户区背景,不含系统标题栏
静态文字 CStatic CTLCOLOR_STATIC;SetTextColor、背景画刷 文字颜色和控件区域背景
输入框 CEdit 编辑区颜色消息;按控件 ID 识别 文字、文字背景、编辑区域背景;只读输入框可能走 STATIC 类型
列表框 CListBox CTLCOLOR_LISTBOX;需要逐项配色时使用自绘 列表区域、文字;选中项颜色另行处理
下拉框 CComboBox 分别处理编辑区、显示区和下拉列表;必要时派生控件处理反射消息或自绘 各区域分别配色
普通按钮 CButton BS_OWNERDRAW 与 DrawItem,或增强按钮类 正常、按下、禁用状态的背景与文字
复选框、单选框 自定义绘制或支持主题的增强控件 文本、勾选标记和背景需分别处理,保留勾选与焦点行为
分组框 标题颜色按绘制机制处理;边框需要自定义绘制 标题、边框、区域背景
列表视图 CListCtrl SetBkColor、SetTextBkColor、SetTextColor;逐项颜色用 NM_CUSTOMDRAW 背景、文字背景与文字;选中项、表头另行处理
树控件 CTreeCtrl SetBkColor、SetTextColor;逐项颜色用自定义绘制 树区域和文字;选中项另行处理
富文本 CRichEditCtrl SetBackgroundColor、SetSelectionCharFormat 背景和选定文字的格式、颜色
进度条 CProgressCtrl SetBarColor、SetBkColor 进度条和底色;启用系统主题时,部分设置会被主题覆盖
滑块、滚动条、标签页、日期等控件 各自接口、绘制通知或自定义控件 系统主题决定的区域通常需要专门绘制

公共控件的初始化配色通常写在 OnInitDialog() 调用父类、完成控件关联之后。例如 CListCtrl 的 SetBkColor、SetTextBkColor、SetTextColor 分别接收一个 COLORREF,设置列表区域、文字背景和文字颜色。它们属于列表视图类,不是对话框上所有子控件的统一设置。

列表逐行、树逐节点配色,需要处理 NM_CUSTOMDRAW 等绘制通知;组合框由显示区、编辑区、下拉列表等部分组成,需要分别处理。复选框、单选框和分组框还包含勾选标记、边框等内容,不能直接套用普通按钮的绘制实现。

如果窗口使用 CDialogEx,也可在初始化中调用它的 SetBackgroundColor(color) 设置纯色背景。背景图片和渐变一般通过背景绘制或专用控件实现。

MFC 没有类似 QSS 的统一样式表语言。CMFCVisualManager 可以管理支持它的 MFC 控件、菜单和工具栏;更完整的主题通常由增强控件、自定义绘制或第三方界面库实现。运行中颜色数据改变后,调用目标控件的 Invalidate() 请求重绘;UpdateWindow() 会立即处理已有无效区域,普通刷新通常不必强制立即绘制。

参考:微软 CButton::DrawItem、按钮颜色消息。

五、让按钮读取输入并显示结果

5.1 新增点击处理函数

在 main.cpp 的 DemoDialog 类中,放在 protected: 下、DECLARE_MESSAGE_MAP() 之前,新增完整函数:

cpp 复制代码
afx_msg void on_show_clicked()
    {
        CString text;
        m_input.GetWindowText(text);
        text.Trim();
        // 展开成 if/else,便于初学者逐句阅读。
        if (text.IsEmpty())
        {
            m_result.SetWindowText(_T("请先输入一些文字。"));
        }
        else
        {
            CString result = _T("你输入了:");
            result += text;
            m_result.SetWindowText(result);
        }
    }

on_show_clicked 是本例编写的消息处理函数,通过按钮通知映射由 MFC 调用。它没有参数,也不返回业务结果,结果直接显示在控件上。

CString text 声明字符串对象。m_input.GetWindowText(text) 把实际输入框的当前文字写入 text,参数是输出引用;text.Trim() 去掉首尾空白。IsEmpty() 返回是否为空,据此选择提示文字或拼接结果。

非空分支先创建前缀字符串,result += text 追加输入内容,再把它传给 m_result.SetWindowText(result),修改实际结果控件的文字。操作的是已关联的窗口控件,不是只改变了一个普通字符串变量。

5.2 在消息映射中连接按钮通知

将 DemoDialog 类外原有消息映射替换为完整版本:

cpp 复制代码
BEGIN_MESSAGE_MAP(DemoDialog, CDialog)
    ON_BN_CLICKED(IDC_SHOW, on_show_clicked)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

ON_BN_CLICKED 的第一个参数是按钮 ID,第二个是处理函数。它把 IDC_SHOW 按钮的点击通知连接到 on_show_clicked。

一次点击的过程如下:用户操作按钮 → 按钮向父窗口发送带有 BN_CLICKED 的 WM_COMMAND 通知 → MFC 查找对话框消息映射 → 调用 on_show_clicked() → 函数读取输入并修改结果文字 → 控件重绘。

自绘按钮的 DrawItem 负责画外观,点击通知负责触发业务处理,两者可以在同一个按钮上配合工作。DrawItem 不负责读取输入和执行本例业务逻辑。

5.3 运行检查

重新编译运行,输入"测试输入"并点击按钮,结果应变为"你输入了:测试输入"。清空输入,或只输入空格再点击,应显示"请先输入一些文字。"。拖动窗口边框时,布局仍按第三节的规则变化。

通过这个例子可以区分三种调用:OnInitDialog、DoDataExchange 是框架调用的虚函数;OnSize、OnCtlColor、on_show_clicked 是经消息分派进入的处理函数;layout_controls 是由自己的代码显式调用的普通成员函数。SetWindowText、MoveWindow 等则是处理过程中调用的库方法。

参考:微软消息映射宏。

六、完整代码与阶段工程

配套文件的 mfc_demo 是最终版本。"分阶段示例"中保存了五份独立工程:01_空窗口、02_添加控件、03_自动布局、04_窗口和控件配色、05_按钮事件。每份都包含 resource.h、main.rc、main.cpp 和配套工程配置,可以独立编译运行。

最终的 resource.h 与第二节一致,main.rc 在第二节基础上增加第四节的 BS_OWNERDRAW,编译配置沿用第一节的设置。下面给出最终 main.cpp,用于核对类、函数、消息映射之间的完整位置。

cpp 复制代码
/**
 * @file main.cpp
 * @brief 最小 MFC 对话框示例:控件绑定、按钮事件、布局和颜色。
 */
#include <afxwin.h>

#include "resource.h"

#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

/** @brief 自绘按钮:由 MFC 在按钮重绘时调用 DrawItem。 */
class ColorButton : public CButton
{
protected:
    /** @brief 绘制背景、文字和焦点框;item 由框架传入。 */
    void DrawItem(LPDRAWITEMSTRUCT item) override
    {
        CDC dc;
        dc.Attach(item->hDC); // 临时借用系统提供的绘图句柄。
        CRect area(item->rcItem);

        const bool disabled = (item->itemState & ODS_DISABLED) != 0;
        const bool pressed = (item->itemState & ODS_SELECTED) != 0;
        COLORREF background = RGB(35, 100, 190);
        if (disabled)
        {
            background = RGB(210, 215, 222);
        }
        else if (pressed)
        {
            background = RGB(25, 75, 145);
        }

        dc.FillSolidRect(&area, background);
        dc.SetBkMode(TRANSPARENT);
        dc.SetTextColor(disabled ? RGB(110, 115, 125) : RGB(255, 255, 255));

        CFont *font = GetFont();
        CFont *old_font = nullptr;
        if (font != nullptr)
        {
            old_font = dc.SelectObject(font);
        }

        CString caption;
        GetWindowText(caption);
        dc.DrawText(caption, &area, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        if ((item->itemState & ODS_FOCUS) != 0)
        {
            CRect focus_area(area);
            focus_area.DeflateRect(3, 3);
            dc.DrawFocusRect(&focus_area);
        }

        if (old_font != nullptr)
        {
            dc.SelectObject(old_font);
        }
        dc.Detach(); // 归还句柄,不能销毁系统这次提供的 HDC。
    }
};

/** @brief 一个输入框、一个自绘按钮和一段结果文字。 */
class DemoDialog : public CDialog
{
  public:
    /** @brief 指定使用哪个对话框资源,并创建背景画刷。 */
    DemoDialog()
        : CDialog(IDD_DEMO),
          m_background(RGB(245, 247, 250)),
          m_edit_background(RGB(255, 255, 235))
    {
    }

  protected:
    /** @brief 窗口创建后初始化控件。@return TRUE 表示由框架设置初始焦点。 */
    BOOL OnInitDialog() override
    {
        // 先执行父类初始化,其中完成初始数据交换与控件关联。
        CDialog::OnInitDialog();
        m_input.SetWindowText(_T("你好,MFC"));
        CRect area;
        GetClientRect(&area);
        layout_controls(area.Width());
        return TRUE;
    }

    /** @brief 将资源中的控件绑定到成员变量。@param[in,out] exchange 框架的数据交换对象。 */
    void DoDataExchange(CDataExchange *exchange) override
    {
        CDialog::DoDataExchange(exchange);
        DDX_Control(exchange, IDC_INPUT, m_input);
        DDX_Control(exchange, IDC_SHOW, m_show);
        DDX_Control(exchange, IDC_RESULT, m_result);
    }

    /** @brief 用户点击"显示文字"按钮后执行。 */
    afx_msg void on_show_clicked()
    {
        CString text;
        m_input.GetWindowText(text);
        text.Trim();
        // 展开成 if/else,便于初学者逐句阅读。
        if (text.IsEmpty())
        {
            m_result.SetWindowText(_T("请先输入一些文字。"));
        }
        else
        {
            CString result = _T("你输入了:");
            result += text;
            m_result.SetWindowText(result);
        }
    }

    /** @brief 窗口大小改变时更新布局。@param[in] type 调整类型。@param[in] width 客户区宽度,像素。@param[in] height 客户区高度,像素。 */
    afx_msg void OnSize(UINT type, int width, int height)
    {
        CDialog::OnSize(type, width, height);
        if (type != SIZE_MINIMIZED && m_input.GetSafeHwnd() != nullptr)
        {
            layout_controls(width);
        }
    }

    /** @brief 限制窗口最小尺寸。@param[in,out] info 窗口尺寸约束,像素。 */
    afx_msg void OnGetMinMaxInfo(MINMAXINFO *info)
    {
        CDialog::OnGetMinMaxInfo(info);
        info->ptMinTrackSize = CPoint(440, 230);
    }

    /** @brief 处理窗口背景及标准控件颜色;由 MFC 消息分派调用。 */
    afx_msg HBRUSH OnCtlColor(CDC *dc, CWnd *window, UINT type)
    {
        HBRUSH brush = CDialog::OnCtlColor(dc, window, type);

        // 按 ID 识别输入框,也覆盖只读输入框走 STATIC 消息的情况。
        if (window != nullptr && window->GetDlgCtrlID() == IDC_INPUT)
        {
            dc->SetTextColor(RGB(35, 50, 70));
            dc->SetBkColor(RGB(255, 255, 235));
            return static_cast<HBRUSH>(m_edit_background.GetSafeHandle());
        }

        // 当前示例没有列表框,此分支说明标准编辑 / 列表区的处理方式。
        if (type == CTLCOLOR_EDIT || type == CTLCOLOR_LISTBOX)
        {
            dc->SetTextColor(RGB(35, 50, 70));
            dc->SetBkColor(RGB(255, 255, 235));
            return static_cast<HBRUSH>(m_edit_background.GetSafeHandle());
        }

        if (type == CTLCOLOR_DLG || type == CTLCOLOR_STATIC)
        {
            dc->SetBkMode(TRANSPARENT);
            dc->SetTextColor(RGB(35, 80, 150));
            return static_cast<HBRUSH>(m_background.GetSafeHandle());
        }
        return brush;
    }

    // 类里面:声明消息映射相关成员。
    DECLARE_MESSAGE_MAP()

  private:
    /** @brief 根据窗口宽度摆放控件。@param[in] width 客户区宽度,像素。 */
    void layout_controls(int width)
    {
        m_input.MoveWindow(16, 50, width - 148, 30);
        m_show.MoveWindow(width - 116, 50, 100, 30);
        m_result.MoveWindow(16, 105, width - 32, 60);
    }

    CEdit m_input;        ///< 输入框。
    ColorButton m_show;   ///< 自绘按钮。
    CStatic m_result;     ///< 结果文字。
    CBrush m_background;      ///< 对话框及静态文字背景。
    CBrush m_edit_background; ///< 输入框及标准列表区背景。
};

// 类定义已经结束。下面在类外定义 DemoDialog 的消息映射。
// 宏在编译前展开,不是运行时依次调用的普通函数。
BEGIN_MESSAGE_MAP(DemoDialog, CDialog)
    ON_BN_CLICKED(IDC_SHOW, on_show_clicked)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

/** @brief MFC 应用类;框架启动流程调用它的 InitInstance。 */
class DemoApp : public CWinApp
{
  public:
    /** @brief 创建并显示窗口。@return FALSE 表示窗口关闭后结束应用。 */
    BOOL InitInstance() override
    {
        CWinApp::InitInstance();
        DemoDialog dialog;
        m_pMainWnd = &dialog;
        dialog.DoModal();
        m_pMainWnd = nullptr; // 对话框关闭后,不保留即将失效的对象地址。
        return FALSE;
    }
};

DemoApp the_app;

文件顶部的 #pragma comment(linker, "/manifestdependency:...") 向链接器声明新版公共控件依赖,用于启用相应视觉样式。它是构建指令,不是运行时函数。

实际项目通常把窗口类声明放进 .h,把成员函数定义和消息映射放进 .cpp。本例把类和实现放在一个文件里,便于直接查看全部位置;调用关系与拆分文件后的写法相同。

七、线程、定时器与界面更新

7.1 长任务为什么会阻塞界面

按钮处理、窗口布局和绘制都在界面线程中执行。如果在按钮处理函数里读取大文件、执行长时间计算或长时间休眠,窗口就无法及时处理其他消息,表现为拖动卡顿、按钮失去响应。

读取大文件、批量处理图片等耗时任务应放到工作线程,界面线程只做短时间的状态读取和显示更新。

7.2 MFC 线程与 fork 的区别

fork 创建新进程;MFC 工作线程仍在同一个进程内,线程之间共享地址空间,各自有执行流程和栈。MFC 通常用 AfxBeginThread 创建线程,线程对象为 CWinThread。

工作线程入口通常采用 UINT worker(LPVOID context) 这样的形式,创建线程时把函数地址和上下文地址交给 AfxBeginThread,之后由线程运行机制调用入口。context 是调用方提供的指针,用来传入共享状态;返回值表示线程退出代码。

这里不向前面的最小程序加入线程。完整线程示例还需要设计停止请求、共享数据同步、退出等待和窗口关闭时的资源释放,只有一个启动调用不足以构成可用实现。

7.3 工作线程怎样通知界面

常见方式是:工作线程完成一批数据处理,把结果安全地放入共享缓冲区,再调用窗口的 PostMessage(message, wParam, lParam) 投递自定义消息。第一个参数是消息编号,后两个是消息参数。投递成功只表示消息进入队列,不表示界面已经处理完成。

窗口类声明对应的消息处理函数,在类外消息映射中用 ON_MESSAGE 连接;之后由界面线程处理消息、读取数据、更新控件。跨线程直接操作 MFC 控件对象容易出现线程归属和生命周期问题,应通过界面线程完成更新。

共享数据需要同步;停止时要处理仍在队列中的通知。若用消息参数携带指针,发送方和接收方还必须约定对象所有权、释放位置及投递失败的处理。

7.4 窗口定时器适合做什么

窗口的 SetTimer(id, interval_ms, nullptr) 创建定时器,参数分别为定时器编号、请求间隔毫秒数、空回调指针。使用这种形式时,系统产生 WM_TIMER,MFC 根据 ON_WM_TIMER() 分派到 OnTimer(UINT_PTR id),其参数用于区分定时器;KillTimer(id) 停止它。

启动可以放在 OnInitDialog() 或按钮处理函数中;ON_WM_TIMER() 放在同一窗口类的类外消息映射中;OnTimer 放在该窗口类的消息处理函数位置。这里只说明接入位置,前面完整示例没有启用定时器。

定时器处理仍在界面线程执行,不会自动变成后台任务,也不保证严格实时。它适合短时间的状态刷新;批量任务可以由工作线程执行,界面定期读取最新进度并显示。

参考:微软创建工作线程。

八、常用类与常用方法

下面用于查阅常见操作。窗口和控件的大部分方法由程序显式调用;初始化、消息处理和绘制入口则由框架在对应流程中调用。

8.1 窗口与对话框:CWnd、CDialog、CWinApp

多数控件继承 CWnd,因此窗口文字、位置、可见性等方法可以用于多种控件。

类 常用方法 用途
CWnd SetWindowText(text) / GetWindowText(text) 设置、读取窗口或控件文字
CWnd MoveWindow(x, y, w, h) 设置位置与尺寸
CWnd SetWindowPos(...) 调整位置、尺寸、层级等
CWnd ShowWindow(SW_SHOW / SW_HIDE) 显示或隐藏
CWnd EnableWindow(TRUE / FALSE) 启用或禁用输入
CWnd GetClientRect(&rect) 取得客户区范围
CWnd GetDlgItem(id) 查找子控件,使用前检查结果
CWnd GetSafeHwnd() 取得关联的 Windows 窗口句柄
CWnd SetFont(font) / GetFont() 设置、取得控件字体
CWnd Invalidate() / UpdateWindow() 请求重绘 / 立即处理无效区域
CWnd SetTimer(id, ms, nullptr) / KillTimer(id) 启动、停止窗口定时器
CWnd PostMessage(msg, wParam, lParam) 异步投递窗口消息
CDialog DoModal() / Create(...) 创建模态 / 非模态对话框
CDialog EndDialog(result) 结束模态对话框
CDialog UpdateData(TRUE / FALSE) 经 DDX 读入 / 写回成员值
CDialog OnInitDialog()、DoDataExchange(...) 框架调用的初始化、数据交换虚函数
CWinApp InitInstance() / ExitInstance() 框架调用的启动、退出虚函数

DoModal() 由程序调用;OnInitDialog() 由框架在对话框初始化时调用。方法属于哪个类与谁负责调用,是阅读 MFC 代码时需要区分的两件事。

8.2 输入与选择控件

类 常用方法 用途
CEdit SetWindowText / GetWindowText 设置、读取内容,继承自 CWnd
CEdit SetSel(start, end) / ReplaceSel(text) 选择、替换一段文字
CEdit SetReadOnly(TRUE) 设置只读
CEdit LimitText(count) 限制可输入的文字长度
CButton SetCheck(BST_CHECKED) / GetCheck() 设置、读取复选框或单选框状态
CButton SetBitmap(...) / SetIcon(...) 设置相应样式按钮的位图或图标
CButton DrawItem(...) 自绘按钮的绘制虚函数,由框架调用
CStatic SetWindowText(text) 显示文字
CStatic SetBitmap(...) / SetIcon(...) 显示对应类型的位图或图标
CComboBox AddString(text) 添加选项
CComboBox SetCurSel(index) / GetCurSel() 设置、取得当前选择
CComboBox GetLBText(index, text) 取得指定选项文字
CComboBox ResetContent() 清空选项
CListBox AddString / InsertString / DeleteString 增加、插入、删除列表项
CListBox GetCurSel() / GetText(index, text) 取得单选位置与项目内容
CListBox GetSelItems(...) 取得多选列表中的选中项
CListBox ResetContent() 清空列表

SetCheck 用于复选框、单选框等带状态的按钮。普通按钮的点击由通知处理函数接收,不能用 GetCheck() 判断是否刚刚点击。

8.3 列表、树、进度与滑块

公共控件通常需要包含 <afxcmn.h>;具体控件还需相应初始化及资源定义。

类 常用方法 用途
CListCtrl InsertColumn / InsertItem / SetItemText 添加列、行及子项文字
CListCtrl GetItemText / GetItemCount 读取内容和行数
CListCtrl SetExtendedStyle(...) 配置网格线、整行选择等扩展样式
CListCtrl SetBkColor / SetTextBkColor / SetTextColor 设置区域与文字配色
CTreeCtrl InsertItem / Expand 添加、展开或折叠节点
CTreeCtrl GetSelectedItem / GetItemText 取得选中节点及文字
CTreeCtrl SetBkColor / SetTextColor 设置背景与文字颜色
CProgressCtrl SetRange32(min, max) / SetPos(value) 设置范围和进度
CProgressCtrl SetStep(step) / StepIt() 设置并执行一步增量
CProgressCtrl SetBarColor / SetBkColor 设置颜色,受主题机制限制
CSliderCtrl SetRange / SetPos / GetPos 设置范围、设置或取得当前位置
CSliderCtrl SetTicFreq(...) 设置刻度间隔

8.4 字符串:CString

常用方法或操作 用途
IsEmpty() / Empty() 判断为空 / 清空字符串
GetLength() 取得字符代码单元数量
Trim() 去掉首尾空白
Format(format, ...) 按格式串生成文字,参数类型需匹配
Find(text) 查找子串,未找到时返回 -1
Left(count) / Mid(start, count) / Right(count) 截取子串
Replace(old, replacement) 替换匹配内容
Compare / CompareNoCase 比较字符串,后者忽略大小写
+= 追加字符串

本例 Unicode 构建中的 CString 使用宽字符。CStringW 和 CStringA 分别明确宽、窄字符类型;窄字符串不自动等于 UTF-8。_T("文字") 是字符集宏,不执行运行时编码转换。

8.5 绘图、颜色与图片

类 常用方法 用途
CDC SetTextColor / SetBkColor / SetBkMode 设置文字及背景绘制状态
CDC FillSolidRect / Rectangle / MoveTo / LineTo 填充矩形、绘制形状和线条
CDC TextOut / DrawText 绘制文字,DrawText 支持对齐、换行等格式
CDC SelectObject(...) 选择字体、画笔、画刷等,使用后恢复原对象
CDC Attach / Detach 关联、解除底层绘图句柄;注意句柄归属
CPaintDC 构造与析构 在 OnPaint 中管理 BeginPaint / EndPaint 流程
CBrush 纯色构造 / CreateSolidBrush 创建填充画刷
CBrush GetSafeHandle() 取得底层画刷句柄
CPen 构造 / CreatePen 创建线型、宽度和颜色指定的画笔
CFont CreatePointFont(...) 创建字体,字号参数单位为 1/10 磅
CImage Load(path) / Save(path) / Draw(...) 加载、保存和绘制图片,使用前包含 <atlimage.h>
CBitmap LoadBitmap(id) / GetBitmap(...) 加载资源位图并取得位图信息

在 OnPaint() 中使用 CPaintDC 绘图。OnCtlColor 或 DrawItem 传入的绘图上下文由系统管理,按对应入口的句柄生命周期使用。CImage::Load 返回 HRESULT,先判断加载结果,再使用图像尺寸或执行绘制。

8.6 几何、文件和容器

类 常用方法或成员 用途
CPoint x、y、Offset(...) 表示点坐标并平移
CSize cx、cy 表示宽高或偏移量
CRect Width / Height / DeflateRect / PtInRect 取得尺寸、缩进矩形、判断点是否在内部
CFile Open / Read / Write / Seek / Close 二进制文件读写与定位
CStdioFile ReadString / WriteString 文本流读写,内容编码需明确
CArray / CStringArray Add / GetSize / RemoveAt 动态数组操作
CMap SetAt / Lookup / RemoveKey 哈希映射的写入、查询与移除

标准 C++ 的 std::vector、std::map、std::unordered_map 和文件流同样可以用于 MFC 工程。MFC 的 CMap 是哈希映射,与有序的 std::map 数据结构不同。

相关推荐
嘟哩DuliDuli1 天前
剧本节点输入归一化:文本来源、版本与下游引用
android·人工智能·安全·ai·软件工程
qiyongwork1 天前
泛金融IT 研发管理平台设计
金融·项目管理·软件工程·研发管理
闻缺陷则喜何志丹1 天前
vc入门宝典(九)
数据结构·list·mfc·存储·null·class
我命由我123452 天前
软件工程概念 - 可插拔性
java·后端·学习·java-ee·系统架构·软件工程·学习方法
信可维2 天前
AI 读 Word 表格总读错?先查合并单元格和表头这 3 处
开发语言·人工智能·c#·word·软件工程
闻缺陷则喜何志丹4 天前
VC入门宝典
dialog·mfc·优化·文档·application·command
Teable任意门互动4 天前
项目管理数字化落地案例|任意门互动科技(Teable 官方合作伙伴)以多维表格搭建项目管理平台
人工智能·开源·软件工程·飞书·开源软件·数据库开发
Allen-Steven4 天前
P/V操作详解与相关考点(软考知识点)
软件工程
嘟哩DuliDuli5 天前
AI创作工作区的项目上下文与多模态任务边界
android·人工智能·安全·ai·软件工程