Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing by Windows GDI inside of Qt

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);
}
like image 508
IKM2007 Avatar asked Feb 27 '15 08:02

IKM2007


1 Answers

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();
}
like image 74
Kuba hasn't forgotten Monica Avatar answered Nov 22 '22 11:11

Kuba hasn't forgotten Monica