Qt:如何在特定时间内冻结某些按钮?

Posted

技术标签:

【中文标题】Qt:如何在特定时间内冻结某些按钮?【英文标题】:Qt: how to freeze some buttons for a specific amount of time? 【发布时间】:2019-01-04 20:09:06 【问题描述】:

我正在尝试使用 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,而是尝试了我在这个问题 (How do I create a pause/wait function using Qt?) 上看到的这个解决方案:

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

虽然我删除了这一行:QCoreApplication::processEvents(QEventLoop::AllEvents, 100);,因为这会导致主线程不会冻结,按钮仍然可以点击。

但是使用这种方法,每当用户单击第二个图块时,如果没有匹配,则图像甚至不会显示,即使在上面的代码之前调用了我的 showImage() 方法,我也不知道为什么这是。所以用户知道没有匹配,因为 1 秒后图块恢复到初始状态,但他们永远无法看到第二个按钮上的图像。

作为另一种方法,我还考虑禁用所有按钮,然后在单次 QTimer 超时后,仅重新启用尚未匹配的按钮。但这需要额外的逻辑来跟踪哪些图块已匹配。所以现在我坚持

有更清洁的解决方案吗?也许有办法让 QTimer 冻结主线程直到它超时?

【问题讨论】:

为什么不保留第一个解决方案并在 turnTilesBack() 插槽中重新启用?这样您就没有阻塞等待功能并获得所需的行为。 您将需要您在第二段到最后一段中提到的额外逻辑。您只需要一个简单的按钮状态数组。或者,也许您可​​以在计时器处于活动状态时设置一个标志,并在设置该标志时忽略按钮点击。 @HerrvonWurst 我试过了,但没有运气:(我的 turnTilesBack() 方法只为已单击的两个按钮设置了空样式,然后我在两者上都调用 setEnabled(true)按钮,但其余按钮仍保持启用状态。 【参考方案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();

【讨论】:

谢谢史蒂夫。我实际上是在迭代每个 QPushButton 来改变状态。但问题是我不想重新启用每个按钮。我只需要重新启用那些尚未匹配的。这就是为什么在采用这种方法之前,我试图找到 QTimer 阻塞线程的不同解决方案。 @Floella 是的,这就是为什么启用/禁用所有 QPushButtons 的父小部件效果如此之好。您无需跟踪哪些已启用,哪些已禁用。您根据成功匹配的规则设置每个 QPushButton 启用/禁用,同时一旦用户选择了 2 个图块,禁用父小部件会阻止选择任何进一步的图块......并且它不会与您的匹配状态混淆 哦,抱歉,我没有完全理解你的意思。这听起来像是一个不错且干净的方法。我在实现方面有点挣扎,但现在就尝试。谢谢!

以上是关于Qt:如何在特定时间内冻结某些按钮?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不冻结线程的情况下延迟答案? [复制]

QT QThread::isrunning 冻结程序在 Pi

文件下载后如何修复冻结页面?(TreeView SelectedNodeChanged中的TransmitFile)

如何从 QT 窗口内停止多进程?

如何在一个模型中训练多个损失,并在 pytorch 中选择冻结网络的某些部分?

Qt如何实现这种效果,展示多组多列的数据,某些列可放按钮接收事件