一、基础图形绘制
对于Qt中的图形绘制,最基础的元素之一就是“Qt中画家”——QPainter类,通过这个类,我们可以在任何QWidget上进行绘画,这个类拥有绘图所需的画笔(QPen),画刷(QBrush),字体(QFont),以及一系列针对不同形状(点、线、圆形、矩形等)不同参数的绘制函数。
绘图的基础在于QPaintDevice类,这个类代表着Qt的绘图板,并且通过这个类,可以设置绘图板的高度、宽度等参数。
我们来看Qt图形系统的关键角色的继承关系:
重点:对于图形的绘制,必须(只能)在QWidget::paintEvent()中进行,即必须在实现文件中重载这个函数,它在Qt中承担图像绘制承上启下的作用。
问题1:如何动态的绘制所需的图形?
解决方案:1、根据需要确定参数对象(绘图类型,点坐标,角度等);
2、将参数对象存入数据集合中(如:链表);
3、在paintEvent()函数中遍历数据集合;
4、根据参数对象绘制图形(update())
具体实现如下:
定义一个按钮,当按钮按下的时候,触发槽函数,随机绘制直线,矩形或圆形。
#include <QWidget>
#include <QPushButton>
#include <QPoint>
#include <QList>
class Widget : public QWidget
{
Q_OBJECT
enum //使用枚举来定义常量
{
LINE,
RECT,
ELLIPSE
};
struct DrawParameter
{
int type;
Qt::PenStyle pen;
QPoint begin;
QPoint end;
};
QPushButton m_PtBtn;
QList<DrawParameter> m_list; //使用链表表示绘图参数
protected slots:
void onTestBtnClicked();
protected:
void paintEvent(QPaintEvent *);
在头文件中,定义一个枚举类型的常量作为绘制图形形状的选项,同时定义一个DrawParameter的结构体,用来存放绘制图形的具体参数。
在penStyle这个类中,有多种画笔类型可供选择。查看帮助文档可以看到:
我们通过随机值来获取不同类型的画笔。同理,对于绘图形状和绘图的具体坐标,也通过随机值来产生。
实现代码如下:
void Widget::onTestBtnClicked()
{
DrawParameter dp =
{
qrand() % 3,
static_cast<Qt::PenStyle>(qrand() % 5 + 1), //笔的样式 随机取值
QPoint(qrand() % 400, qrand() % 300), //得到随机的两个点
QPoint(qrand() % 400, qrand() % 300)
};
if( m_list.count() == 5 )
{
m_list.clear();
}
m_list.append(dp); //数据进入链表--》 模型
update(); // 模型改变,视图应发生变化
}
注意:每次获取绘图信息之后,必须对窗口进行更新update();
上面我们定义了了一个DrawParameter类型的链表,在paintEvent()函数中,我们对这个链表的数据逐个取出,并且获取绘制不同类型图像的参数,并且通过switch语句,对随机出来的数据进行绘制。
实现代码如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.begin(this);
for(int i=0; i<m_list.count(); i++)
{
int x = (m_list[i].begin.x() < m_list[i].end.x() ) ? m_list[i].begin.x() : m_list[i].end.x();
int y = (m_list[i].begin.y() < m_list[i].end.y() ) ? m_list[i].begin.y() : m_list[i].end.y();
int w = qAbs(m_list[i].begin.x() - m_list[i].end.x()) + 1;
int h = qAbs(m_list[i].begin.y() - m_list[i].end.y()) + 1;
painter.setPen(m_list[i].pen);
switch (m_list[i].type)
{
case LINE:
painter.drawLine(m_list[i].begin, m_list[i].end);
break;
case RECT:
painter.drawRect(x, y, w, h);
break;
case ELLIPSE:
painter.drawEllipse(x, y, w, h);
break;
default:
break;
}
}
painter.end();
}
上述就是通过改变绘图参数来进行动态绘图的方法。
Qt中的坐标系统
首先必须了解两个概念:物理坐标系、逻辑坐标系。
物理坐标系:原点(0,0)在左上角的位置,单位为具体的像素(点),x坐标向右增长,y坐标向下增长;
逻辑坐标系:数学模型中的抽象坐标系,单位有具体的问题决定;坐标轴的增长方向由具体问题决定。
我们必须清楚,QPainter使用逻辑坐标系进行图形绘制,逻辑坐标系中的图形大小和位置经过转换后绘制于具体设备,默认情况下,逻辑坐标系于物理坐标系完全一致。
另两个概念:
视口(view port):物理坐标系中一个任意指定的矩形;
窗口(window):逻辑坐标系下对应到物理坐标系中的相同矩形。
一些概念:
转换关系:
通过一段代码来具体体会一下上面这些概念的区别。
我们来实现一个正弦函数的图像绘制:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPen pen;
pen.setColor(Qt::green);
pen.setStyle(Qt::SolidLine);
pen.setWidthF(0.01);
painter.setPen(pen);
painter.setViewport(50, 50, width()-100, height()-100);
qDebug() << width() << height() ;
painter.setWindow(-10, 2, 20, -4); //(-10, 2)(10, -2)
painter.fillRect(-10, 2, 20, -4, Qt::black);
painter.drawLine(QPointF(-10, 0), QPointF(10, 0));
painter.drawLine(QPointF(0, 2), QPointF(0, -2));
for(float x=-10; x<10; x+=0.01)
{
float y = qSin(x);
painter.drawPoint(QPointF(x ,y));
}
}
在paintEvent()函数中,我们注意,视口大小的定义是根据widget的大小定义的,我们通过qDebug()可以看到随着认为的拖动widget的同时,输出的宽度和高度也在不断变化。但是对于窗口(window),是采用直接输入具体参数的方式定义的。
具体效果如下:
当拖动其变小时:
通过上面这个实验,就可以清晰地看出,不同参数的具体应用和区别了。
视口和窗口其实指的是不同坐标系下的同一个矩形,视口用于实际物理设备上的图形显示,窗口用于逻辑坐标系下的图形绘制。