前言
通过前面介绍的:
在MFC中使用Qt(一):玩腻了MFC,试试在MFC中使用Qt!(手动配置编译Qt)
在MFC中使用Qt(三):通过编辑项目文件(.vcxproj)实现Qt的自动化编译流程
在MFC中使用Qt(四):使用属性表(Property Sheet)实现自动化Qt编译流程
MFC中接入Qt后,MFC的事件处理机制和Qt的事件处理机制&信号和槽机制是否独立?有无重叠?如何共存?如何交互?
本文会就上述问题进行详细的分析解答。
MFC和Qt的共存和交互
首先,需要了解下MFC和Qt各自的事件处理方式,然后看看它们在同一个应用程序中共存时如何交互,是否有冲突或重叠,以及如何让它们协同工作。
MFC的事件处理主要依赖于Windows消息循环。
MFC应用程序有一个主消息循环(通常在CWinApp的Run方法中),负责接收和分发Windows消息到相应的窗口过程。
每个窗口类(如对话框、控件)通过消息映射(Message Map)将消息映射到对应的处理函数。
例如,按钮点击会触发WM_COMMAND消息,对应的处理函数会被调用。
而Qt的事件处理机制则是基于事件循环(Event Loop),由QApplication或QCoreApplication管理。
Qt使用信号和槽(Signals and Slots)机制来处理事件。
当某个事件发生(如按钮点击),对应的QObject派生类(如QPushButton)会发出一个信号,连接的槽函数会被执行。
此外,Qt还可以通过重写事件处理函数(如mousePressEvent、keyPressEvent)来处理特定事件。
框架 | 事件处理机制 | 核心组件 |
---|---|---|
MFC | 基于 Windows 消息循环(GetMessage /DispatchMessage ),通过消息映射(Message Map)处理事件。 |
CWinApp , CWnd , MSG , 消息映射宏(如 ON_COMMAND )。 |
Qt | 基于事件循环(QApplication::exec() ),使用信号槽和事件过滤器(eventFilter )处理事件。 |
QApplication , QObject , 信号槽机制 |
现在,当在MFC应用程序中嵌入Qt窗口或控件时,两者的消息/事件循环需要协调工作。例如,主消息循环由MFC管理,而Qt有自己的事件循环。这时候可能出现的问题包括:
- 消息循环冲突:MFC的主消息循环和Qt的事件循环是否互相干扰?比如,当MFC处理消息时,Qt的事件是否会被阻塞,反之亦然?
- 事件传递路径:当用户在Qt控件上进行操作(如点击按钮),该事件如何传递?是先由Qt处理,还是传递给MFC的窗口过程?
- 信号和槽与MFC消息的交互:如何在MFC中响应Qt的信号,或者在Qt中触发MFC的消息处理?
下面将针对这些问题进行详细分析,并给到相应的处理方式。
消息循环共存
MFC的CWinApp::Run方法负责消息循环,通常在一个while循环中使用GetMessage、TranslateMessage、DispatchMessage。
cpp
// MFC 消息泵逻辑
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg); // MFC 窗口处理消息
}
而Qt的QApplication::exec()也会启动一个类似的消息循环。
cpp
qApp->exec();
如果直接嵌套调用,可能会导致两个消息循环互相阻塞,无法处理对方的事件。
cpp
//伪代码
{
// MFC 消息泵逻辑
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg); // MFC 窗口处理消息
}
//Qt消息循环被上面MFC消息循环阻塞
qApp->exec();
}
cpp
//伪代码
{
//Qt消息循环
qApp->exec();
// MFC 消息循环被上方Qt消息循环阻塞
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg); // MFC 窗口处理消息
}
}
而解决方案通常是将Qt的事件循环集成到MFC的消息循环中。
QMfcApp(来自Qt Solutions)是一个常见的方法,它继承自QApplication,并重写了某些方法,使得Qt的事件处理能够融入MFC的消息循环。
这样,MFC的主消息循环会处理所有Windows消息,而Qt的事件循环会在空闲时处理Qt的事件,或者通过定时器触发Qt事件的处理。
统一消息循环
通过 QMfcApp(Qt Solutions) 将 Qt 事件循环集成到 MFC 的消息循环中:
-
原理 :重写 MFC 的
Run()
方法,调用QApplication::exec()
处理 Qt 事件。 -
代码示例:
cpp
// MFC 应用程序类重写
BOOL CMyApp::InitInstance()
{
//...
CWinApp::InitInstance();
QMfcApp::instance(this); // 初始化 Qt 应用
//...
return TRUE;
}
int CMyApp::Run() {
return QMfcApp::run(this); // 启动混合消息循环
}
QMfcApp::run(this)内部Qt 主循环逻辑大致如下:
cpp
// 伪代码:Qt 主循环内部逻辑
int QApplication::exec() {
while (!exit_loop) {
// 处理 Qt 事件
processEvents();
// 嵌入 MFC 消息泵逻辑
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg); // MFC 窗口处理消息
}
}
return exit_code;
}
Qt 的 exec()
并非单纯阻塞,其内部会通过 非阻塞轮询(PeekMessage
) 持续检查并分发 Windows 消息。
以此实现MFC和Qt的消息循环共存问题。
事件传递路径
当Qt控件作为子窗口嵌入到MFC窗口中时(例如通过QWinWidget),Windows消息首先由MFC的窗口过程处理,但Qt控件需要处理自己的事件。此时,Qt可能需要拦截某些消息,或者将消息传递给Qt的事件系统。
例如,鼠标点击Qt控件时,消息应该由Qt处理,生成对应的鼠标事件,而不是被MFC的窗口过程处理。
解决方案:
这通常通过在MFC窗口中创建一个QWinWidget实例,并将其作为子窗口。QWinWidget会处理窗口消息,并将其转发给Qt的事件系统,确保Qt控件能够正确响应事件。同时,未被Qt处理的消息会传递给父窗口(MFC窗口)处理。
cpp
// 在 MFC 对话框中嵌入 Qt 控件
BOOL CMyDialog::OnInitDialog() {
CDialog::OnInitDialog();
QWinWidget *qtWidget = new QWinWidget(this); // Qt 控件容器
QPushButton *qtButton = new QPushButton("Qt Button", qtWidget);
qtWidget->show();
return TRUE;
}
在 MFC 窗口中重写 WindowProc,将特定消息转发到 Qt 控件(MFC → Qt):
cpp
LRESULT CMyWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_MOUSEMOVE) {
// 转发鼠标消息到 Qt 控件
QApplication::sendEvent(qtWidget, new QMouseEvent(...));
}
return CWnd::WindowProc(message, wParam, lParam);
}
在 Qt 控件中重写 event
方法,处理事件后调用 MFC 接口(Qt → MFC):
cpp
bool QMyWidget::event(QEvent *event) {
if (event->type() == QEvent::KeyPress) {
// 触发 MFC 处理
::PostMessage(mfcHwnd, WM_KEYDOWN, ...);
}
return QWidget::event(event);
}
信号槽与MFC的交互
当需要在MFC中响应Qt控件的信号时,可以通过Qt的信号连接到MFC对象中的槽函数。
但MFC的类并不是QObject的派生类,因此无法直接使用Qt的信号槽机制。
解决方法包括:
- 使用中间QObject类作为桥梁,将Qt信号转发到MFC的回调函数或消息。
- 在MFC类中混入QObject的功能(多重继承自QObject和MFC类),但这可能涉及复杂的生命周期管理。
- 使用Qt的元对象系统,通过QObject::connect将信号连接到全局函数或静态方法,再调用MFC的方法。
解决方案:
可以在MFC的对话框类中创建一个辅助的QObject派生类,该类拥有指向MFC对象的指针。
当Qt发出信号时,辅助对象的槽函数被调用,进而调用MFC对象的方法或发送MFC消息。
此外,也可以在MFC中发送自定义消息,通过PostMessage或SendMessage触发MFC的消息处理,从而响应Qt的事件。
示例:使用 QObject
派生类作为桥梁。
cpp
class Bridge : public QObject {
Q_OBJECT
public:
Bridge(CMyDialog *dialog) : m_dialog(dialog) {}
public slots:
void onQtButtonClicked() {
m_dialog->PostMessage(WM_QT_SIGNAL); // 发送 MFC 消息
}
private:
CMyDialog *m_dialog;
};
// 在 MFC 对话框中连接信号
Bridge *bridge = new Bridge(this);
QObject::connect(qtButton, &QPushButton::clicked, bridge, &Bridge::onQtButtonClicked);
潜在的重叠和冲突
在某些情况下,MFC和Qt可能同时处理相同的事件,导致重复处理或冲突。
例如,键盘或鼠标事件可能被两个框架同时处理。此时需要确保事件在某一层被正确处理,并阻止其传播到另一层。
例如,在Qt控件中处理事件后,可以标记事件为已处理,防止MFC继续处理。
扩展场景
- 混合界面开发:将复杂 UI(如 3D 图表)用 Qt 实现,保留 MFC 的原有业务逻辑。
- 渐进式迁移:逐步替换 MFC 控件为 Qt 控件,降低重构风险
总结
为了在MFC中嵌套Qt并让两者的事件处理协同工作,需要:
- 集成消息循环:使用QMfcApp或类似方法,将Qt事件循环嵌入到MFC的消息循环中,避免阻塞。
- 正确处理窗口消息:通过QWinWidget等机制,确保Qt控件接收并处理相关消息,未被处理的消息传递给MFC。
- 信号与MFC交互:使用中间对象或适配器,将Qt信号转换为MFC的消息或方法调用。
- 事件过滤与协调:在必要时,通过事件过滤器或重写事件处理函数,控制事件的传递路径,避免冲突。
通过上述方法,MFC 和 Qt 的事件处理机制可以高效共存,实现功能互补。