在 Qt 中通过 Windows GDI 绘图
Posted
技术标签:
【中文标题】在 Qt 中通过 Windows GDI 绘图【英文标题】:Drawing by Windows GDI inside of Qt 【发布时间】:2015-04-29 21:23:51 【问题描述】:我正在尝试在 QGraphicsView paintEvent 中使用 Windows GDI,但注意到一些绘图问题,例如,当我调整窗口大小或最小化和最大化时,绘图会消失或闪烁。当我使用 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/***/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);
【问题讨论】:
作为提问的人,您应该提供一个最小的、自包含的示例,我们可以复制粘贴并运行。不要将您的整个项目交给我们。最小化它。这是你的工作。 鉴于您仍然希望在该小部件上绘制 QGraphicsView,我真的不明白您为什么要使用 gdiplus。它会使事情的表现比必要的糟糕得多。您真正想要达到什么目的? 我恳请你告诉我们为什么你认为你必须使用被称为 GDI/GDIPlus 的可憎之物。请让我们知道您想要绘制什么——Qt 很可能会这样做,或者允许在不使用成熟的 GDI 绘图实现的情况下这样做。 TL;DR 我的答案是你想要的,但你真的不想那样做。您的问题是没有看到假定解决方案的问题的典型示例。您认为唯一的解决方案是直接 GDIplus 绘画。很可能,你错了,但我们无法读懂你的想法。请告诉我们你想要达到的目标。 Kuba Ober,我们有一个大项目,它大量使用 MFC 和 Windows GDI 绘图,我们不想将它移植到 Qt 以使其在 Linux 和 Mac 上也能工作,但希望保留 GDI 绘图代码用于 Windows 并为 LInux/Mac 编写 Qt 绘图代码。这就是为什么我问这个问题是为了知道是否可以在 Qt 类中使用 GDI 绘图。 如果您的 GDI 绘图代码少于 10,000 行,将其移植到 Qt 可能会更容易。它也将变成 3,000 行 QPainter 代码。不要问我是怎么知道的。 【参考方案1】:打算通过 GDI仅进行绘制的 QWidget
必须:
重新实现 paintEngine
以返回 nullptr
。
设置WA_PaintOnScreen
属性。
可选择设置WA_NativeWindow
属性。这只会加快小部件的第一次重绘。
重新实现 QObject::event
并捕获 Paint
和 UpdateRequest
事件。这些事件应导致调用执行 GDI 绘制的方法。事件不得转发到基类。
此外,通过 GDI 绘制的QWidget
在通过paintEvent
/QPainter
绘制的内容之上,必须另外:
在构造函数中调用一次基类的paintEngine()
方法。这将为小部件实例化本机绘制引擎。
在QObject::event
实现中,基类的event
必须在进行GDI 绘制之前调用。这将使用光栅绘制引擎绘制内容,并将控制权返回给您以在其之上重绘一些其他内容。
下面的例子展示了如何在 Qt 的绘图系统完成的绘图之上进行重绘。当然,由于绘制是在 Qt 已经绘制的顶部内容上完成的,所以会有闪烁。
在 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();
【讨论】:
非常感谢!我做了你提到的步骤,现在它正在工作! @IKM2007 它变得更好 - 我已经想出了如何做到这一点而不会出现任何闪烁,并且不会失去任何性能。有时间我会更新答案。 @KubaOber 我知道这是一篇很老的帖子,但我很想知道“没有任何闪烁”和“没有任何性能损失”的解决方案是什么。 @weblar83 有一个与 GDI 设备无关的位图映射到后备存储QImage
。您可以使用 QPainter
在幕后工作 QImage
或使用 GDI 访问该位图。它“正常工作”。我想现在是时候更新这个答案了:)以上是关于在 Qt 中通过 Windows GDI 绘图的主要内容,如果未能解决你的问题,请参考以下文章
适用于所有 Win32 程序员的在 Windows Aero Glass(DWM、GDI、GDI+)上绘图的文档和 API 示例