Introduction to Qt Drawing System

This article was originally written in the Qter open source community ( www.qter.org ), author devbean , blog www.devbean.net , please indicate the source for reprinting!

Qt's drawing system allows drawing on the screen and other printing devices using the same API. The whole drawing system is based on three classes: QPainter, QPainterDevice and QPaintEngine.



QPainter is used to perform drawing operations; QPaintDevice is an abstraction of a two-dimensional space, this two-dimensional space allows QPainter to draw on it, that is, the space where QPainter works; QPaintEngine provides brushes (QPainter) to draw on different devices unified interface. The QPaintEngine class is used between QPainter and QPaintDevice and is usually transparent to developers. Unless you need to customize a device, you don't need to care about the QPaintEngine class. We can understand QPainter as a brush; QPaintDevice is understood as a place where brushes are used, such as paper, screen, etc.; and for paper and screen, different brushes must be used to draw. In order to use one brush uniformly, we designed QPaintEngine Class, this class allows different papers and screens to use a brush.



The following diagram shows the hierarchy between these three classes (from the Qt API documentation):





The above diagram tells us that Qt's drawing system is actually using QPainter to draw on QPainterDevice, and QPaintEngine is used to communicate between them (that is, to translate QPainter's instructions).



Let's introduce the use of QPainter through an example:

  1. //!!! Qt4/Qt5

  2. class PaintedWidget : public QWidget
  3. {
  4.     Q_OBJECT
  5. public:
  6.     PaintedWidget(QWidget *parent = 0);
  7. protected:
  8.     void paintEvent(QPaintEvent *);
  9. };
复制代码

注意我们重写了 QWidget 的 paintEvent() 函数。这或许是我们在理解了 Qt 事件系统之后首次实际应用。接下来就是 PaintedWidget 的源代码:

  1. //!!! Qt4/Qt5

  2. PaintedWidget::PaintedWidget(QWidget *parent) :
  3.     QWidget(parent)
  4. {
  5.     resize(800, 600);
  6.     setWindowTitle(tr("Paint Demo"));
  7. }

  8. void PaintedWidget::paintEvent(QPaintEvent *)
  9. {
  10.     QPainter painter(this);
  11.     painter.drawLine(80, 100, 650, 500);
  12.     painter.setPen(Qt::red);
  13.     painter.drawRect(10, 10, 100, 400);
  14.     painter.setPen(QPen(Qt::green, 5));
  15.     painter.setBrush(Qt::blue);
  16.     painter.drawEllipse(50, 150, 400, 200);
  17. }
复制代码

在构造函数中,我们仅仅设置了窗口的大小和标题。而 paintEvent() 函数则是绘制的代码。首先,我们在栈上创建了一个 QPainter 对象,也就是说,每次运行 paintEvent() 函数的时候,都会重建这个 QPainter 对象。注意,这一点可能会引发某些细节问题:由于我们每次重建 QPainter,因此第一次运行时所设置的画笔颜色、状态等,第二次再进入这个函数时就会全部丢失。有时候我们希望保存画笔状态,就必须自己保存数据,否则的话则需要将 QPainter 作为类的成员变量。



QPainter 接收一个 QPaintDevice 指针作为参数。QPaintDevice 有很多子类,比如 QImage,以及 QWidget。注意回忆一下,QPaintDevice 可以理解成要在哪里去绘制,而现在我们希望画在这个组件,因此传入的是 this 指针。



QPainter 有很多以 draw 开头的函数,用于各种图形的绘制,比如这里的 drawLine(),drawRect() 以及 drawEllipse() 等。当绘制轮廓线时,使用 QPainter 的 pen() 属性。比如,我们调用了 painter.setPen(Qt::red) 将 pen 设置为红色,则下面绘制的矩形具有红色的轮廓线。接下来,我们将 pen 修改为绿色,5 像素宽(painter.setPen(QPen(Qt::green, 5))),又设置了画刷为蓝色。这时候再调用 draw 函数,则是具有绿色 5 像素宽轮廓线、蓝色填充的椭圆。



运行一下我们的程序,可以看到最终效果:


我们会在后面的章节详细介绍画笔 QPen 和画刷 QBrush 的属性。



另外要说明一点,请注意我们的绘制顺序,首先是直线,然后是矩形,最后是椭圆。按照这样的绘制顺序,可以看到直线是第一个绘制,位于最下一层;矩形是第二个绘制,在中间一层;椭圆是最后绘制,在最上层。



如果了解 OpenGL,肯定听说过这么一句话:OpenGL 是一个状态机。所谓状态机,就是说,OpenGL 保存的只是各种状态。比如,将画笔颜色设置成红色,那么,除非你重新设置另外的颜色,它的颜色会一直是红色。QPainter 也是这样,它的状态不会自己恢复,除非你使用了各种设置函数。因此,如果在上面的代码中,我们在椭圆绘制之后再画一个矩形,它的样式还会是绿色 5 像素的轮廓线以及蓝色的填充,除非你显式地调用了设置函数进行状态的更新。这是大多数绘图系统的实现方式,包括 OpenGL、QPainter 以及 Java2D。正因为 QPainter 是一个状态机,才会引出我们前面曾经介绍过的一个细节问题:由于 paintEvent() 是需要重复进入的,因此,需要注意第二次进入时,QPainter 的状态是不是和第一次一致,否则的话可能会造成闪烁的现象。这个闪烁并不是由于双缓冲的问题,而是由于绘制状态的快速切换。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324631763&siteId=291194637