Попытки переопределить 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/
Добавить комментарий