Qt主界面使用QComboBox时,无法接收nativeEvent事件导致无法接收WM_COPYDATA消息的问题处理
Qt主界面使用QComboBox时,无法接收nativeEvent事件导致无法接收WM_COPYDATA消息的问题处理
问题描述
给需要接收消息的mainwindow窗体发送windows消息,mainwindow的nativeEvent方法无响应。
背景:mainwindow.ui文件中包含了一个QComboBox控件
1.给mainwindow窗体发送一次WM_COPYDATA消息
mainwindow窗体可正确收到WM_COPYDATA消息,进入nativeEvent方法
// 这里多发送几次,均可正确进入
3.单击mainwindow窗体上的QComboBox下拉框,弹出下拉列表,随意选取或不选取新的选项均可,只需要执行弹出下拉列表的操作
4.给mainwindow窗体发送一次WM_COPYDATA消息
mainwindow窗体无法收到WM_COPYDATA消息,无法进入nativeEvent方法
//这里无论发多少次,均无法进入
经过多次实验,得出结论:在ui的QComboBox的下拉列表被弹出后,mainwindow窗体均无法响应WM_COPYDATA消息
问题排查
测试环境
消息发送进程名称:TestMsgSender.exe
消息接收进程名称:mainwindow.exe
WM_COPYDATA消息中的窗体名称: mainwindow
FindWindowW传参: mainwindow
继承QAbstractNativeEventFilter,并实现nativeEventFilter方法,截获nativeEvent消息.
class mainwindow : public QMainWindow, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
mainwindow(QWidget *parent = Q_NULLPTR);
~mainwindow();
protected:
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override;
public slots:
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
private:
Ui::mainwindowClass ui;
};
bool mainwindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
MSG* msg = (MSG*)message;
switch (msg->message)
{
case WM_COPYDATA:
{
printf("receive mainwindow::nativeEvent: WM_COPYDATA\n");
break;
}
default:
break;
}
return QMainWindow::nativeEvent(eventType, message, result);
}
bool mainwindow::nativeEventFilter(const QByteArray &eventType, void *message, long *ll)
{
if (eventType == "windows_generic_MSG" || eventType == "windows_dispatcher_MSG")
{
MSG* pMsg = reinterpret_cast<MSG*>(message);
if (pMsg->message == WM_COPYDATA)
{
printf("MainWindow windows system message WM_COPYDATA\n");
}
}
return false;
}
在“printf(“MainWindow windows system message WM_COPYDATA\n”)”处设置断点
- 测试结果
首次进入的数据截图
首次进入时,hwnd的值为0x200942
弹出ComboBox的下拉框后的数据截图
可以看到,此时hwnd的值变成了0x3c1892
由上述可知,在弹出QComboBox的下拉框后,Qt根据窗口名称“mainwindow”获得的窗口hwnd发生了变化,最终导致nativeEvent事件无法进入mainwindow类的重写方法
bool mainwindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
根据前述过程,怀疑时因为弹出QComboBox的下拉列表时导致Qt根据消息中的窗体名称获取对应窗体的hwnd时出错。
因为windows系统下发送WM_COPYDATA消息需要使用::FindWindowW()
方法获取程序句柄,因此在测试代码中插入以下代码打印窗口句柄查看句柄是否发生了变化。
std::wstring str = CharToWchar("mainwindow", CP_ACP);
HWND server = ::FindWindowW(nullptr, str.c_str()); // 打印窗口句柄
if (!IsWindow(server))
{
qDebug() << "can't find server :" << server << "end";
}
else
{
qDebug() << "find window :" << server << "end";
}
结论
再次进行了几个验证性试验后,我认为当消息接收进程名称和WM_COPYDATA消息中要查找的窗体名称一致时是存在bug的,打开QComboBox下拉列表后会提前触发这个bug。
类似这种情况均会出发这个bug:
消息接收进程名称:mainwindow.exe
WM_COPYDATA消息中的窗体名称: mainwindow
所以,最终的建议是,在使用windows的WM_COPYDATA消息和Qt的nativeEvent事件进行进程间通讯时,WM_COPYDATA消息名称不要和消息接收者的进程名称相同。
建议如下
消息接收进程名称:mainwindow.exe
WM_COPYDATA消息中的窗体名称: mainwindowServer (名字与exe文件名称不一样即可)