Перекрасить заставку из ветки / отключить утверждение

проблема

Я хочу использовать 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 экземпляры с диска или рисовать на QImages.

Этот ответ предоставляет несколько подходов к выполнению функтора в другом потоке в стиле 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);
    //...
  }
};
Другие вопросы по тегам