Привет, хабралюди!
Не так давно я стал работать с Qt под Windows. Моей задачей является разработка графического приложения с отрисовкой пользовательского интерфейса в реальном времени с анимацией и прочими плюшками. Работает вся эта красота через DirectX, а Qt очень помогает анимацией, окнами, сигналами и прочими полезными вещами.
Если кто занимался разработкой игр, то знает, что перерисовка экрана происходит во время «простоя приложения» — т.е. те моменты, когда очередь сообщений к окну пустая. Это позволяет перерисовывать окно довольно быстро, так в частности пустое окно, может отрисовываться несколько тысяч фреймов в секунду(с отключенной вертикальной синхронизацией).
Когда-то давно я работал с MFC и помнил, что там была функция OnIdle(), которую можно перегрузить. Покопавшись в интернете и в исходниках, я так и не нашел что-то подобное в Qt. Единственное решение, которое вроде бы работало — это создание таймера, который запускается с интервалом 0 ms, т.е. выглядело примерно так:
По словам автора, сообщение таймера активизируется, когда у нас пустая очередь сообщений. Но проблема в том, что эти самые сообщения таймера «забивают» очередь так, что даже QResizeEvent не сразу доходит до обработчика.
Решение пришло неожиданно — использовать схему отрисовки окна в WinAPI:
По коду видно, что сперва мы получаем сообщение, через PeekMessage, затем сообщение обработываем, и отсылаем обработчику окна, а если очередь пуста — рисуем наши элементы. На Qt это выглядит так:
Вкратце, пару моментов:
Стоит отдельно рассказать о том, почему в условии while стоит hasPendingEvents(). Дело в том, что при закрытии окна, мы можем поставить объекты в очередь на удаление вызовом deleteLater, которые удаляются при возвращении к eventLoop. Чтобы избежать возможных проблем надо обработать очередь до конца.
Также вы, наверное обратили внимание на закомментированную строку с вызовом sendPostedEvents(). В документации сказано, что отсутствие вызова этой процедуры может вызвать проблемы с отложенным удалением(deleteLater), хотя в моем приложении вроде все работает.
Получилось, конечно, немного костыльно, но все же это работает.Если есть предложения — милости просим в комментарии. Всем спасибо за внимание!
Встпуление
Не так давно я стал работать с Qt под Windows. Моей задачей является разработка графического приложения с отрисовкой пользовательского интерфейса в реальном времени с анимацией и прочими плюшками. Работает вся эта красота через DirectX, а Qt очень помогает анимацией, окнами, сигналами и прочими полезными вещами.
Зачем это надо?
Если кто занимался разработкой игр, то знает, что перерисовка экрана происходит во время «простоя приложения» — т.е. те моменты, когда очередь сообщений к окну пустая. Это позволяет перерисовывать окно довольно быстро, так в частности пустое окно, может отрисовываться несколько тысяч фреймов в секунду(с отключенной вертикальной синхронизацией).
Как не надо было делать?
Когда-то давно я работал с MFC и помнил, что там была функция OnIdle(), которую можно перегрузить. Покопавшись в интернете и в исходниках, я так и не нашел что-то подобное в Qt. Единственное решение, которое вроде бы работало — это создание таймера, который запускается с интервалом 0 ms, т.е. выглядело примерно так:
- class QMyWindow : public QWidget
- {
- Q_OBJECT
- int m_idleTimerId;
- public:
- QMyWindow(QObject* parent) : QWidget(parent) : m_idleTimerId(-1)
- {
- m_idleTimerId = startTimer(0);
- }
- void timerEvent(QTimerEvent* event)
- {
- if(event->timerId() == m_idleTimerId)
- {
- //your idle handling code is here...
- }
- }
- };
По словам автора, сообщение таймера активизируется, когда у нас пустая очередь сообщений. Но проблема в том, что эти самые сообщения таймера «забивают» очередь так, что даже QResizeEvent не сразу доходит до обработчика.
Решение найдено
Решение пришло неожиданно — использовать схему отрисовки окна в WinAPI:
- while( msg.message != WM_QUIT )
- {
- if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
- {
- TranslateMessage( &msg );
- DispatchMessage( &msg );
- }
- else
- Render();
- }
По коду видно, что сперва мы получаем сообщение, через PeekMessage, затем сообщение обработываем, и отсылаем обработчику окна, а если очередь пуста — рисуем наши элементы. На Qt это выглядит так:
- WinPlatform w;
- MwApplication a(&w,argc,argv,true);
- a.installEventFilter(&w);
- a.postEvent(&w,new FLoadingEvent(),Qt::LowEventPriority);
- while(w.isRunning() || QApplication::hasPendingEvents())
- {
- //QApplication::sendPostedEvents();
- if(QApplication::hasPendingEvents())
- QApplication::processEvents();
- else
- w.renderObjects();
- }
Вкратце, пару моментов:
- ставим фильтр для сообщений типа QCloseEvent, ну или пользуемся сигналом lastWindowClosed, чтобы w.isRunning возвращала false в случае закрытия окна
- обрабатываем сообщения до тех пор, пока они есть в очереди, или рисуем наши объекты
Стоит отдельно рассказать о том, почему в условии while стоит hasPendingEvents(). Дело в том, что при закрытии окна, мы можем поставить объекты в очередь на удаление вызовом deleteLater, которые удаляются при возвращении к eventLoop. Чтобы избежать возможных проблем надо обработать очередь до конца.
Также вы, наверное обратили внимание на закомментированную строку с вызовом sendPostedEvents(). В документации сказано, что отсутствие вызова этой процедуры может вызвать проблемы с отложенным удалением(deleteLater), хотя в моем приложении вроде все работает.
Заключение
Получилось, конечно, немного костыльно, но все же это работает.Если есть предложения — милости просим в комментарии. Всем спасибо за внимание!