书籍:《Visual C++ 2017从入门到精通》的4.2 按钮控件
环境:visual studio 2022
内容:[例 4.2]制作图片按钮
说明:以上内容大部分来自腾讯元宝。
在 MFC 中,PreSubclassWindow()
是窗口子类化过程中的关键函数,其调用时机与窗口创建方式密切相关。以下是其调用时机的详细分析:
1. 窗口子类化的基本流程
MFC 中窗口的创建和子类化流程如下:
- 窗口句柄创建:通过
CreateWindowEx
或Create
函数创建原生 Windows 窗口。 - 对象关联:将窗口句柄(HWND)附加到 MFC 的
CWnd
派生类对象(如CButton
)。 - 子类化处理:在此过程中,MFC 调用
PreSubclassWindow()
,允许开发者修改窗口属性(如样式、消息处理函数)。
2. PreSubclassWindow()
的调用时机
(1) 通过 CWnd::Create
或 CreateEx
创建窗口
- 调用条件:当通过代码直接创建窗口(如
CButton::Create
)时,MFC 在窗口附加到CWnd
对象后立即调用PreSubclassWindow()
。 - 典型场景:
CMyButton m_btn; m_btn.Create(_T("按钮"), WS_CHILD | WS_VISIBLE, CRect(0,0,100,30), this, 1);
- 此时
PreSubclassWindow()
被调用,允许修改按钮样式(如设置BS_OWNERDRAW
)。
- 此时
(2) 通过对话框模板创建控件
- 调用条件:当控件通过对话框模板定义,并在运行时通过
DDX_Control
或SubclassDlgItem
子类化时,PreSubclassWindow()
会被调用。 - 典型流程:
- 对话框初始化时,系统根据模板创建控件窗口。
- 对话框的
OnInitDialog
调用DoDataExchange
,触发DDX_Control
。 DDX_Control
调用SubclassDlgItem
,此时PreSubclassWindow()
被执行。
- 代码示例:
void CMyDialog::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_BUTTON1, m_btn); }
(3) 动态子类化控件
- 调用条件:通过
SubclassWindow
或SubclassDlgItem
动态替换窗口过程时,PreSubclassWindow()
会被调用。 - 用途:修改控件行为(如拦截消息)或调整样式(如设置透明背景)。
3. 与其他函数的对比
函数 | 调用时机 | 用途 |
---|---|---|
PreCreateWindow | 窗口创建前(调用 CreateEx 时) | 修改 CREATESTRUCT 参数(如窗口类名、扩展样式) |
PreSubclassWindow | 窗口子类化前(附加到 CWnd 后) | 修改窗口样式或安装钩子 |
OnCreate | 窗口创建后(处理 WM_CREATE ) | 初始化控件(如设置字体、定时器) |
关键区别:
- 对话框控件:通过模板创建的控件不会触发
PreCreateWindow
,但会触发PreSubclassWindow
。 - 直接创建控件:通过代码调用
Create
时,PreCreateWindow
和PreSubclassWindow
均会被调用。
4. 典型应用场景
(1) 修改控件样式
在 PreSubclassWindow
中设置 BS_OWNERDRAW
,实现自绘按钮:
void CMyButton::PreSubclassWindow() {
ModifyStyle(0, BS_OWNERDRAW); // 启用自绘模式
CButton::PreSubclassWindow();
}
(2) 动态调整控件属性
根据父窗口尺寸调整控件大小:
void CMyButton::PreSubclassWindow() {
CWnd* pParent = GetParent();
if (pParent) {
pParent->GetClientRect(&m_initialRect);
}
CButton::PreSubclassWindow();
}
(3) 拦截消息
通过修改窗口过程(WndProc)拦截特定消息:
void CMyButton::PreSubclassWindow() {
WNDPROC oldProc = (WNDPROC)SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)MyCustomProc);
m_pOldProc = oldProc; // 保存原窗口过程
CButton::PreSubclassWindow();
}
5. 注意事项
- 调用顺序:
PreSubclassWindow
在窗口句柄有效后调用,此时可安全操作窗口属性。 - 基类调用:若重写此函数,需显式调用基类实现(
CWnd::PreSubclassWindow()
),否则可能破坏 MFC 的子类化流程。 - 对话框控件:对话框中的控件通过模板创建时,
PreSubclassWindow
是唯一可修改样式的入口。
总结
PreSubclassWindow()
是 MFC 中窗口子类化的核心扩展点,其调用时机集中在以下两种场景:
- 代码直接创建控件:通过
Create
或CreateEx
触发。 - 对话框模板控件:通过
DDX_Control
或SubclassDlgItem
动态子类化时触发。
开发者应优先在此函数中完成样式修改和消息处理,以确保兼容性和灵活性。