简体   繁体   中英

QTimer::singleShot not work in the qkeyevent

I create a keyevent which if I push key "A" it will do the function A() .

In the A() function, I increment global parameter "g" by 1 and create a QTimer::singleShot to wait 2 seconds and print the value of "g". For example, the initial value of "g" is 0. When I push Key "A" twice, the output value of "g" should be 1 in the first time and 2 in the second time.

However, when I push the key in 2 seconds, I found that the QTimer::singleShot not work in the first time and the output is "first:g=2,second:g=2" . Why the output is "first:g=2,second:g=2" not "first:g=1,second:g=2" ? Also why the QTimer::singleShot not work in the first time,it just print the values in the same time, did not wait 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
}

If I push the key in 2s,the output are "2, 2" not "1,2". It means that QTimer::singleShot not work. How can i do to get the true values when i push the key so quickly.

The a() slot simply outputs the current value of g at the time the slot runs. If you press two keys before the first one-shot is actually fired, that will cause g to have been incremented twice by the key release event function before the first output has happened.

In fact, if you go whole hog and press a key 314159 times within that initial two seconds, what you will see is a whole lot of 314159 values output.

One approach may be to defer the updating of g until the last possible moment, such as with:

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

although this won't work if there's some other piece of code somewhere that relies on g being updated at the original point.

Unfortunately, singleShot generates a timeout event that carries no additional information. If you need extra information (such as the value of g at the time it was modified), you can create your own thread in the key release event, give it the current value to be stored as a member, then start the thread - it will then sleep for as long as necessary and print its stored value rather than the current g .


Here's some example code that shows this in action. The MyTask represents your key release event function in that it starts an individual thread to manage the timing and data to print. Each time it gets an "event" (which is a simple loop in this case but, in yours, it will be in response to a key being released), it kicks off a thread, passing it the current value of g . The thread object stores that g for later use.

And, once the thread has waited a suitable time, it prints out that stored value of g , regardless of what the main task has done to the real g in the meantime.

#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();
}

Running this code shows you what happens:

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

Threading jokes aside, you can capture the current value of g in a lambda and submit the lambda for execution by the timer. If you're stuck with Qt 4 or a pre-C++11 compiler, you can explicitly queue the values to be submitted to the method.

This is a complete example:

// https://github.com/KubaO/stackoverflown/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 app{argc, argv};
   LogWindow w;
   w.appendPlainText("Press and release 'a' a few times.\n");
   w.show();
   return app.exec();
}

#include "main.moc"

If you're still confused as to why the threading joke was a joke: it makes fun of the "if unsure, throw a thread at it" approach espoused by the not-knowing-any-better circles of the public.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM