在Qt内部使用Windows GDI进行绘图

5

我试图在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/Stackoverflow/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);
}

作为提问者,您应该提供一个最小化、自包含的示例,以便我们可以复制粘贴并运行。不要把整个项目都扔给我们。请尽量将其最小化,这是您的职责。 - Kuba hasn't forgotten Monica
假设您仍希望在该小部件上进行QGraphicsView绘制,我真的不明白为什么您想要使用gdiplus。这会使事情比必要的更糟糕。您真正想要实现什么? - Kuba hasn't forgotten Monica
我恳求你告诉我们为什么你认为必须使用被称为GDI/GDIPlus的可憎物。请让我们知道你想要绘制什么 - 很可能,Qt可以做到这一点,或者允许在不使用完整的GDI绘图实现的情况下完成。简而言之,我的答案可以解决你的问题,但你真的不想这样做。你的问题是一个典型的例子,即看不到问题只能预设解决方案。你认为唯一的解决方案是直接使用GDIplus绘画。很可能,你错了,但我们无法读取你的思维。拜托,请告诉我们你想要实现什么。 - Kuba hasn't forgotten Monica
1
Kuba Ober,我们有一个使用MFC和Windows GDI绘图技术的大型项目,我们希望将其移植到Qt上,以便在Linux和Mac上也能够运行,但同时想要保留Windows下的GDI绘图代码,并为Linux/Mac编写Qt绘图代码。这就是我提出此问题的原因,想知道是否可能在Qt类中使用GDI绘图。 - IKM2007
如果你有不到10,000行的GDI绘图代码,将其移植到Qt可能会更容易。它也会变成3,000行的QPainter代码。不要问我怎么知道的。 - Kuba hasn't forgotten Monica
1个回答

6
一个意图仅通过GDI绘制的 QWidget 必须:
  1. 重新实现paintEngine方法并返回nullptr

  2. 设置WA_PaintOnScreen属性。

  3. 可选地设置WA_NativeWindow属性。它只会加速小部件的第一次重绘。

  4. 重新实现QObject::event并捕获PaintUpdateRequest事件。这些事件应该导致调用一个执行GDI绘制的方法。不应将这些事件转发给基类。

此外,通过GDI绘制位于paintEvent/QPainter绘制内容之上的QWidget,必须额外进行以下操作:
  1. 在构造函数中调用基类的paintEngine()方法一次。这将为小部件实例化本机绘画引擎。

  2. QObject::event实现中,在进行GDI绘制之前必须先调用基类的event方法。这将使用光栅器绘画引擎绘制内容,并将控制权返回给您来覆盖其上方的其他内容。

以下示例显示了如何在Qt绘制系统完成的绘制上进行覆盖绘制。当然,由于绘制是在Qt已经绘制的内容之上进行的,因此会出现闪烁。 screenshot of the example 在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
@IKM2007 它变得更好了 - 我已经想出了如何在没有任何闪烁的情况下,同时又不损失任何性能来完成它。我有时间时会更新答案。 - Kuba hasn't forgotten Monica
@KubaOber 我知道这是一个非常古老的帖子,但我很想知道“没有任何闪烁”和“不失任何性能”的解决方案是什么。 - weblar83
@weblar83,有一个GDI设备无关位图被映射到后备存储QImage。您可以使用在幕后的QImage上工作的QPainter或使用GDI来访问该位图。它“只是有效”。我想现在是更新那个答案的时候了 :) - Kuba hasn't forgotten Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接