从线程重新绘制初始屏幕/禁用断言
Repaint splash screen from thread / disable assertion
>问题
我想使用QTimer
来更新绘制进度条(使用绘制命令,而不是小部件)的派生QSplashScreen
,以估计程序何时开始运行。
这必然发生在QCoreApplication
的exec
调用之前。我已经让它在 X11 和 Windows 上工作(仅在发布模式下),方法是在第二个线程中放置一个计时器,并在启动画面中调用一个函数来更新进度并重新绘制小部件。但是,这在调试模式下不起作用,因为它会产生以下错误:
"ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread."
我并不真正担心这个断言,因为代码在发布时不会崩溃,它只是一个启动画面,但是我需要能够在调试中运行程序,所以我想 a) 重构代码,使其不会触发断言,或 b) 不执行此特定断言。
试:
- 使用
update()
而不是repaint()
。这不会导致断言,但它也不会重新绘制,因为主线程太忙于在共享库中加载等,并且在我准备好在初始屏幕上调用finish
之前不会处理计时器事件。 - 在主循环中启动
QTimer
。结果与上述相同。 - 使用
QT::QueuedConnection
.相同的结果。
主要
#include <QApplication>
#include <QtGui>
#include <QTimer>
#include <QThread>
#include "mySplashScreen.h"
#include "myMainWindow.h" // contains a configure function which takes a
// LONG time to load.
int main( int argc, char* argv[] )
{
// estimate the load time
int previousLoadTime_ms = 10000;
QApplication a(argc, argv);
MySplashScreen* splash = new MySplashScreen(QPixmap(":/splashScreen"));
// progress timer. Has to be in a different thread since the
// qApplication isn't started.
QThread* timerThread = new QThread;
QTimer* timer = new QTimer(0); // _not_ this!
timer->setInterval(previousLoadTime_ms / 100.0);
timer->moveToThread(timerThread);
QObject::connect(timer, &QTimer::timeout, [&]
{
qApp->processEvents(); splash->incrementProgress(1);
});
QObject::connect(timerThread, SIGNAL(started()), timer, SLOT(start()));
timerThread->start();
splash->show();
a.processEvents();
myMainWindow w;
QTimer::singleShot(0, [&]
{
// This will be called as soon as the exec() loop starts.
w.configure(); // this is a really slow initialization function
w.show();
splash->finish(&w);
timerThread->quit();
});
return a.exec();
}
初始屏幕
#include <QSplashScreen>
class MySplashScreen : public QSplashScreen
{
Q_OBJECT
public:
MySplashScreen(const QPixmap& pixmap = QPixmap(), Qt::WindowFlags f = 0)
: QSplashScreen(pixmap, f)
{
m_pixmap = pixmap;
}
virtual void drawContents(QPainter *painter) override
{
QSplashScreen::drawContents(painter);
// draw progress bar
}
public slots:
virtual void incrementProgress(int percentage)
{
m_progress += percentage;
repaint();
}
protected:
int m_progress = 0;
private:
QPixmap m_pixmap;
};
我的主窗口
#include <QMainWindow>
class myMainWindow : public QMainWindow
{
public:
void configure()
{
// Create and configure a bunch of widgets.
// This takes a long time.
}
}
问题是因为设计是倒退的。GUI 线程不应执行任何加载。GUI 线程的一般方法是:不执行 GUI 线程中的任何工作。您应该生成一个工作线程来加载您需要加载的内容。它可以将事件(或使用排队连接调用槽)发布到 GUI 线程及其初始屏幕。
当然,工作线程不应该创建任何 GUI 对象 - 它不能实例化从QWidget
派生的任何内容。但是,它可以实例化其他内容,因此如果您需要任何昂贵的数据,请在工作线程中准备它,然后在数据可用后在 GUI 线程中廉价地构建QWidget
。
如果延迟是由于库加载造成的,请显式加载工作线程中的所有库,并确保它们的所有页面都驻留在内存中 - 例如,在将其作为库加载后读取整个.DLL。
可以在工作线程中调用MyMainWindow::configure()
,只要它不调用任何QWidget
方法或构造函数即可。它可以做GUI工作,只是在屏幕上不可见。例如,您可以从磁盘加载QImage
实例,或者在QImage
上绘制。
这个答案提供了几种在不同线程中执行函子的方法,GCD 风格。
如果要构建构建成本高昂的小部件,或者构造其中的许多小部件,则可以确保事件循环可以在每个小部件的实例化之间运行。例如:
class MainWindow : public QMainWindow {
Q_OBJECT
QTimer m_configureTimer;
int m_configureState = 0;
Q_SLOT void configure() {
switch (m_configureState ++) {
case 0:
// instantiate one widget from library A
break;
case 1:
// instantiate one widget from library B
...
break;
case 2:
// instantiate more widgets from A and B
...
break;
default:
m_configureTimer.stop();
break;
}
}
public:
MainWindow(QWidget * parent = 0) : QMainWindow(parent) {
connect(&m_configureTimer, SIGNAL(timeout()), SLOT(configure()));
m_configureTimer.start(0);
//...
}
};
- UE4-如何在给定4个屏幕坐标的情况下缩放纹理或材质
- 如何在GTK程序运行时禁用屏幕保护程序/电源管理/屏幕消隐
- 使用 SFML 和 C++ 将 Pixel 打印到屏幕上
- 尝试使用 std::vector<std::thread时出现静态断言失败错误>
- uint_not_usable_without_attribute在业力规则中使用数字生成器时静态断言失败
- 在 Windows 8/10 技术中完全实时的屏幕捕获,没有延迟
- C++ 使用增强正则表达式库时断言崩溃
- Winapi:屏幕截图未显示在窗口中
- 如何在快板的屏幕中显示子位图的绘制?
- 从 exe 文件 (Visual Studio ) 启动时调试断言失败
- 如何将向量断言到特征矩阵
- OpenCV - Python 断言错误:SAD 算法 - 立体相机视差图计算
- 按回车键后输出屏幕关闭
- 要在屏幕上绘制一些小瓷砖,我应该使用 QQuickItem 还是 QQuickPaintedItem?
- 使用 Google Test 对自定义断言函数进行单元测试
- 断言"id < 0"在Qt ActiveX中失败
- 初始值设定项列表构造和静态断言
- SFML 向下移动时如何围绕屏幕中心旋转?
- 截取屏幕截图后程序卡住
- 从线程重新绘制初始屏幕/禁用断言