在 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 并捕获 PaintUpdateRequest 事件。这些事件应导致调用执行 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 绘图的主要内容,如果未能解决你的问题,请参考以下文章

MFC GDI绘图基础

Windows绘图中的GDI映射模式

GDI+ 多线程绘图

7Windows_paint GDI绘图

适用于所有 Win32 程序员的在 Windows Aero Glass(DWM、GDI、GDI+)上绘图的文档和 API 示例

使用 Direct2D 在非客户区绘图