c++ - Qt 使用 exec() 循环测试 QMessageBoxes(或通用 QDialog)
问题描述
我想测试QMessageBox
我创建的自定义。特别是,我的 API 将自动执行 的exec()
循环QMessageBox
,并且我想在用户单击按钮或关闭小部件时检查结果。
问题是我被困在exec()
循环中。
我找到的唯一解决方案是创建一个QTimer
在循环执行之前执行我想要的操作。对此解决方案的引用是this和this。
测试工作正常,但在 Qt 中真的没有更原生的exec()
方式来测试 s 的循环QDialog
吗?我的意思是,有一些方法可以激活/模拟信号QTest
,没有什么可以模拟循环exec()
?
解决方案
为什么要“模拟”事件循环?你想运行真正的事件循环!您只想将代码注入其中,这就是计时器的用途(或一般的事件)。
Qt Test 在事件循环周围有各种方便的包装器,例如keyClick
传递事件并排空事件队列等,但您不需要模拟任何东西。如果你没有找到你需要的东西,你可以自己做。
您使用的 API 当然是损坏的:它假装异步操作是同步的。它应该将控件反转为延续传递样式,即:
// horrible
int Api::askForValue();
// better
Q_SIGNAL void askForValueResult(int);
void Api::askForValue(); // emits the signal
template <typename F>
void Api::askForValue(F &&fun) { // convenience wrapper
auto *conn = new QMetaObject::Connection;
*conn = connect(this, &Class::askForValueResult,
[fun = std::forward<F>(fun),
c = std::unique_ptr<QMetaObject::Connection>(conn)]
(int val){
fun(val);
QObject::disconnect(*c);
});
askForValue();
}
同步 API 之类的让很多应用程序代码不得不面临重入,这不是一个假设的问题。关于它的问题并不少见。很少有人意识到这是一个多么糟糕的问题。
但是假设您被迫坚持使用可怕的 API:您真正想要的是对显示的消息窗口做出反应。您可以通过在QEvent::WindowActivate
. 只需在应用程序实例上安装您的事件过滤器对象。这可以包含在一些帮助代码中:
/// Invokes setup, waits for a new window of a type T to be activated,
/// then invokes fun on it. Returns true when fun was invoked before the timeout elapsed.
template <typename T, typename F1, typename F2>
bool waitForWindowAnd(F1 &&setup, F2 &&fun, int timeout = -1) {
QEventLoop loop;
QWidgetList const exclude = QApplication::topLevelWidgets();
struct Filter : QObject {
QEventLoop &loop;
F2 &fun;
bool eventFilter(QObject *obj, QEvent *ev) override {
if (ev.type() == QEvent::WindowActivate)
if (auto *w = qobject_cast<T*>(obj))
if (w->isWindow() && !exclude.contains(w)) {
fun(w);
loop.exit(0);
}
return false;
}
Filter(QEventLoop &loop, F2 &fun) : loop(loop), fun(fun) {}
} filter{loop, fun};
qApp->installEventFilter(&filter);
QTimer::singleShot(0, std::forward<F1>(setup));
if (timeout > -1)
QTimer::singleShot(timeout, &loop, [&]{ loop.exit(1); });
return loop.exec() == 0;
}
可以制作进一步的包装器,以排除常见的要求:
/// Invokes setup, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template <typename T, typename F>
bool waitForStandardOK(F &&setup, int timeout = -1) {
return waitForWindowAnd<T>(setup,
[](QWidget *w){
if (auto *box = w->findChild<QDialogButtonBox*>())
if (auto *ok = box->standardButton(QDialogButtonBox::Ok))
ok->click();
}, timeout);
}
然后,假设您想测试阻塞 API QDialogInput::getInt
:
waitForStandardOK<QDialog>([]{ QInputDialog::getInt(nullptr, "Hello", "Gimme int!"); }, 500);
您还可以制作一个包装器来构建绑定调用,而无需使用 lambda:
/// Binds and invokes the setup call, waits for new widget of type T to be shown,
/// then clicks the standard OK button on it. Returns true if the button
/// was clicked before the timeout.
template<typename T, class ...Args>
void waitForStandardOKCall(int timeout, Args&&... setup) {
auto bound = std::bind(&invoke<Args&...>, std::forward<Args>(args)...);
return waitForStandardOK<T>(bound, timeout);
}
因此:
waitForStandardOKCall<QDialog>(500, &QInputDialog::getInt, nullptr, "Hello", "Gimme int!");
有关std::bind
完美转发的更多信息,请参阅此问题。
推荐阅读
- flutter - Flutter 使用 just-audio 包比较不同的音频
- android - 违反意图重定向策略 - 按照提供的文档修复后被拒绝
- gitlab - 可以在 JSON 文件中访问 gitlab 变量吗?
- reactjs - 如何卸载 DatePicker 组件,以便在重新呈现页面时使用默认值再次加载
- sql - Postgresql:根据项目出现在列上的次数进行聚合
- r - 闪亮的领先撇号与 Googlesheets4
- pandas - 带有分区的 dask read_parquet 目录的目录通配符
- autodesk-forge - !ACESAPI:acesHttpOperation 中的标题 - Autodesk Forge
- javascript - Zoho CRM SDK - 如何在加载时触发函数?
- mongodb - 使用某些条目中缺少的管道和字段时,Mongo 查找非常慢