Qt: как заморозить некоторые кнопки на определенное время?

Я пытаюсь создать простую игру памяти с Qt 5.11.1 и C++, где вы получаете несколько плиток на экране, и вам нужно щелкнуть две и попытаться сопоставить изображения, которые они показывают.

Мои плитки реализованы как QPushButtons. Каждый раз, когда вы нажимаете на один из них, отображается изображение (путем вызова метода showImage(), который изменяет фон кнопки). При нажатии на вторую плитку, если есть совпадение, две кнопки отключаются, поэтому вы не можете нажать на них снова (и вы получите более высокий балл). Однако, если вы не получили совпадения, две плитки, которые вы только что щелкнули, вернутся в исходное состояние (отсутствие изображения) через 1 секунду (это позволяет пользователю «запомнить», какое изображение отображалось на каждой плитке) .

Всякий раз, когда вы нажимаете на «плитку» (кнопку), она отключается (button->setEnabled(false)). Если после щелчка по второй плитке совпадения не было, то обе плитки переворачиваются назад, а затем снова setEnabled(true). Я использую один выстрел QTimer для вызова метода, который вернет плитки:

QTimer::singleShot(1000, this, SLOT(turnTilesBack()));
firstTile->setEnabled(true);
secondTile->setEnabled(true);

Все работает, как и ожидалось, за исключением одного: поскольку QTimer работает в своем собственном потоке (по крайней мере, я так понял из того, что я прочитал), все доступные плитки остаются включенными в течение 1000-миллисекундного промежутка времени, что позволяет пользователю продолжать нажимать на них. Однако, когда совпадений нет, я хотел бы «заморозить» кнопки до тех пор, пока не истечет время QTimer, чтобы пользователь не мог продолжать играть, пока плитки не повернутся назад.

Поэтому вместо использования QTimer я пробовал это решение, которое я видел по этому вопросу (Как создать функцию паузы/ожидания с помощью Qt?):

QTime dieTime= QTime::currentTime().addSecs(1);
while (QTime::currentTime() < dieTime)
    turnTilesBack();

хотя я удалил эту строку: QCoreApplication::processEvents(QEventLoop::AllEvents, 100);, так как это привело бы к тому, что основной поток не зависал, а кнопки по-прежнему можно было бы нажимать.

Но при таком подходе всякий раз, когда пользователь нажимает на вторую плитку, если совпадения нет, изображение даже не отображается, даже когда мой метод showImage() вызывается перед приведенным выше кодом, и я не уверен, почему это так. Таким образом, пользователь знает, что совпадения не было, потому что через 1 секунду плитки возвращаются в исходное состояние, но он так и не увидел изображение на второй кнопке.

В качестве другого подхода я также подумал об отключении всех кнопок, а затем, после истечения времени ожидания одиночного выстрела QTimer, повторном включении только тех, которые еще не были сопоставлены. Но это потребует дополнительной логики для отслеживания того, какие плитки были сопоставлены. Так что пока я придерживаюсь

Есть ли более чистое решение? Может быть, есть способ заставить QTimer заморозить основной поток, пока он не истечет?


person Floella    schedule 04.01.2019    source источник
comment
Почему бы вам не сохранить свое первое решение и не включить его снова в слоте turnTilesBack()? Таким образом, у вас нет блокирующей функции ожидания и вы получаете желаемое поведение.   -  person Herr von Wurst    schedule 04.01.2019
comment
Вам понадобится дополнительная логика, которую вы упомянули в предпоследнем абзаце. Простой массив состояний кнопок должен быть всем, что вам нужно. Или, возможно, вы могли бы установить флаг, пока таймер активен, и игнорировать нажатия кнопок, пока этот флаг установлен.   -  person Jim Rhodes    schedule 04.01.2019
comment
@HerrvonWurst Я пробовал это, но безуспешно :( Мой метод turnTilesBack() устанавливает пустой стиль только для обеих нажатых кнопок, а затем я вызываю setEnabled(true) для обеих кнопок, но все остальные кнопки остаются включенными.   -  person Floella    schedule 04.01.2019


Ответы (1)


Простой способ включить/отключить всю группу QPushButtons — разместить их на промежуточном виджете (в приведенном ниже примере я использовал QFrame).

Если вы хотите отключить все QPushButtons, вы просто отключите фрейм, и все его дочерние виджеты будут отключены.

Когда вы хотите снова включить их, вы включаете фрейм.

Любые виджеты внутри фрейма, которые уже отключены, не будут включены при повторном включении фрейма, поэтому вы не потеряете включенное/отключенное состояние отдельных кнопок.

Вот простой пример. Обратите внимание, что я использовал явные кнопки включения/выключения, которые действуют как прокси для вашего таймера.

#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QFrame>

int main(int argc, char** argv)
{
    QApplication* app = new QApplication(argc, argv);
    QMainWindow*  window = new QMainWindow();
    window->setFixedSize(1024, 200);

    QWidget* widget = new QWidget();
    QHBoxLayout layout(widget);

    QPushButton* enable  = new QPushButton("enable");
    QPushButton* disable = new QPushButton("disable");
    QFrame*      frame   = new QFrame();

    layout.addWidget(enable);
    layout.addWidget(disable);
    layout.addWidget(frame);

    QVBoxLayout frame_layout(frame);
    for (int i = 0; i < 5; ++i)
        frame_layout.addWidget(new QPushButton("click"));

    // this shows that an already disabled button remains disabled
    QPushButton* already_chosen = new QPushButton("click");
    frame_layout.addWidget(already_chosen);
    already_chosen->setEnabled(false);

    QObject::connect(enable,  &QPushButton::clicked, [&]{ frame->setEnabled(true); });
    QObject::connect(disable, &QPushButton::clicked, [&]{ frame->setEnabled(false); });

    window->setCentralWidget(widget);
    window->show();
    return app->exec();
}
person Steve Lorimer    schedule 04.01.2019
comment
Спасибо, Стив. На самом деле я перебирал каждую кнопку QPushButton, чтобы изменить состояние. Но дело в том, что я не хочу повторно включать каждую кнопку. Мне нужно только повторно включить те, которые еще не были сопоставлены. Вот почему я пытался найти другое решение, в котором QTimer блокирует поток, прежде чем использовать этот подход. - person Floella; 05.01.2019
comment
@Floella да, вот почему включение / отключение родительского виджета всех ваших QPushButtons работает так хорошо. Вам не нужно отслеживать, какие из них включены, а какие отключены. Вы устанавливаете каждый QPushButton включенным/отключенным в соответствии с вашими успешно сопоставленными правилами, и в то же время, как только пользователь выбрал 2 плитки, отключение родительского виджета предотвращает выбор каких-либо дополнительных плиток... и это не мешает вашему согласованному состоянию - person Steve Lorimer; 05.01.2019
comment
Ой, извините, я не совсем понял, что вы имели в виду. Это звучит как хороший и чистый подход. Я немного борюсь с реализацией, но попробую прямо сейчас. Спасибо! - person Floella; 09.01.2019