QTimer::singleShot 在 qkeyevent 中不起作用

Posted

技术标签:

【中文标题】QTimer::singleShot 在 qkeyevent 中不起作用【英文标题】:QTimer::singleShot not work in the qkeyevent 【发布时间】:2017-08-28 01:00:49 【问题描述】:

我创建了一个按键事件,如果我按下按键“A”,它将执行A() 的功能。

在 A() 函数中,我将全局参数“g”加 1 并创建一个 QTimer::singleShot 以等待 2 秒并打印“g”的值。例如,“g”的初始值为0。当我按“A”键两次时,“g”的输出值应该是第一次为1,第二次为2。

但是,当我在 2 秒内按下按键时,我发现 QTimer::singleShot 在第一次不起作用并且输出为 "first:g=2,second:g=2"。为什么输出是 "first:g=2,second:g=2" 而不是 "first:g=1,second:g=2"?还有为什么QTimer::singleShot第一次不起作用,它只是同时打印值,没有等待2s。

int g = 0;
void MainWindow::keyReleaseEvent(QKeyEvent *event) 
  Qt::Key_A: 
    g++;
    qtimer1->singleShot(2000, this, SLOT(a()));


//slots
a() 
  qdebug << g;//print value

如果我在 2 秒内按下键,输出是“2, 2”而不是“1,2”。这意味着QTimer::singleShot 不起作用。当我如此快速地按下键时,我该怎么做才能获得真实值。

【问题讨论】:

由于输出为2, 2,如果只打印g字母。 了解您正在增加 keyPressevent 中的数字,这会导致这种行为是正常的,例如,如果您按 2 次,变量 g 将增加 2 倍,并且只有插槽打印变量,如果在打印变量之前按两次 A 键,您只会看到先打印 2,然后再打印 2,如果要打印 1,然后再打印 2,则必须在插槽中进行增量。 这就是我使用 QTimer::singleShot 的原因。我尝试停止该过程两秒钟,这将有足够的时间打印,但不起作用 【参考方案1】:

a() 插槽仅在插槽运行时输出g 的当前值。如果您在第一个单发实际触发之前按下两个键, 这将导致g 被键释放事件函数之前增加两次 第一个输出已经发生。

事实上,如果你在最初的两秒内全力以赴并按下一个键 314159 次,你会看到大量的 314159 值输出。

一种方法可能是将g 的更新推迟到最后一刻,例如:

int g = 0;
void MainWindow::keyReleaseEvent(QKeyEvent *event) 
    qtimer1->singleShot(2000, this, SLOT(a()));

a() 
  qdebug << (++g);

虽然如果有一些 other 某处依赖于 g 的代码在原始点被更新,这将不起作用。

不幸的是,singleShot 生成了一个 timeout 事件,其中不包含任何附加信息。如果您需要额外的信息(例如修改时g的值),您可以在密钥释放事件中创建您的自己的线程,给它要存储为成员的当前值,然后启动线程 - 然后它会根据需要休眠并打印其存储的值而不是当前的g


下面是一些示例代码,展示了这一点。 MyTask 代表您的按键释放事件函数,它启动一个单独的线程来管理时间和要打印的数据。每次它得到一个“事件”(在这种情况下是一个简单的循环,但在你的情况下,它将响应一个键被释放),它会启动一个线程,将g的当前值传递给它。线程对象存储 g 以供以后使用。

而且,一旦线程等待了合适的时间,它就会打印出 g 的存储值,而不管在此期间主任务对真正的 g 做了什么。

#include <QtCore>
#include <iostream>

class MyThread : public QThread

    Q_OBJECT
public:
    MyThread(int useGVal): m_gVal(useGVal) 
public slots:
    void run()
    
        QThread::msleep(6000);
        std::cout << QTime::currentTime().toString().toStdString()
            << " MyThread, g is " << m_gVal << std::endl;
    
private:
    int m_gVal;
;

class MyTask : public QObject

    Q_OBJECT
public:
    MyTask(QObject *parent = 0) : QObject(parent) 
public slots:
    void run()
    
        MyThread *x[5];
        for (size_t i = 0; i < sizeof(x) / sizeof(*x); ++i)
        
            std::cout << QTime::currentTime().toString().toStdString()
                << " MyTask, g <- " << ++g << std::endl;
            x[i] = new MyThread(g);
            x[i]->start();
            QThread::msleep(1000);
        
        for (int i = 0; i < 5; ++i)
        
            x[i]->wait();
            std::cout << QTime::currentTime().toString().toStdString()
                << " MyTask, thread #" << (i + 1) << " finished"
                << std::endl;
        
        emit finished();
    
