Решение вопроса с «морганием» экрана при растяжке видео

от автора

Занимаюсь сейчас разработкой проигрывателя видео под Windows. И «завис» на некоторое время над задачей — после перехода на Qt, видео в проигрывателе начинает моргать и исчезать (см. видео).

Попытки переопределить QWidget::paintEvent невозможны из-за того, что Qt выполняет заливку на (https://qt-project.org/doc/qt-4.8/qwidget.html#autoFillBackground-prop) перед QWidget::paintEvent.
Попытка переопределить WM_PAINT и WM_ERASEBACKGOUND в QWidget::winEvent тоже не удалась, т.к. paintEvent может вызываться не только из WM_PAINT, но и другими сервисами по неизвестному мне алгоритму.
Поэтому ниже приведу решение как выходил из этой ситуации.

Итак, решение:
Решил не изобретать велосипед, а использовать нативные виджеты вместе с Qt. Сам нативный виджет будет находиться внутри QWidget. Схематично в окне плеера это можно представить так:

Сначала создаем обработчик для окон. Он возьмет на себя регистрацию класса окна и пересылку сообщений виджетам.
Также необходимо запретить QApplication обрабатывать сообщения для нативных виджетов. У нас в проекте используется libqxt, поэтому необходимо добавить фильтр с помощью QxtApplication:: installNativeEventFilter. Другой вариант – переопределить QCoreApplication::winEventFilter.

Для начала я написал класс WindowProcMapper для того, чтобы сопоставить HWND с объектом.
nativewidgetimpl.h

namespace Native {   	class NativeWndFilter : public QxtNativeEventFilter 	{ 	public: 		inline NativeWndFilter() { }   		inline void insert(HWND h) { m_wnds.insert(h); } 		inline void remove(HWND h) { m_wnds.remove(h); } 		inline bool contains(HWND h) { return m_wnds.contains(h); }   		bool winEventFilter(MSG * msg, long *result) override;   	private: 		QSet<HWND> m_wnds; 	};   	template<typename T> 	class WindowProcMapper 	{ 	public: 		WindowProcMapper(const wchar_t *className); 		~WindowProcMapper(); 		inline T *getWindow(HWND hwnd) const; 		inline void insertWindow(HWND hwnd, T *ptr); 		inline void removeWindow(HWND hwnd); 		ATOM getRegisterResult();   		bool registerWindowClass();   		inline static WindowProcMapper<T> *instance() 		{ return self; } 		 	private: 		QHash<HWND, T*> m_hash; 		LPCWSTR m_className; 		ATOM m_registerResult;   		static WindowProcMapper *self;   		static LRESULT CALLBACK WindowProc(_In_  HWND hwnd, _In_  UINT uMsg,  			_In_  WPARAM wParam, _In_  LPARAM lParam);   		 		NativeWndFilter *m_filter; 	}; } 

Создаем обертку для нативного виджета:
nativewidget.h

namespace Native {   	struct NativeWidgetPrivate; 	class NativeWidget : public QWidget 	{ 		Q_OBJECT 	public: 		NativeWidget(QWidget *parent = nullptr); 		NativeWidget(const QString &wndName, QWidget *parent = nullptr); 		~NativeWidget();   		WId nativeHWND() const; 		HDC getNativeDC() const; 		void releaseNativeDC(HDC hdc) const; 	 		QString wndName() const;   	protected: 		NativeWidget(NativeWidgetPrivate *p, const QString &wndName,  			QWidget *parent = nullptr); 		void paintEvent(QPaintEvent *ev) override; 		void resizeEvent(QResizeEvent *ev) override;  		virtual bool nativeWinEvent(MSG *msg, long *result); 		 		QScopedPointer<NativeWidgetPrivate> d_ptr; 	private: 		void init(); 		Q_DECLARE_PRIVATE(NativeWidget); 	};  	void NativeWidget::paintEvent(QPaintEvent *ev) 	{ 		Q_D(NativeWidget); 		SendMessage(d->m_hwnd, WM_PAINT, 0, 0); 		ev->accept(); 	}   	void NativeWidget::resizeEvent(QResizeEvent *ev) 	{ 		Q_D(NativeWidget); 		const QSize &sz = ev->size();   		SetWindowPos(d->m_hwnd, NULL, 0, 0, sz.width(), sz.height(), 0);   		QWidget::resizeEvent(ev); 	}    } 

Для поведения нативного виджета переопределяем функции resizeEvent и paintEvent. Они будут пересылать события в нативный виджет при изменении QWidget.
Приватный класс возьмет на себя обязанности по управлению нативным виджетом. Он принимает сообщения с помощью метода windowsEvent и, передает их NativeWidget:: nativeWinEvent, который может быть легко переопределен в классах-наследниках.

namespace Native { 	struct NativeWidgetPrivate 	{ 		NativeWidgetPrivate(NativeWidget *q); 		~NativeWidgetPrivate();   		inline HWND winID() const; 		HDC getDC() const; 		void releaseDC(HDC hdc) const;   		QRect windowPlacement() const;   		virtual bool createWindow(QWidget *parent);   		void sendEvent(QEvent *ev); 		inline bool isCreating() const { return m_creating; } 	 		LRESULT windowsEvent(_In_  HWND hwnd, _In_  UINT msg,  			_In_  WPARAM wParam, _In_  LPARAM lParam);   		 		HWND m_hwnd; 		mutable HDC m_hdc; 		bool m_creating;   		void sendToWidget(uint msg, WPARAM wparam, LPARAM lparam);   		QString m_wndName;   		NativeWidget *const q_ptr; 		Q_DECLARE_PUBLIC(NativeWidget); 	}; } 

Для реализации виджета с видео необходимо перехватить сообщения WM_PAINT и WM_ERASEBACKGROUND.
Примерная реализация приведена ниже:

bool MovieScreen::nativeWinEvent(MSG *msg, long *result)  { 	Q_D(MovieScreen); 	//qDebug() << __FUNCTION__;   	switch (msg->message) 	{ 	case WM_ERASEBKGND: 	case WM_PAINT: 		d->updateMovie(); 		break; 	case WM_SHOWWINDOW: 		{ 			auto r = NativeWidget::nativeWinEvent(msg, result); 			if (msg->wParam == TRUE) 				d->updateMovie(); 			return r; 		}   	case WM_SIZE: 	case WM_MOVE: 	case WM_MOVING: 	case WM_SIZING: 		{ 			auto r = NativeWidget::nativeWinEvent(msg, result); 			d->resizeVideo(); 			return r; 		}  	}     	return NativeWidget::nativeWinEvent(msg, result); } 

Примерная реализация:

IVMRWindowlessControl9 *m_pVideoRenderer9;  MovieScreenPrivate:: updateMovie() { 	If (isPaused()) 	{ 		HDC hdc = getHDC(); 		m_pVideoRenderer9->Repaint_Video(winID(), hdc); 		releaseHDC(hdc); } } MovieScreenPrivate::resizeVideo() { 	long lWidth, lHeight;  	HRESULT hr = m_pVideoRenderer9->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);  	if (SUCCEEDED(hr)) 	{ 		RECT rcSrc, rcDest;  				 		// Set the source rectangle. 		SetRect(&rcSrc, 0, 0, lWidth, lHeight);  					 		// Get the window client area. 		GetClientRect(winID(), &rcDest);  				 		// Set the destination rectangle. 		SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom);   		m_pVideoRenderer9->SetVideoPosition(&rcSrc, &rcDest); 			 	}  } 

Ну вот, в итоге получаем:

Исходники можно скачать здесь

ссылка на оригинал статьи http://habrahabr.ru/post/172869/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *