У меня проблема в том, что в Linux с Xorg (Ubuntu 14.04) и Qt 5.5.1 QSplashScreen
не нарисован, пока я не доберусь до цикла событий. Даже если я позвоню QApplication::processEvents()
несколько раз, он все еще не нарисован, даже после 1000 вызовов, хотя окно уже на экране, сохраняя оригинальные пиксели, которые были там до запуска приложения, таким образом, будучи фактически невидимым*. От этот ответ Я получил представление об использовании приуроченная цикл вызова QApplication::processEvents()
, как здесь:
#include <QThread>
#include <QApplication>
#include <QSplashScreen>
int main(int argc, char** argv)
{
QApplication a(argc,argv);
QSplashScreen splash;
splash.show();
splash.showMessage("Loading...");
// The hack to try to ensure that splash screen is repainted
for(int i=0;i<30;++i)
{
QThread::usleep(1e3);
a.processEvents();
}
QThread::usleep(5e6); // simulate slow loading process
splash.showMessage("Finished");
return a.exec();
}
Приведенный выше код активно спит в течение 30 мс в попытке сделать QSplashScreen
перекрашивать. Это работает для меня, но я не уверен, что это будет работать всегда, например. на занятом / медленном процессоре или в любых других условиях (магическое значение 30 итераций было найдено опытным путем).
Другим, довольно навязчивым способом, было бы сделать всю необходимую загрузку в другом потоке, только чтобы убедиться, что QSplashScreen
в основном потоке есть активная очередь сообщений. Из-за необходимости значительно переделать основную программу, это выглядит не слишком хорошим решением.
Итак, есть ли способ убедиться, что QSplashScreen
был перекрашен, чтобы его окно не содержало мусора, и только потом приступать к длительной блокировке процесса загрузки?
* Я обнаружил это, когда переместил окно за заставкой
Один из способов избежать непостижимого априорного магического таймаута — дождаться точного события: события рисования. На X11 похоже идет с задержкой. Чтобы сделать это ожидание, мы должны подкласс QSplashScreen
и переопределить QSplashScreen::paintEvent()
, как здесь:
#include <QThread>
#include <QApplication>
#include <QSplashScreen>
class MySplashScreen : public QSplashScreen
{
bool painted=false;
void paintEvent(QPaintEvent* e) override
{
QSplashScreen::paintEvent(e);
painted=true;
}
public:
void ensureFirstPaint() const
{
while(!painted)
{
QThread::usleep(1e3);
qApp->processEvents();
}
}
};
int main(int argc, char** argv)
{
QApplication a(argc,argv);
MySplashScreen splash;
splash.show();
splash.showMessage("Loading...");
splash.ensureFirstPaint();
QThread::usleep(5e6); // simulate slow loading process
splash.showMessage("Finished");
return a.exec();
}
Решение довольно простое: продолжайте цикл обработки событий, пока окно не будет перекрашено. Это должно быть сделано без каких-либо вращений, то есть вы не должны использовать никаких явных тайм-аутов.
#include <QtWidgets>
class EventSignaler : public QObject {
Q_OBJECT
QEvent::Type m_type;
protected:
bool eventFilter(QObject *src, QEvent *ev) override {
if (ev->type() == m_type)
emit hasEvent(src);
return false;
}
public:
EventSignaler(QEvent::Type type, QObject *object) :
QObject(object), m_type(type) {
object->installEventFilter(this);
}
Q_SIGNAL void hasEvent(QObject *);
};
int execUntilPainted(QWidget *widget) {
EventSignaler painted{QEvent::paint, widget};
QObject::connect(&painted, &EventSignaler::hasEvent, qApp, &QCoreApplication::quit);
return qApp->exec();
}
int main(int argc, char **argv) {
QApplication app{argc, argv};
MySplashScreen splash;
EventSignaler painted{QEvent::Paint, &splash};
splash.show();
splash.showMessage("Loading...");
execUntilPainted(&splash);
QThread::sleep(5); // simulate slow loading process
splash.showMessage("Finished");
return app.exec();
}
#include "main.moc"