Перекрасить заставку из ветки / отключить утверждение
проблема
Я хочу использовать QTimer
обновить производную QSplashScreen
который рисует индикатор выполнения (используя команды рисования, а не виджет), чтобы оценить, когда программа начнет работать.
По необходимости это происходит до exec
зов QCoreApplication
, Я заставил это работать (только в режиме выпуска) как на X11, так и на Windows, поместив таймер во второй поток и вызвав функцию на заставке, которая обновляет ход выполнения и перерисовывает виджет. Однако это не работает в режиме отладки, так как выдает следующую ошибку:
"ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread."
Я не очень беспокоюсь об этом утверждении, так как код не дает сбоя при выпуске, и это просто заставка, однако мне нужно иметь возможность запустить программу в режиме отладки, поэтому я бы хотел либо: а) реорганизовать код так, это не вызывает утверждение, или б) рассеивает это конкретное утверждение.
Пытался:
- С помощью
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;
};
MyMainWindow
#include <QMainWindow>
class myMainWindow : public QMainWindow
{
public:
void configure()
{
// Create and configure a bunch of widgets.
// This takes a long time.
}
}
1 ответ
Проблемы в том, что дизайн задом наперед. Поток GUI не должен делать никакой загрузки. Общий подход к потокам GUI: не работать в потоке GUI. Вы должны создать рабочий поток, чтобы загрузить то, что вам нужно. Он может публиковать события (или вызывать слоты, используя подключение в очереди) в поток GUI и его заставку.
Конечно, рабочий поток не должен создавать какие-либо объекты графического интерфейса - он не может создавать экземпляры ничего, происходящего из QWidget
, Однако он может создавать другие экземпляры, поэтому, если вам нужны дорогостоящие данные, подготовьте их в рабочем потоке, а затем дешево создайте QWidget
в потоке GUI, как только эти данные доступны.
Если ваши задержки вызваны загрузкой библиотек, загрузите все библиотеки в рабочем потоке явно и убедитесь, что все их страницы находятся в памяти, например, прочитав весь файл.DLL после загрузки его как библиотека.
MyMainWindow::configure()
может вызываться в рабочем потоке, если он не вызывает QWidget
методы, ни конструкторы. Он может работать с графическим интерфейсом, просто не виден на экране. Например, вы можете загрузить QImage
экземпляры с диска или рисовать на QImage
s.
Этот ответ предоставляет несколько подходов к выполнению функтора в другом потоке в стиле 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);
//...
}
};