C++ Qt blockSignals未阻止第一次额外单击
我试图通过在连接到QPushButton的插槽函数中使用QObject的blockSignals方法,使QPushButton上的额外点击不起任何作用。然后,我触发一个排队连接信号,该信号连接到一个插槽,该插槽将解锁按钮的信号 我的想法是,下面代码中的sleep调用所表示的阻塞操作(如数据库操作)可能会导致用户额外单击按钮。我解决这一问题的方法是在第一次点击后阻止按钮的信号,这样在阻止操作期间累积的额外点击将不会起任何作用,自发事件队列将完成,然后,发布的事件队列将处理排队的信号,该信号将解锁按钮并将应用程序返回到其正常状态 我的问题是,第一次单击会处理,但是第一次额外的单击(应该被阻止)会意外地处理。后续的额外点击没有任何作用 代码如下:C++ Qt blockSignals未阻止第一次额外单击,c++,qt,events,queue,C++,Qt,Events,Queue,我试图通过在连接到QPushButton的插槽函数中使用QObject的blockSignals方法,使QPushButton上的额外点击不起任何作用。然后,我触发一个排队连接信号,该信号连接到一个插槽,该插槽将解锁按钮的信号 我的想法是,下面代码中的sleep调用所表示的阻塞操作(如数据库操作)可能会导致用户额外单击按钮。我解决这一问题的方法是在第一次点击后阻止按钮的信号,这样在阻止操作期间累积的额外点击将不会起任何作用,自发事件队列将完成,然后,发布的事件队列将处理排队的信号,该信号将解锁按
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QPushButton *btn;
signals:
void delayed_unblock();
private slots:
void doStuff();
void unblock();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include <QDebug>
#include <QTest>
int num = 0;
void MainWindow::doStuff() {
qDebug() << btn->signalsBlocked();
qDebug() << btn;
qDebug() << sender();
qDebug() << num++;
btn->blockSignals(true);
QTest::qSleep(5000);
emit(delayed_unblock());
}
void MainWindow::unblock() {
btn->blockSignals(false);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
btn(new QPushButton("foo"))
{
connect(btn, &QPushButton::clicked, this, &MainWindow::doStuff);
connect(this, &MainWindow::delayed_unblock,
this, &MainWindow::unblock,
Qt::QueuedConnection);
setCentralWidget(btn);
}
我原以为输出结果是正确的
false
QPushButton(0x3e8840)
QPushButton(0x3e8840)
0
因为,据我所知,第一次点击会导致一个同步插槽调用,它可以阻止任何后续按钮信号的发生。在我的例子中,随后的信号来自自发事件队列中的鼠标单击事件。看起来,所有额外的点击都应该被阻止,但第一次额外的点击仍然可以通过
如果有任何帮助,单击一次,等待3秒钟,然后单击3次可快速获得上述相同的结果
我的编译器是MSVC 2015。深入研究Qt的调度机制,可以看出按哪个顺序发生了什么。从这一点可以明显看出为什么会发生这种奇怪的行为 从回溯中,我深入了解了Qt用来调度事件的Glib2.0的g_main_context_调度 在该函数中,事件被分为不同的组(队列)。例如,首先是所有已发布的事件,然后是所有X11/Windows事件,如有 请注意,一次鼠标点击(包括新闻发布事件)通常会导致两个连续的队列,因为它们的处理速度非常快 表示“按下”键进入队列,包含此单一事件的队列或多或少会立即处理,“释放”键进入下一个队列并立即处理(总是在程序触发某些已发布事件之后) 只有在处理某些事件(如qSleep())需要更长时间时,队列中每个组可能包含多个事件 我设置了三个断点,并在主窗口和按钮上安装了一个eventfilter。这样就可以看到所有东西是如何交互的
gdb$ info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0xb6d41db2 <g_main_context_dispatch+578>
breakpoint already hit 549 times
silent
p "dispatch"
continue
4 breakpoint keep y 0xb6d41bdd <g_main_context_dispatch+109>
breakpoint already hit 546 times
silent
p $ebp
continue
5 breakpoint keep y 0xb6d41bc9 <g_main_context_dispatch+89>
breakpoint already hit 551 times
silent
p "leaving"
continue
因此,很明显为什么发布的取消阻止会干扰点击。没有机会阻止所有点击,因为qt比其他人先处理发布的事件(取消阻止)。只有缓冲点击的整体处理“似乎”起作用
如果将阻塞信号与耗时的处理结合使用,最好记住这一点
这也解释了为什么我的“hack”(见下文)使用QMetaObject::invokeMethod
来调用信号有效。它会导致重定向并需要两个已发布的事件。首先用于信号(否则会立即使用emit()调用)第二个是插槽。只有在解锁时才会发生。此时,按钮仍处于静音状态时,其他单击已发出:
1. click // dispatched, blocking
qSleep() // meanwhile clicking 3 times
// followed by enqueuing the SIGNAL
// followed by enqueuing the 3 clicks in a single queue
unblocking SIGNAL // dispatched, unblocks not yet
2.,3., and 4. click // dispatched, but button still blocked
unblocking SLOT // dispatched, finally unblocks
在我下面的“解决方案”中,如果使用非阻塞本地eventloop而不是阻塞qSleep(),则三次单击将立即处理(而不是在取消阻塞排队后),并且不会发出任何信号。
解决方案在保持qSleep()的同时: 我通过使用
QMetaObject::invokeMethod()
调用信号解决了这个问题:
QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);
而不是emit(delayed_unblock());
解决方案使用本地事件循环:
void MainWindow::doStuff()
{
qDebug() << btn->signalsBlocked();
qDebug() << num++;
btn->blockSignals(true);
QTimer t;
t.setSingleShot(true);
t.setInterval(5000);
QEventLoop loop;
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
t.start();
loop.exec(); // lets event processing happen nothing blocked (no mopuseclicks stuck in the windows system !?)
//QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);
emit(delayed_unblock());
}
void主窗口::doStuff()
{
qDebug()信号锁定();
qDebug()块信号(真);
QTimer t;
t、 设置singleshot(真);
t、 设定间隔(5000);
QEventLoop循环;
连接(&t),信号(超时()),&loop,插槽(退出());
t、 start();
loop.exec();//让事件处理在没有阻塞的情况下进行(windows系统中没有卡住的mopuseclicks!)
//QMetaObject::invokeMethod(这是“延迟解除阻塞”,Qt::QueuedConnection);
发射(延迟解除阻塞());
}
解决方案在qSleep()之后立即使用processEvents
QApplication::processEvents();
似乎可以立即接收和发送windows系统事件。这也解决了问题
由于历史原因,请留下以下行;)
因为Qt5.6的文档告诉我们“被阻塞时发出的信号不会被缓冲”。。由于qSleep()会阻止应用程序,因此不会发出任何消息。完全是。因此,在qSleep()完成之前,Qt根本无法抓住单击的鼠标按钮(仍然卡在Windows或X11中)。而且应该是由于windows系统,点击被缓冲了。第一次点击是在计时器完成后,在所有其他操作(包括取消阻塞)后处理的。对于其余的咔哒声,到那时信号再次被阻断。(@thuga很好地解释了这一点)。据我所知,这里是发生了什么:
QTest::qSleep
QTest::qSleep
退出MainWindow::unblock()的排队调用
QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);
void MainWindow::doStuff()
{
qDebug() << btn->signalsBlocked();
qDebug() << num++;
btn->blockSignals(true);
QTimer t;
t.setSingleShot(true);
t.setInterval(5000);
QEventLoop loop;
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
t.start();
loop.exec(); // lets event processing happen nothing blocked (no mopuseclicks stuck in the windows system !?)
//QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);
emit(delayed_unblock());
}