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