Qt绘图主要是基于QPainter、QPaintDevice和QPaintEngine这三个类。
QPainter(画家类)用来完成具体的绘制操作,QPainter可以在继承自QPainterDevice类的任何对象上进行绘制操作。QPainter一般在一个部件重绘事件(painterEvent())中绘制,首先需要创建QPainter对象,再进行图形的绘制,最后销毁QPainter对象。
先看一个画矩形的例子:(新建一个Qt项目,widget就行)
/*widget.h */
#ifndef WIDGET_H
#define WIDGET_H
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QPixmap pix; //绘图设备,在使用QPixmap的时候用到,第二个程序
QPoint startPoint;
QPoint endPoint; //绘图的起始坐标
protected:
void paintEvent(QPaintEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
};
#endif // WIDGET_H
/*widget.cpp*/
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
resize(400, 400); //widget大小设置为400*400
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *ev)
{
Q_UNUSED(ev);
QPainter painter(this);
int x,y,w,h;
x = startPoint.x();
y = startPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
painter.drawRect(x, y, w, h);
}
void Widget::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠标左键按下
startPoint = event->pos(); //记录鼠标按下的点的坐标
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标
{
endPoint = event->pos(); //鼠标移动中的结束点的坐标
update(); //更新绘制,update函数会调用paintEvent函数进行重新绘制。
}
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠标左键释放
{
endPoint = event->pos();
update(); //在鼠标按键释放的时候也进行重绘
}
}
以上的绘图效果如下所示:
以上程序在鼠标按下的时候记录了一个起始点startPoint,在移动或是松开鼠标的时候记录了另外一个终止点endPoint。通过这两个点直接在widget上画出了矩形,但是注意到,在该widget上画矩形的时候,之前画的会被之后画的覆盖掉,另外也没有办法存储。为了能够存储绘制的图形,可以在绘图设备上进行画图,只需要在以上的widget的构造函数中加入:
pix = QPixmap(350, 400);
pix.fill(Qt::white);
并将paintEvent()改成:
void Widget::paintEvent(QPaintEvent *ev)
{
Q_UNUSED(ev);
int x,y,w,h;
x = startPoint.x();
y = startPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
// QPainter painter(this);
// painter.drawRect(x, y, w, h);
QPainter painter1(&pix); //painter1是在绘图设备中进行绘制的。
painter1.drawRect(x,y,w,h);
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
以上的绘制效果如下所示:
以上程序在进行绘制的时候会出现很多重影,而且拖动速度越快,重影越少,这是因为在拖动的过程中屏幕已经刷新了很多次了,也就是说paintEvent()函数已经执行了多次,每执行一次就会绘制一个矩形,而双缓冲绘图可以消除这种重影。
我们再添加一个辅助画布(绘图设备),正在绘图的时候(拖动鼠标的过程中),在这个辅助的绘图设备上进行绘制,当鼠标释放的时候,在真正的画布上绘图。在widget.h文件中添加两个private成员变量:
QPixmap tempPix; //辅助画布
bool isPainting = 0; //判断当前是否正在绘图,初始值设为0,即没在绘图状态
然后更改paint Event():
void Widget::paintEvent(QPaintEvent *)
{
int x,y,w,h;
x = startPoint.x();
y = startPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
QPainter painter(this);
if(isPainting) //绘图状态下在辅助画布上绘制
{
tempPix = pix;
QPainter painter1(&tempPix);
painter1.drawRect(x,y,w,h);
painter.drawPixmap(0, 0, tempPix);
} else {
QPainter painter1(&pix);
painter1.drawRect(x,y,w,h);
painter.drawPixmap(0,0,pix);
}
}
最后更改鼠标事件如下所示:
void Widget::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠标左键按下
{
startPoint = event->pos();
isPainting = true; //正在绘图
}
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠标左键释放
{
endPoint = event->pos();
isPainting = false; //结束绘图
update();
}
}
此时的运行如下图所示:
以上就是双缓冲(double-buffers)绘图
双缓冲绘图就是在进行绘制时先将所有内容都绘制到一个绘图设备上,然后再将整个图像绘制到部件上显示出来。使用双缓冲绘图可以有效的避免显示的闪烁现象,从Qt4.0以后,QWidget部件的所有绘制都自动默认使用了双缓冲绘制,所以一般没有必要再paintEvent中使用双缓冲绘制来避免闪烁,但是有的时候为了实现一些绘图效果,还要借助于双缓冲的。例如以上。上述程序中我们建立了两个QPixmap实例(画布),其中tempPix用来作为临时缓冲区,当鼠标正在拖动时,将矩形先绘制到这个临时画布tempPix上,然后将tempPix整个绘制到界面上;而另一个pix作为缓冲区,用来保存已经完成的绘制。当松开鼠标完成矩形的绘制后,则将tempPix的内容复制到pix上。为了绘制时不显示拖影,而且保证以前绘制的内容不消失,那么在移动鼠标过程中,每绘制一次,都要在绘制这个矩形的原来的图像上进行绘制,所以需要在每次绘制tempPix之前,先将pix的内容复制到tempPix上。因为这里有两个tempPix对象,也可以说有两个缓冲区,所以称之为双缓冲绘图。