5

I am trying to use Windows GDI inside of QGraphicsView paintEvent but have noticed some drawing issues, for example the drawing disappear or blinking when I resize window or minimize and maximize. When I use Qt instead of GDI its working perfectly. Here is the code:

[UPDATED CODE]

#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();
}

and here is the whole project(Visual Studio 2010 + Qt 5.4.1)[UPDATED ARCHIVE] https://dl.dropboxusercontent.com/u/105132532/Stackoverflow/Qt5TestApplication.7z

Any ideas?

Solution(edited the code after Kuba Ober's answer)

#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);
}
IKM2007
  • 716
  • 1
  • 8
  • 31
  • As someone asking the question, you should provide a minimal, self contained example that we can copy-paste and run. Do not drop your entire project on us. Minimize it. It's your job. – Kuba hasn't forgotten Monica Feb 27 '15 at 21:20
  • Given that you're still wishing to have the QGraphicsView paint on that widget, I really don't see why you wish to use gdiplus. It'll make things perform much worse than necessary. What are you *really* trying to achieve? – Kuba hasn't forgotten Monica Feb 27 '15 at 21:22
  • I implore you to tell us why you believe that you have to use the abomination known as GDI/GDIPlus. Please let us know what you want to draw - in all likelihood, Qt does it, or allows to do it without using full-blown GDI drawing implementation. **TL;DR** My answer does what you want, but you really don't want to do that. Your question is a typical example of not seeing the problem for a presumed solution. You *think* that the only solution is direct GDIplus painting. Most likely, you're wrong, but we can't read your mind. Please, please, tell us what you're trying to achieve. – Kuba hasn't forgotten Monica Feb 27 '15 at 23:19
  • 1
    Kuba Ober, we have a big project which uses MFC and Windows GDI painting heavily and we wan't to port it to Qt to make it work also on Linux and Mac but want to keep GDI drawing code for Windows and write Qt drawing code for LInux/Mac. That's why I asked this question to know is it possible to use GDI drawing inside Qt class. – IKM2007 Feb 28 '15 at 09:01
  • If you've got under 10,000 lines of GDI drawing code, it'll be probably easier to port it to Qt. It'll also become 3,000 lines of QPainter code. Don't ask how I know. – Kuba hasn't forgotten Monica Mar 01 '15 at 21:25

1 Answers1

6

A QWidget that intends to paint via GDI only must:

  1. Reimplement paintEngine to return nullptr.

  2. Set the WA_PaintOnScreen attribute.

  3. Optionally set the WA_NativeWindow attribute. This only speeds up the first redraw of the widget.

  4. Reimplement QObject::event and catch Paint and UpdateRequest events. These events should result in calling a method that does the GDI painting. The events must not be forwarded to the base class.

Additionally, a QWidget that paints via GDI on top of contents painted through paintEvent/QPainter, must additionally:

  1. Call base class's paintEngine() method once in the constructor. This will instantiate the native paint engine for the widget.

  2. In the QObject::event implementation, the base class's event must be called before doing the GDI painting. This will draw the contents using the raster paint engine, and return control to you to overdraw some other contents on top of it.

The example below shows how to overpaint on top of the drawing done by Qt's paint system. Of course, since the painting is done on top contents already drawn by Qt, there's flicker.

screenshot of the example

Doing GDI painting on top of the Qt's backing store, avoiding the flicker, is also possible, but requires a somewhat different approach.

#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();
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thank you very much! I did the steps you're mentioned and now it's working! – IKM2007 Feb 28 '15 at 09:02
  • @IKM2007 It gets better - I've figured out how to do it without any flicker, and without losing any performance. I'll update the answer when I get some time. – Kuba hasn't forgotten Monica Mar 01 '15 at 21:23
  • @KubaOber I know this is a very old post but I'm intrigued to find out what the "without any flicker" and "without losing any performance" solution is. – weblar83 Mar 10 '17 at 09:32
  • @weblar83 There's a GDI device-independent bitmap that is mapped to the backing store `QImage`. You can access that bitmap using either `QPainter` working on the behind-the-scenes `QImage`, or using GDI. And it "just works". I guess now is the time to update that answer :) – Kuba hasn't forgotten Monica Mar 10 '17 at 17:00