signals:
    void finished();
private:
    int g = 0;
;

#include "main.moc"

int main(int argc, char *argv[])

    QCoreApplication appl(argc, argv);
    MyTask *task = new MyTask(&appl);
    QObject::connect(task, SIGNAL(finished()), &appl, SLOT(quit()));
    QTimer::singleShot(0, task, SLOT(run()));

    return appl.exec();

运行这段代码会告诉你发生了什么:

10:49:48 MyTask, g <- 1
10:49:49 MyTask, g <- 2
10:49:50 MyTask, g <- 3
10:49:51 MyTask, g <- 4
10:49:52 MyTask, g <- 5
10:49:54 MyThread, g is 1
10:49:54 MyTask, thread #1 finished
10:49:55 MyThread, g is 2
10:49:55 MyTask, thread #2 finished
10:49:56 MyThread, g is 3
10:49:56 MyTask, thread #3 finished
10:49:57 MyThread, g is 4
10:49:57 MyTask, thread #4 finished
10:49:58 MyThread, g is 5
10:49:58 MyTask, thread #5 finished

【讨论】:

谢谢。我怎样才能确保keyevent在2s后执行,而我在1s内按下键。 @MotoJack,当你释放钥匙时,钥匙释放事件将被执行——这就是它的目的。答案显示了如何将g 的更新推迟到需要时(基于给定的代码)。如果您希望在密钥释放时立即更新g,但仍希望在该时间而不是插槽运行时报告值,它还为您提供了一种使用方法。 感谢您的回答。就我而言,源代码比较复杂,无法使用 qdebug @MotoJack,你没有把密钥释放事件放在一个线程中,你在密钥释放事件中启动一个线程并给它足够的信息来执行它的任务.等一下,我会为你准备一些东西...... 我不知道该怎么做。你的意思是我只是在密钥释放事件中放入一个线程并放入“g++; qtimer1->singleShot(2000, this, SLOT(a()));”在线程中?【参考方案2】:

抛开线程玩笑不谈,您可以在 lambda 中捕获 g 的当前值,然后提交 lambda 以供计时器执行。如果您被 Qt 4 或 C++11 之前的编译器卡住,您可以显式地将要提交给该方法的值排队。

这是一个完整的例子:

// https://github.com/KubaO/***n/tree/master/questions/timer-lambda-notadevil-45910623
#include <QtWidgets>

class LogWindow : public QPlainTextEdit 
   Q_OBJECT
   int g = ;
#if __cplusplus < 201103L
   // C++98
   QQueue<int> logQueue;
#endif
   void keyReleaseEvent(QKeyEvent * event) override 
      if (event->key() == Qt::Key_A) 
         g++;
#if __cplusplus >= 201402L
         // C++14
         QTimer::singleShot(2000, this, [this, val=g] log(val); );
#elif __cplusplus >= 201103L
         // C++11
         int val = g;
         QTimer::singleShot(2000, this, [=] log(val); );
#else
         // C++98
         logQueue.enqueue(g);
         QTimer::singleShot(2000, this, SLOT(log()));
#endif
      
      QPlainTextEdit::keyReleaseEvent(event);
   
   void log(int value) 
      appendPlainText(QString::number(value));
   
   Q_SLOT void log()  // becasue MOC doesn't define __cplusplus :(
#if __cplusplus < 201103L
      // C++98
      log(logQueue.dequeue());
#endif
   
;

int main(int argc, char ** argv) 
   QApplication appargc, argv;
   LogWindow w;
   w.appendPlainText("Press and release 'a' a few times.\n");
   w.show();
   return app.exec();


#include "main.moc"

如果您仍然对为什么线程笑话是个笑话感到困惑:它取笑了“如果不确定,就向它抛出线程”的方法,这种方法是不知道任何更好的公众圈子所支持的。

【讨论】:

以上是关于QTimer::singleShot 在 qkeyevent 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

QTimer::singleShot()函数使用

QTimer::singleShot(0, object SLOT(obj_slot())) 做啥?

QTimer::singleShot 仅在间隔为 0 时调用 lambda

PyQT实时显示串口数据在QtCore.QTimer.singleShot()上抛出最大递归深度超出异常

当 singleShot 为 True 时,QTimer 永远不会触发超时 [关闭]

在 QTimer Singleshot 之后终止 QThread