在 Qt 中通过 Windows GDI 绘图
Drawing by Windows GDI inside of Qt
我正在尝试在 QGraphicsView paintEvent 中使用 Windows GDI,但注意到一些绘图问题,例如当我调整 window 大小时或最小化和最大化时绘图消失或闪烁。当我使用 Qt 而不是 GDI 时,它工作得很好。这是代码:
[更新代码]
#include "CustomView.h"
#include <QPainter>
#include <QPaintEngine>
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
CustomView::CustomView(QWidget *parent)
: QGraphicsView(parent)
{
setAutoFillBackground(true);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setAttribute(Qt::WA_NativeWindow, true);
}
CustomView::~CustomView()
{
}
void CustomView::paintEvent(QPaintEvent * event)
{
QPainter painter(viewport());
painter.beginNativePainting();
// Drawing by Windows GDI
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
QString text("Test GDI Paint");
RECT rect;
GetClientRect(hwnd, &rect);
HBRUSH hbrRed = CreateSolidBrush(RGB(0, 255, 0));
FillRect(hdc, &rect, hbrRed);
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
ReleaseDC(hwnd, hdc);
painter.endNativePainting();
QGraphicsView::paintEvent(event);
// Drawing the same by Qt
// QPainter painter(viewport());
// painter.save() ;
// QBrush GreenBrush(Qt::green);
// QBrush WhiteBrush(Qt::white);
// QRect ViewportRect = viewport()->rect();
// painter.fillRect(ViewportRect, GreenBrush);
// painter.drawEllipse(50, 50, ViewportRect.right() - 100, ViewportRect.bottom() - 100);
// QPainterPath EllipsePath;
// EllipsePath.addEllipse(50, 50, ViewportRect.right() - 100, ViewportRect.bottom() - 100);
// painter.fillPath(EllipsePath, WhiteBrush);
// painter.drawText(ViewportRect.width() / 2, ViewportRect.height() / 2, "Test Qt Paint");
// painter.restore();
}
这是整个项目(Visual Studio 2010 + Qt 5.4.1)[更新存档]
https://dl.dropboxusercontent.com/u/105132532/Whosebug/Qt5TestApplication.7z
有什么想法吗?
解决方案(根据Kuba Ober的回答编辑了代码)
#include "CustomView.h"
#include <QPainter>
#include <QPaintEngine>
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
CustomView::CustomView(QWidget *parent)
: QGraphicsView(parent)
{
// This brings the original paint engine alive.
QGraphicsView::paintEngine();
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
setRenderHint(QPainter::Antialiasing);
}
CustomView::~CustomView()
{
}
QPaintEngine* CustomView::paintEngine() const
{
return NULL;
}
bool CustomView::event(QEvent * event) {
if (event->type() == QEvent::Paint)
{
bool result = QGraphicsView::event(event);
drawGDI();
return result;
}
else if (event->type() == QEvent::UpdateRequest)
{
bool result = QGraphicsView::event(event);
drawGDI();
return result;
}
return QGraphicsView::event(event);
}
void CustomView::drawGDI()
{
// Drawing by Windows GDI
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
QString text("Test GDI Paint");
RECT rect;
GetClientRect(hwnd, &rect);
HBRUSH hbrRed = CreateSolidBrush(RGB(0, 255, 0));
FillRect(hdc, &rect, hbrRed);
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
ReleaseDC(hwnd, hdc);
}
打算通过 GDI 仅 绘画的 QWidget
必须:
重新实现 paintEngine
到 return nullptr
.
设置 WA_PaintOnScreen
属性。
可选择设置 WA_NativeWindow
属性。这只会加快小部件的第一次重绘。
重新实现 QObject::event
并捕获 Paint
和 UpdateRequest
事件。这些事件应该导致调用执行 GDI 绘制的方法。事件不得转发到基地class。
此外,通过 GDI 在通过 paintEvent
/QPainter
绘制的 内容之上绘制的 QWidget
必须 另外:
在构造函数中调用基class的paintEngine()
方法一次。这将为小部件实例化本机绘制引擎。
在 QObject::event
实现中,必须在 执行 GDI 之前 调用基础 class 的 event
绘画。这将使用光栅绘制引擎绘制内容,并且 return 控制你在它上面透支一些其他内容。
下面的示例显示了如何在 Qt 的绘制系统完成的绘图之上进行叠加绘制。当然,由于绘制是在Qt已经绘制好的top内容上进行的,所以会出现闪烁。
在 Qt 的后备存储之上进行 GDI 绘制,避免闪烁,也是可能的,但需要一种稍微不同的方法。
#include <QApplication>
#include <QGraphicsObject>
#include <QPropertyAnimation>
#include <QGraphicsView>
#include <QPainter>
#include <QPaintEvent>
#include <windows.h>
class View : public QGraphicsView {
public:
View(QWidget *parent = 0) : QGraphicsView(parent)
{
// This brings the original paint engine alive.
QGraphicsView::paintEngine();
//setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
setRenderHint(QPainter::Antialiasing);
}
QPaintEngine * paintEngine() const Q_DECL_OVERRIDE { return 0; }
bool event(QEvent * event) Q_DECL_OVERRIDE {
if (event->type() == QEvent::Paint) {
bool result = QGraphicsView::event(event);
paintOverlay();
return result;
}
if (event->type() == QEvent::UpdateRequest) {
bool result = QGraphicsView::event(event);
paintOverlay();
return result;
}
return QGraphicsView::event(event);
}
void resizeEvent(QResizeEvent *) {
fitInView(-2, -2, 4, 4, Qt::KeepAspectRatio);
}
virtual void paintOverlay();
};
void View::paintOverlay()
{
// We're called after the native painter has done its thing
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
HBRUSH hbrGreen = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 255, 0));
RECT rect;
GetClientRect(hwnd, &rect);
SetBkMode(hdc, TRANSPARENT);
SelectObject(hdc, hbrGreen);
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
QString text("Test GDI Paint");
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
DeleteObject(hbrGreen);
ReleaseDC(hwnd, hdc);
}
class EmptyGraphicsObject : public QGraphicsObject
{
public:
EmptyGraphicsObject() {}
QRectF boundingRect() const { return QRectF(0, 0, 0, 0); }
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) {}
};
void setupScene(QGraphicsScene &s)
{
QGraphicsObject * obj = new EmptyGraphicsObject;
QGraphicsRectItem * rect = new QGraphicsRectItem(-1, 0.3, 2, 0.3, obj);
QPropertyAnimation * anim = new QPropertyAnimation(obj, "rotation", &s);
s.addItem(obj);
rect->setPen(QPen(Qt::darkBlue, 0.1));
anim->setDuration(2000);
anim->setStartValue(0);
anim->setEndValue(360);
anim->setEasingCurve(QEasingCurve::InBounce);
anim->setLoopCount(-1);
anim->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
setupScene(s);
View view;
view.setScene(&s);
view.show();
return a.exec();
}
我正在尝试在 QGraphicsView paintEvent 中使用 Windows GDI,但注意到一些绘图问题,例如当我调整 window 大小时或最小化和最大化时绘图消失或闪烁。当我使用 Qt 而不是 GDI 时,它工作得很好。这是代码:
[更新代码]
#include "CustomView.h"
#include <QPainter>
#include <QPaintEngine>
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
CustomView::CustomView(QWidget *parent)
: QGraphicsView(parent)
{
setAutoFillBackground(true);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setAttribute(Qt::WA_NativeWindow, true);
}
CustomView::~CustomView()
{
}
void CustomView::paintEvent(QPaintEvent * event)
{
QPainter painter(viewport());
painter.beginNativePainting();
// Drawing by Windows GDI
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
QString text("Test GDI Paint");
RECT rect;
GetClientRect(hwnd, &rect);
HBRUSH hbrRed = CreateSolidBrush(RGB(0, 255, 0));
FillRect(hdc, &rect, hbrRed);
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
ReleaseDC(hwnd, hdc);
painter.endNativePainting();
QGraphicsView::paintEvent(event);
// Drawing the same by Qt
// QPainter painter(viewport());
// painter.save() ;
// QBrush GreenBrush(Qt::green);
// QBrush WhiteBrush(Qt::white);
// QRect ViewportRect = viewport()->rect();
// painter.fillRect(ViewportRect, GreenBrush);
// painter.drawEllipse(50, 50, ViewportRect.right() - 100, ViewportRect.bottom() - 100);
// QPainterPath EllipsePath;
// EllipsePath.addEllipse(50, 50, ViewportRect.right() - 100, ViewportRect.bottom() - 100);
// painter.fillPath(EllipsePath, WhiteBrush);
// painter.drawText(ViewportRect.width() / 2, ViewportRect.height() / 2, "Test Qt Paint");
// painter.restore();
}
这是整个项目(Visual Studio 2010 + Qt 5.4.1)[更新存档] https://dl.dropboxusercontent.com/u/105132532/Whosebug/Qt5TestApplication.7z
有什么想法吗?
解决方案(根据Kuba Ober的回答编辑了代码)
#include "CustomView.h"
#include <QPainter>
#include <QPaintEngine>
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
CustomView::CustomView(QWidget *parent)
: QGraphicsView(parent)
{
// This brings the original paint engine alive.
QGraphicsView::paintEngine();
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
setRenderHint(QPainter::Antialiasing);
}
CustomView::~CustomView()
{
}
QPaintEngine* CustomView::paintEngine() const
{
return NULL;
}
bool CustomView::event(QEvent * event) {
if (event->type() == QEvent::Paint)
{
bool result = QGraphicsView::event(event);
drawGDI();
return result;
}
else if (event->type() == QEvent::UpdateRequest)
{
bool result = QGraphicsView::event(event);
drawGDI();
return result;
}
return QGraphicsView::event(event);
}
void CustomView::drawGDI()
{
// Drawing by Windows GDI
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
QString text("Test GDI Paint");
RECT rect;
GetClientRect(hwnd, &rect);
HBRUSH hbrRed = CreateSolidBrush(RGB(0, 255, 0));
FillRect(hdc, &rect, hbrRed);
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
ReleaseDC(hwnd, hdc);
}
打算通过 GDI 仅 绘画的 QWidget
必须:
重新实现
paintEngine
到 returnnullptr
.设置
WA_PaintOnScreen
属性。可选择设置
WA_NativeWindow
属性。这只会加快小部件的第一次重绘。重新实现
QObject::event
并捕获Paint
和UpdateRequest
事件。这些事件应该导致调用执行 GDI 绘制的方法。事件不得转发到基地class。
此外,通过 GDI 在通过 paintEvent
/QPainter
绘制的 内容之上绘制的 QWidget
必须 另外:
在构造函数中调用基class的
paintEngine()
方法一次。这将为小部件实例化本机绘制引擎。在
QObject::event
实现中,必须在 执行 GDI 之前 调用基础 class 的event
绘画。这将使用光栅绘制引擎绘制内容,并且 return 控制你在它上面透支一些其他内容。
下面的示例显示了如何在 Qt 的绘制系统完成的绘图之上进行叠加绘制。当然,由于绘制是在Qt已经绘制好的top内容上进行的,所以会出现闪烁。
在 Qt 的后备存储之上进行 GDI 绘制,避免闪烁,也是可能的,但需要一种稍微不同的方法。
#include <QApplication>
#include <QGraphicsObject>
#include <QPropertyAnimation>
#include <QGraphicsView>
#include <QPainter>
#include <QPaintEvent>
#include <windows.h>
class View : public QGraphicsView {
public:
View(QWidget *parent = 0) : QGraphicsView(parent)
{
// This brings the original paint engine alive.
QGraphicsView::paintEngine();
//setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
setRenderHint(QPainter::Antialiasing);
}
QPaintEngine * paintEngine() const Q_DECL_OVERRIDE { return 0; }
bool event(QEvent * event) Q_DECL_OVERRIDE {
if (event->type() == QEvent::Paint) {
bool result = QGraphicsView::event(event);
paintOverlay();
return result;
}
if (event->type() == QEvent::UpdateRequest) {
bool result = QGraphicsView::event(event);
paintOverlay();
return result;
}
return QGraphicsView::event(event);
}
void resizeEvent(QResizeEvent *) {
fitInView(-2, -2, 4, 4, Qt::KeepAspectRatio);
}
virtual void paintOverlay();
};
void View::paintOverlay()
{
// We're called after the native painter has done its thing
HWND hwnd = (HWND)viewport()->winId();
HDC hdc = GetDC(hwnd);
HBRUSH hbrGreen = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 255, 0));
RECT rect;
GetClientRect(hwnd, &rect);
SetBkMode(hdc, TRANSPARENT);
SelectObject(hdc, hbrGreen);
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Ellipse(hdc, 50, 50, rect.right - 100, rect.bottom - 100);
QString text("Test GDI Paint");
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, (LPCWSTR)text.utf16(), text.size());
DeleteObject(hbrGreen);
ReleaseDC(hwnd, hdc);
}
class EmptyGraphicsObject : public QGraphicsObject
{
public:
EmptyGraphicsObject() {}
QRectF boundingRect() const { return QRectF(0, 0, 0, 0); }
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) {}
};
void setupScene(QGraphicsScene &s)
{
QGraphicsObject * obj = new EmptyGraphicsObject;
QGraphicsRectItem * rect = new QGraphicsRectItem(-1, 0.3, 2, 0.3, obj);
QPropertyAnimation * anim = new QPropertyAnimation(obj, "rotation", &s);
s.addItem(obj);
rect->setPen(QPen(Qt::darkBlue, 0.1));
anim->setDuration(2000);
anim->setStartValue(0);
anim->setEndValue(360);
anim->setEasingCurve(QEasingCurve::InBounce);
anim->setLoopCount(-1);
anim->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
setupScene(s);
View view;
view.setScene(&s);
view.show();
return a.exec();
}