Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/162.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ Qt blockSignals未阻止第一次额外单击_C++_Qt_Events_Queue - Fatal编程技术网

C++ Qt blockSignals未阻止第一次额外单击

C++ Qt blockSignals未阻止第一次额外单击,c++,qt,events,queue,C++,Qt,Events,Queue,我试图通过在连接到QPushButton的插槽函数中使用QObject的blockSignals方法,使QPushButton上的额外点击不起任何作用。然后,我触发一个排队连接信号,该信号连接到一个插槽,该插槽将解锁按钮的信号 我的想法是,下面代码中的sleep调用所表示的阻塞操作(如数据库操作)可能会导致用户额外单击按钮。我解决这一问题的方法是在第一次点击后阻止按钮的信号,这样在阻止操作期间累积的额外点击将不会起任何作用,自发事件队列将完成,然后,发布的事件队列将处理排队的信号,该信号将解锁按

我试图通过在连接到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());
    
    }