本节对应的视频讲解:B_站_视_频
https://www.bilibili.com/video/BV1x14y1J7rn
完成了界面布局,以及添加了初始化数据,就可以开始真正绘制图形了
本节讲解如何绘制点、线
1. 基本点线的绘制
1.1 为 cboShape 关联信号槽
首先,在 widget.h
中,声明一个槽函数
class Widget : public QWidget
{
private slots:
void shapeChanged();
};
然后,在 widget.cpp
中,实现这个槽函数,先空着,一会再来实现
void Widget::shapeChanged()
{
}
接着,在 widget.cpp
的构造中,关联信号和槽
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 5. 信号槽
// 形状
connect(ui->cboShape, SIGNAL(activated(QString)), this, SLOT(shapeChanged())); // 槽的参数可以比信号少
}
1.2 实现 shapeChanged 槽函数
shapeChanged
槽函数会调用 PaintWidget
类的方法完成绘制,如下:
void Widget::shapeChanged()
{
int index = ui->cboShape->currentIndex();
Shape shape = (Shape)ui->cboShape->itemData(index).toInt();
ui->paintWidget->setShape(shape);
}
1.3 实现 setShape 函数
接下来,在 PaintWidget
类中,来实现这个 setShape
函数
首先,在 PaintWidget.h
中声明一个该函数,并声明一个成员变量 mShape
class PaintWidget : public QWidget
{
public slots:
void setShape(Shape shape);
private:
Shape mShape;
};
然后,实现这个 setShape
函数
void PaintWidget::setShape(Shape shape)
{
this->mShape = shape;
update();
}
这里调用 update
函数,会自动调用该类的 paintEvent
函数,paintEvent
是重载的父类的
1.4 重载 paintEvent
接下来,就实现这个 paintEvent
函数,来完成真正的绘制
paintEvent
是什么?
paintEvent
是一个绘图函数,它是并不是由我们手动调用的, 而是由系统自动调用的。
调用时机:
-
窗口大小变化时
比如最大化、最小化、缩放窗口时,系统都会调用该函数
-
手动调用
update
手动调用 update 函数,同样会触发系统调用 paintEvent,从而完成界面的刷新。
首先,在 paintwidget.h
中声明 paintEvent
class PaintWidget : public QWidget
{
protected:
void paintEvent(QPaintEvent *event) override;
};
然后,在 paintwidget.cpp
中实现它
#include <QPainter>
void PaintWidget::paintEvent(QPaintEvent *event)
{
static const QPoint points[4] = {
QPoint(10, 80), //
QPoint(20, 10), //
QPoint(80, 30), //
QPoint(90, 70) //
};
// 创建画家类的对象,参数为绘图设备。指定为this,表示在当前窗口绘制
QPainter painter(this);
switch ( mShape ) {
// 点、线
case _Point:
painter.drawPoints(points, 4);
break;
case _Line:
painter.drawLine(points[0], points[2]);
break;
}
}
最后,在 widget.cpp
的构造中,手动调用一个 shapeChanged
槽函数,触发一下绘制
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
shapeChanged();
}
这样就完成了点、线的绘制,如下:
2. 循环绘制
由于在 Qt 中,坐标原点默认在左上角,因此以上将点、线绘制在了 PaintWidget
的左上角
接下来实现:将绘制的点、线铺满整个 PaintWidget
,也就是如下效果:
该如何实现呢?
以绘制线为例,由于向右 x
轴坐标增加,向下 y
轴坐标增加。因此,可以依次计算出每根线的两个端点的坐标,如下:
2.1 平移 translate
这种方式肯定可以实现的,不过 QPainter
为我们提供了更加简便的方法:
平移-translate
:它的作用是,移动绘图的坐标原点
有了平移的方法,就可以直接移动绘图的坐标原点,这样线段的两个端点的坐标就不需要改变,如下:
绘制线段a时:绘图的坐标原点为A(0, 0),此时,两个线段的两个端点为 (10, 80)、(80, 30)
绘制线段b时:先将绘图的坐标原点移动到B(100, 0),此时,两个线段的两个端点仍然为 (10, 80)、(80, 30)
绘制线段c时:先将绘图的坐标原点移动到C(0, 100),此时,两个线段的两个端点仍然为 (10, 80)、(80, 30)
2.2 QPainter 的保存和恢复
save
和 restore
用于保存和恢复 QPainter
的状态
状态包括画笔的状态,画刷的状态,以及绘图的坐标原点等
save
和 restore
是一一对应的,要成对出现。一个 save
就要对应一个 restore
2.3 具体实现
具体代码实现如下:
void PaintWidget::paintEvent(QPaintEvent *event)
{
static const QPoint points[4] = {
QPoint(10, 80), //
QPoint(20, 10), //
QPoint(80, 30), //
QPoint(90, 70) //
};
// 创建画家类的对象,参数为绘图设备。指定为this,表示在当前窗口绘制
QPainter painter(this);
for ( int x = 0; x < width(); x += 100 ) {
for ( int y = 0; y < height(); y += 100 ) {
// 先保存原来的状态
// (0,0),(0,100),(0,200),(0,300)...(0,600)
// (100,0),(100,100),(100,200),(100,300)...(100,600)
// ...
// (800,0),(800,100),(800,200),(800,300)...(800,600)
painter.save();
painter.translate(x, y); // translate 修改的是坐标系
switch ( mShape ) {
// 点、线
case _Point:
painter.drawPoints(points, 4);
break;
case _Line:
painter.drawLine(points[0], points[2]);
break;
}
painter.restore();
}
}
}
说明:
由于 save
和 restore
要成对出现。一个 save
就要对应一个 restore
因此把它们都放在第二个 for
循环中,这样才能保证,它们的一一对应。
经过以上修改,就可以循环绘制点、线了!