MFC中的静态子类化

书籍:《Visual C++ 2017从入门到精通》的4.2 按钮控件

环境:visual studio 2022

内容:[例 4.4]实现一个三角按钮

说明:以上内容大部分来自腾讯元宝。

问题:以下类中没有替换窗口函数,但是重写了PreSubclassWindow()和DrawItem(),那这不是子类化过程吧?

#pragma once
#include 


//该类继承自 CButton,旨在实现 ​​三角形形状的自定义按钮​​,核心功能包括:
//​​形状控制​​:通过 POINTDIRECTION 枚举定义三角形指向(上 / 下 / 左 / 右)。
//​​区域限制​​:使用 CRgn 定义按钮的有效点击区域,避免误触。
//​​自定义绘制​​:重写 DrawItem 实现三角形图形渲染。
class CTriangleButton :
    public CButton
{
public:
    //​​用途​​:控制三角形绘制方向,影响顶点坐标计算和点击区域判断。
    enum POINTDIRECTION
    {
        POINT_UP,           // 三角形顶点朝上
        POINT_DOWN,         // 顶点朝下
        POINT_LEFT,         // 顶点朝左
        POINT_RIGHT         // 顶点朝右
    };

    //决定三角形绘制方向,需与 DrawItem 中的顶点计算逻辑关联。
    POINTDIRECTION PointDirection;      // 三角形指向方向
    //通过 CRgn 定义按钮的点击区域,仅在该区域内响应鼠标事件。
    CRgn CurrentRegion;                 // 当前按钮的有效区域(三角形区域)

    CTriangleButton();
    virtual ~CTriangleButton();
    
public:
    //功能​​:在窗口子类化前执行初始化操作。
    virtual void PreSubclassWindow();

    //功能​​:自定义按钮绘制逻辑。
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
  
};

在 MFC 中,​CTriangleButton 类的实现属于子类化过程​​,但其子类化方式属于 ​静态子类化(通过派生类重写消息处理函数)​​,而非动态替换窗口过程(WndProc)。以下是详细分析:


一、子类化的两种形式

  1. 动态子类化

    • ​定义​​:通过代码在运行时替换窗口的 WndProc 函数地址(如 SetWindowLong),拦截所有消息。
    • ​特点​​:灵活性高,适用于动态修改控件行为。
    • ​示例​​:
      WNDPROC oldProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (LONG)MyProc);
  2. 静态子类化​

    • ​定义​​:通过继承基类(如 CButton)并重写其虚函数(如 DrawItemPreSubclassWindow),利用 MFC 消息映射机制处理特定消息。
    • ​特点​​:依赖框架的消息分发机制,无需手动替换 WndProc
    • ​示例​​:
      class CMyButton : public CButton {
          virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) override;
      };

二、CTriangleButton 的子类化实现分析

1. ​​静态子类化的特征​
  • ​继承与虚函数重写​​:
    CTriangleButton 继承自 CButton,并重写了 PreSubclassWindow 和 DrawItem,这是 MFC 静态子类化的典型做法。
  • ​消息映射机制​​:
    MFC 通过消息映射宏(如 ON_WM_DRAWITEM)将消息与虚函数关联,无需手动管理消息队列或窗口过程。
2. ​​关键函数的作用​
  • PreSubclassWindow

    • ​用途​​:在窗口被子类化前执行初始化操作(如设置自绘样式 BS_OWNERDRAW)。
    • ​代码示例​​:
      void CTriangleButton::PreSubclassWindow() {
          CButton::PreSubclassWindow();
          ModifyStyle(0, BS_OWNERDRAW);  // 启用自绘
      }
    • ​与子类化的关系​​:此函数是 MFC 子类化流程的一部分,用于在窗口创建前调整属性,但本身不涉及窗口过程替换。
  • DrawItem

    • ​用途​​:实现自绘逻辑(如三角形渲染)。
    • ​代码示例​​:
      void CTriangleButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {
          CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
          // 自定义三角形绘制代码
      }
    • ​与消息映射的关系​​:通过 ON_WM_DRAWITEM 宏将 WM_DRAWITEM 消息映射到 DrawItem 函数。
3. ​​未替换窗口过程的原因​
  • ​依赖 MFC 框架​​:MFC 通过派生类和消息映射机制自动处理消息分发,开发者无需直接操作 WndProc
  • ​功能实现方式​​:通过重写虚函数(如 DrawItem)即可完成自定义绘制,通过 PreSubclassWindow 设置控件属性,已满足需求。

三、子类化的判定标准

​特征​ ​动态子类化​ ​静态子类化(如 CTriangleButton)​
​窗口过程替换​ 是(通过 SetWindowLong
​消息处理方式​ 直接拦截所有消息 通过消息映射处理特定消息
​实现复杂度​ 高(需手动管理消息链) 低(依赖 MFC 框架)
​灵活性​ 高(动态修改行为) 中(编译时确定功能)

四、CTriangleButton 的子类化流程

  1. ​控件创建阶段​

    • 对话框模板中放置一个普通按钮,通过 DDX 控件绑定到 CTriangleButton 对象。
    • 对话框初始化时,MFC 自动调用 PreSubclassWindow,设置 BS_OWNERDRAW 样式。
  2. ​消息处理阶段​

    • 当按钮需要重绘时,框架发送 WM_DRAWITEM 消息,调用 DrawItem 实现自绘。
    • 其他消息(如 WM_LBUTTONDOWN)由基类 CButton 的默认处理函数处理。

五、总结

  • CTriangleButton 属于子类化​​,但属于 ​​静态子类化​​,通过继承和消息映射实现功能扩展。
  • ​未替换窗口过程​​:其功能通过重写虚函数(如 DrawItem)和 MFC 框架的消息分发机制完成,无需手动操作 WndProc
  • ​设计合理性​​:对于界面定制需求(如自绘按钮),静态子类化是更简洁高效的实现方式。

你可能感兴趣的:(Visual,C++,2017,从入门到精通,MFC,子类化,静态子类化)