Qt绘图并不复杂,无非就是QWidget和QPainter等控件的使用。很多人想要实现自己想要的控件效果,但一考虑到需要自己绘图,便觉得麻烦觉得难,开始退却。其实实际上手过的人都知道,Qt绘图很容易,里面的工具和方法也很全。
除了绘制自定义控件以外,有时候还会画一些模型图,尤其是允许用户修改模型的各个参数后能实现模型的实时变化的效果。加载的图片是静态的有限的,qt对svg的支持也不是相当友好,绘制功能是最合适的处理方法。
接下来介绍一下在实例中如何使用Qt绘图。
一、创建模型图类,继承于QWidget。
绘制是在paintEvent(QPaintEvent *event)中进行的,此函数为QWidget中protected的虚函数。因此要使用绘图必须继承QWidget并重写paintEvent()函数。
1、模型图类要实现的功能
需求a:支持按各个参数重新绘制。
需求b:支持放大、缩小模型图。
需求c:支持用鼠标拖拽移动模型图。
需求d:当点击、修改(获取焦点)某一参数时,该参数所对应的含义应在图中标识。
2、创建基类MyViewer
由于有了上述的需求,且我们的模型图肯定不止一个,但是除了参数和形状不同,其他公共需求都是相同的,因此我们可以创建一个基类,公共部分的功能可以在基类中实现,减少重复代码。
myviewer.h
#ifndef MYVIEWER_H
#define MYVIEWER_H
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QMouseEvent>
class MyViewer : public QWidget
{
Q_OBJECT
public:
explicit MyViewer(QWidget *parent = nullptr);
void setFlag(int index);//设置标识的参数
void zoomIn();//放大
void zoomOut();//缩小
protected:
void drawFlag(uchar type,QPointF s,QPointF e,QPainter* p);//绘制标识(标识长度的箭头)
void mousePressEvent(QMouseEvent *event) override;//鼠标事件,用来实现拖动功能
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
virtual void reSizeView()=0;//更新模型大小
int flag=0;//标识的参数index
int m_gain=50;//当前缩放系数
int old_gain;//旧缩放系数
private:
bool isMoving = false;//是否拖动中
QPoint startPoint;//拖动起始坐标
};
#endif // MYVIEWER_H
myviewer.cpp
#include "myviewer.h"
MyViewer::MyViewer(QWidget *parent) : QWidget(parent)
{
this->setCursor(Qt::OpenHandCursor);
}
void MyViewer::setFlag(int index)
{
flag = index;
update();
}
void MyViewer::zoomIn()
{
old_gain = m_gain;
m_gain += 5;
reSizeView();
}
void MyViewer::zoomOut()
{
old_gain = m_gain;
m_gain -= 5;
reSizeView();
}
void MyViewer::drawFlag(uchar type, QPointF s, QPointF e, QPainter *p)
{
QPointF p1,p2,p3,p4;
switch (type) {
case 0:
{
p1.setX(s.x()+8);
p1.setY(s.y()-5);
p2.setX(s.x()+8);
p2.setY(s.y()+5);
p3.setX(e.x()-8);
p3.setY(e.y()-5);
p4.setX(e.x()-8);
p4.setY(e.y()+5);
p->drawLine(p1,s);
p->drawLine(s,p2);
p->drawLine(p3,e);
p->drawLine(e,p4);
p->drawLine(s,e);
}
break;
case 1:
{
p1.setX(s.x()-5);
p1.setY(s.y()+8);
p2.setX(s.x()+5);
p2.setY(s.y()+8);
p3.setX(e.x()-5);
p3.setY(e.y()-8);
p4.setX(e.x()+5);
p4.setY(e.y()-8);
p->drawLine(p1,s);
p->drawLine(s,p2);
p->drawLine(p3,e);
p->drawLine(e,p4);
p->drawLine(s,e);
}
break;
case 2:
{
p1.setX(s.x()-8);
p1.setY(s.y()-5);
p2.setX(s.x()-8);
p2.setY(s.y()+5);
p3.setX(e.x()+8);
p3.setY(e.y()-5);
p4.setX(e.x()+8);
p4.setY(e.y()+5);
p->drawLine(p1,s);
p->drawLine(s,p2);
p->drawLine(p3,e);
p->drawLine(e,p4);
p->drawLine(QPointF(s.x()-12,s.y()),s);
p->drawLine(QPointF(e.x()+12,e.y()),e);
}
break;
case 3:
{
p1.setX(s.x()-5);
p1.setY(s.y()-8);
p2.setX(s.x()+5);
p2.setY(s.y()-8);
p3.setX(e.x()-5);
p3.setY(e.y()+8);
p4.setX(e.x()+5);
p4.setY(e.y()+8);
p->drawLine(p1,s);
p->drawLine(s,p2);
p->drawLine(p3,e);
p->drawLine(e,p4);
p->drawLine(QPointF(s.x(),s.y()-12),s);
p->drawLine(QPointF(e.x(),e.y()+12),e);
}
break;
}
}
void MyViewer::mousePressEvent(QMouseEvent *event)
{
isMoving = true;
startPoint = event->pos();
}
void MyViewer::mouseMoveEvent(QMouseEvent *event)
{
if(isMoving){
this->setGeometry(this->x()+event->pos().x()-startPoint.x(),this->y()+event->pos().y()-startPoint.y(),width(),height());
}
}
void MyViewer::mouseReleaseEvent(QMouseEvent *event)
{
isMoving = false;
startPoint = event->pos();
}
我们在MyViewer类中声明定义了几个公共功能:设置标记、缩放、拖动、绘制标记。具体绘制过程在具体的模型类里。
3.具体模型类
我们这里以圆台形烧杯的模型图做例子。首先它要继承于刚刚完成的MyViewer类,并重写paintEvent()、reSizeView()函数。
图形绘制原理:我们先定义确定一个图形所需要的关键点,如绘制三角形需要确定三个点的坐标,再将三个点连起来。我们绘制烧杯需要8个点,根据烧杯参数确定这8个点的坐标,然后连接起来即可。
editcone.h
#ifndef EDITCONE_H
#define EDITCONE_H
#include "myviewer.h"
#include "paramstruct.h"
#include "math.h"
class EditCone : public MyViewer
{
Q_OBJECT
public:
explicit EditCone(QWidget *parent = nullptr);
void setData(Cone c);//设置烧杯参数的接口
protected:
void paintEvent(QPaintEvent *event) override;
void reSizeView() override;
private:
QPointF *point=nullptr;
float C1=400; //烧杯口内径
float C2=350; //烧杯底内径
float D1=420; //烧杯内高度
float D2=350; //样品高度
float T1=5; //烧杯侧壁厚
float T2=5; //烧杯底厚度
};
#endif // EDITCONE_H
editcone.cpp
#include "editcone.h"
EditCone::EditCone(QWidget *parent) : MyViewer(parent)
{
this->setGeometry(0,0,471,867);
}
void EditCone::setData(Cone c)
{
//根据当前缩放系数和烧杯参数来调节实际参数
C1 = c.C1*m_gain;
C2 = c.C2*m_gain;
D1 = c.D1*m_gain;
D2 = c.D2*m_gain;
T1 = c.T1*m_gain;
T2 = c.T2*m_gain;
//初始化QPointF数组指针,根据参数计算各个点坐标
delete [] point;
point = nullptr;
point = new QPointF[8];
float ang = atan((C1-C2)/2/D1);
float w = T1/cos(ang);
float w2 = w-(C1-C2)/2*T2/D1;
point[0].setX(-C1/2-w);
point[0].setY(0);
point[1].setX(-C2/2-w2);
point[1].setY(D1+T2);
point[2].setX(C2/2+w2);
point[2].setY(D1+T2);
point[3].setX(C1/2+w);
point[3].setY(0);
point[4].setX(C1/2);
point[4].setY(0);
point[5].setX(C2/2);
point[5].setY(D1);
point[6].setX(-C2/2);
point[6].setY(D1);
point[7].setX(-C1/2);
point[7].setY(0);
update();
}
void EditCone::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter *painter = new QPainter(this);
painter->setRenderHint(QPainter::Antialiasing);
painter->translate(this->width()/2,20);//将坐标原定定为中间,距顶部20px位置
painter->setPen(QPen(Qt::black,1));
painter->setBrush(QBrush(QColor(254,229,153)));
painter->drawPolygon(point,8);//绘制容器
painter->setPen(Qt::NoPen);
painter->setBrush(QBrush(QColor(187,218,165)));
QPointF *p = new QPointF[4];//绘制容器内液体
p[0].setX(-C2/2-(C1-C2)/2*D2/D1);
p[0].setY(D1-D2);
p[1].setX(-C2/2);
p[1].setY(D1);
p[2].setX(C2/2);
p[2].setY(D1);
p[3].setX(C2/2+(C1-C2)/2*D2/D1);
p[3].setY(D1-D2);
painter->drawPolygon(p,4);
painter->setPen(QPen(Qt::red,2));
//绘制标记
switch (flag) {
case 1:
drawFlag(0,point[7],point[4],painter);
break;
case 2:
drawFlag(0,point[6],point[5],painter);
break;
case 11:
drawFlag(1,QPointF(-C2/2,0),point[6],painter);
break;
case 12:
drawFlag(1,QPointF(0,D1-D2),QPointF(0,D1),painter);
break;
case 21:
{
float ang = atan((C1-C2)/2/D1);
painter->translate(C2/2+(C1-C2)/4,D1/2);
painter->rotate(ang/M_PI*180);
drawFlag(2,QPointF(0,0),QPointF(T1,0),painter);
}
break;
case 22:
drawFlag(3,QPointF(0,D1),QPointF(0,D1+T2),painter);
break;
}
painter->end();
}
void EditCone::reSizeView()
{
C1 = C1/old_gain*m_gain;
C2 = C2/old_gain*m_gain;
D1 = D1/old_gain*m_gain;
D2 = D2/old_gain*m_gain;
T1 = T1/old_gain*m_gain;
T2 = T2/old_gain*m_gain;
delete [] point;
point = nullptr;
point = new QPointF[8];
float ang = atan((C1-C2)/2/D1);
float w = T1/cos(ang);
float w2 = w-(C1-C2)/2*T2/D1;
point[0].setX(-C1/2-w);
point[0].setY(0);
point[1].setX(-C2/2-w2);
point[1].setY(D1+T2);
point[2].setX(C2/2+w2);
point[2].setY(D1+T2);
point[3].setX(C1/2+w);
point[3].setY(0);
point[4].setX(C1/2);
point[4].setY(0);
point[5].setX(C2/2);
point[5].setY(D1);
point[6].setX(-C2/2);
point[6].setY(D1);
point[7].setX(-C1/2);
point[7].setY(0);
this->resize(C1+T1*2+40,D1+T2+40);
update();
}
二、调用模型类
在主界面头文件中声明:
EditCone *eco;
在源文件中使用:
eco = new EditCone(ui.wid_view);//ui.wid_view为父控件
m_cone.C1=ui.wid_param->searchValue("烧杯口内径C1")/m_gain;
m_cone.C2=ui.wid_param->searchValue("烧杯底内径C2")/m_gain;
m_cone.D1=ui.wid_param->searchValue("烧杯内高度D1")/m_gain;
m_cone.D2=ui.wid_param->searchValue("锥体样品高度D2")/m_gain;
m_cone.T1=ui.wid_param->searchValue("烧杯侧壁厚度T1")/m_gain;
m_cone.T2=ui.wid_param->searchValue("烧杯底厚度T2")/m_gain;
eco->setData(m_cone);
eco->show();
三、paintEvent()函数相关问题。
1.QPainter类
作为绘图中最重要的一个类,QPainter必不可少。而且QPainter只能在paintEvent()函数中被实例化。有以下几种方式:
直接在paintEvent()中声明
void EditCone::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
}
或者
void EditCone::paintEvent(QPaintEvent *event)
{
QPainter *painter=new QPainter(this);
}
先在头文件中声明QPainter指针,再在paintEvent中实例化
.h
QPainter *painter=nullptr;
.cpp
void EditCone::paintEvent(QPaintEvent *event)
{
painter=new QPainter(this);
}
有时候我们觉得所有的绘图操作都写在一个paintEvent里会显得臃肿,想拆分成几个函数。此时的QPainter必须声明为成员变量或成员变量指针,并在paintEvent中实例化。在其他函数中定义的QPainter局部变量是无效的。例如:
.h
QPainter *painter=nullptr;
void paintHead();
void paintBody();
.cpp
void EditCone::paintEvent(QPaintEvent *event)
{
painter=new QPainter(this);
paintHead();
paintBody();
}
void EditCone::paintHead()
{
painter.draw···;
}
void EditCone::paintBody()
{
painter.draw···;
}
2.QPainter设置
设置抗锯齿:painter->setRenderHint(QPainter::Antialiasing);
设置坐标原点:painter->translate(x,y);
设置画笔:painter->setPen(pen);
设置画笔形状、粗细、颜色:
QPen(const QBrush &brush, qreal width, Qt::PenStyle style = Qt::SolidLine, Qt::PenCapStyle cap = Qt::SquareCap, Qt::PenJoinStyle join = Qt::BevelJoin)
参数含义分别是:颜色、粗细、画笔形状(实线、虚线、点线等)、末端形状、转折处形状
设置填充:painter->setBrush(QBrush(QColor(254,229,153)));
3.QPainter绘制
绘制圆弧
void QPainter::drawArc(const QRectF &rectangle, int startAngle, int spanAngle)
rectangle圆弧范围,startAngle开始角度,spanAngle圆弧角度,注意两个角度必须为16的倍数。
void QPainter::drawArc(const QRect &rectangle, int startAngle, int spanAngle)
void QPainter::drawArc(int x, int y, int width, int height, int startAngle, int spanAngle)
绘制弦
与绘制圆弧参数相同,比圆弧多了一条连接头尾的直线。
void QPainter::drawChord(const QRectF &rectangle, int startAngle, int spanAngle)
void QPainter::drawChord(int x, int y, int width, int height, int startAngle, int spanAngle)
void QPainter::drawChord(const QRect &rectangle, int startAngle, int spanAngle)
绘制凸多边形
void QPainter::drawConvexPolygon(const QPointF *points, int pointCount)
void QPainter::drawConvexPolygon(const QPoint *points, int pointCount)
points 点坐标数组指针,pointCount点个数
void QPainter::drawConvexPolygon(const QPolygonF &polygon)
void QPainter::drawConvexPolygon(const QPolygon &polygon)
绘制椭圆
void QPainter::drawEllipse(const QRectF &rectangle)
void QPainter::drawEllipse(const QRect &rectangle)
void QPainter::drawEllipse(int x, int y, int width, int height)
void QPainter::drawEllipse(const QPointF ¢er, qreal rx, qreal ry)
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)