QTimer::singleShot не работает в qkeyevent

Я создаю ключевое событие, которое, если я нажму клавишу "А", будет выполнять функцию A(),

В функции A() я увеличиваю глобальный параметр "g" на 1 и создаю QTimer::singleShot подождать 2 секунды и вывести значение "g". Например, начальное значение "g" равно 0. Когда я нажимаю клавишу "A" дважды, выходное значение "g" должно быть 1 в первый раз и 2 во второй раз.

Однако, когда я нажимаю клавишу в течение 2 секунд, я обнаружил, что QTimer::singleShot не работает в первый раз и вывод "первый:g=2, второй:g=2". Почему вывод "первый:g=2, второй:g=2", а не "первый:g=1, второй:g=2"? Также почему QTimer::singleShot не работает в первый раз, он просто печатает значения в то же время, не ждал 2с.

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 ответа

Решение

a() Слот просто выводит текущее значение g в то время как слот работает. Если вы нажмете две клавиши до того, как будет произведен первый выстрел , это приведет к g быть увеличенным дважды функцией события отпускания ключа до того, как произошел первый вывод.

На самом деле, если вы в течение этих двух секунд нажмете целую свинью и нажмете клавишу 314159 раз, то вы увидите, что 314159 вывод значений.

Одним из подходов может быть отсрочка обновления g до последнего возможного момента, например, с:

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

хотя это не будет работать, если где-то есть другой кусок кода, который опирается на 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

Оставляя в стороне шутки, вы можете зафиксировать текущее значение g в лямбду и подайте лямбду на исполнение таймером. Если вы застряли с Qt 4 или компилятором до C++11, вы можете явно поставить в очередь значения, которые будут переданы методу.

Это полный пример:

// https://github.com/KubaO/stackrun/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"

Если вы до сих пор не понимаете, почему шутливая шутка была шуткой: она смеется над подходом "если не уверен, накинь на него нитку", поддерживаемым кругами общественности, которые не знают, что лучше.

Другие вопросы по тегам