Pull to refresh

Idle Event в Qt

Reading time3 min
Views5.6K
Привет, хабралюди!

Встпуление


Не так давно я стал работать с Qt под Windows. Моей задачей является разработка графического приложения с отрисовкой пользовательского интерфейса в реальном времени с анимацией и прочими плюшками. Работает вся эта красота через DirectX, а Qt очень помогает анимацией, окнами, сигналами и прочими полезными вещами.

Зачем это надо?


Если кто занимался разработкой игр, то знает, что перерисовка экрана происходит во время «простоя приложения» — т.е. те моменты, когда очередь сообщений к окну пустая. Это позволяет перерисовывать окно довольно быстро, так в частности пустое окно, может отрисовываться несколько тысяч фреймов в секунду(с отключенной вертикальной синхронизацией).

Как не надо было делать?

Когда-то давно я работал с MFC и помнил, что там была функция OnIdle(), которую можно перегрузить. Покопавшись в интернете и в исходниках, я так и не нашел что-то подобное в Qt. Единственное решение, которое вроде бы работало — это создание таймера, который запускается с интервалом 0 ms, т.е. выглядело примерно так:
  1. class QMyWindow : public QWidget
  2. {
  3.     Q_OBJECT
  4.     int m_idleTimerId;
  5. public:
  6.     QMyWindow(QObject* parent) : QWidget(parent) : m_idleTimerId(-1)
  7.     {
  8.         m_idleTimerId = startTimer(0);
  9.     }
  10.     void timerEvent(QTimerEvent* event)
  11.     {
  12.         if(event->timerId() == m_idleTimerId)
  13.         {
  14.             //your idle handling code is here...
  15.         }
  16.     }
  17. };

По словам автора, сообщение таймера активизируется, когда у нас пустая очередь сообщений. Но проблема в том, что эти самые сообщения таймера «забивают» очередь так, что даже QResizeEvent не сразу доходит до обработчика.

Решение найдено


Решение пришло неожиданно — использовать схему отрисовки окна в WinAPI:
  1. while( msg.message != WM_QUIT )
  2. {
  3.     if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
  4.     {
  5.         TranslateMessage( &msg );
  6.         DispatchMessage( &msg );
  7.     }
  8.     else
  9.         Render();
  10. }

По коду видно, что сперва мы получаем сообщение, через PeekMessage, затем сообщение обработываем, и отсылаем обработчику окна, а если очередь пуста — рисуем наши элементы. На Qt это выглядит так:
  1. WinPlatform w;
  2. MwApplication a(&w,argc,argv,true);
  3.  
  4. a.installEventFilter(&w);
  5.  
  6. a.postEvent(&w,new FLoadingEvent(),Qt::LowEventPriority);
  7. while(w.isRunning() || QApplication::hasPendingEvents())
  8. {
  9.     //QApplication::sendPostedEvents();
  10.     if(QApplication::hasPendingEvents())
  11.         QApplication::processEvents();
  12.     else
  13.         w.renderObjects();
  14. }

Вкратце, пару моментов:
  1. ставим фильтр для сообщений типа QCloseEvent, ну или пользуемся сигналом lastWindowClosed, чтобы w.isRunning возвращала false в случае закрытия окна
  2. обрабатываем сообщения до тех пор, пока они есть в очереди, или рисуем наши объекты

Стоит отдельно рассказать о том, почему в условии while стоит hasPendingEvents(). Дело в том, что при закрытии окна, мы можем поставить объекты в очередь на удаление вызовом deleteLater, которые удаляются при возвращении к eventLoop. Чтобы избежать возможных проблем надо обработать очередь до конца.

Также вы, наверное обратили внимание на закомментированную строку с вызовом sendPostedEvents(). В документации сказано, что отсутствие вызова этой процедуры может вызвать проблемы с отложенным удалением(deleteLater), хотя в моем приложении вроде все работает.

Заключение


Получилось, конечно, немного костыльно, но все же это работает.Если есть предложения — милости просим в комментарии. Всем спасибо за внимание!
Tags:
Hubs:
+1
Comments9

Articles