Занимаюсь сейчас разработкой проигрывателя видео под 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);
}
}
Ну вот, в итоге получаем:
Исходники можно скачать здесь
Автор: Santori