https://www.bilibili.com/video/BV1L24y1Q7hc
前面已经讲解了 QPainter 绘图的基本使用
其中包括:
绘制图形
点、线、矩形、圆角矩形、椭圆、圆、圆弧、饼图、弦图、多段线、多边形、路径、文本、图片
画笔设置
线宽、颜色、样式、连接、末端
画刷设置
颜色、样式
高级选项
变换、抗锯齿
当我们在项目中需要一些简单的绘制时,比如绘制温度曲线,可以直接使用 QPainter
当然了,如果需要更加复杂的曲线绘制,或柱状图等的绘制时,可以使用如下两个:
后面会出一个专题:《Qt开发专题-绘制曲线》,专门讲解这两个类的使用
本节使用 Qt 中的 QPainter
,实现绘制高低温曲线,效果如下:
本节包含以下技术点:
事件过滤器
画笔颜色、样式
虚线、实线
绘制文本
接下来开始,从新建工程开始讲解
首先,新建工程 TempCurve
首先,在窗口上拖放两个标签,修改它们的 name 为 lblHigh,lblLow,并设置它们在 widget 中垂直布局
然后,设置 widget 样式表:
QLabel {
background-color: rgb(63, 106, 138);
}
此时,效果如下:
最后,将 widget 中的空隙都设置为 0
此时,效果如下:
温度曲线绘制到标签上
首先,在 widget 中为标签安装事件过滤器
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
// 安装事件过滤器
ui->lblHigh->installEventFilter(this);
ui->lblLow->installEventFilter(this);
}
然后,在 widget.h 中声明 eventFilter
class Widget : public QWidget
{
public:
bool eventFilter(QObject* watched, QEvent* event);
};
并在 widget.cpp 中实现 eventFilter 函数
bool Widget::eventFilter(QObject* watched, QEvent* event)
{
if ( event->type() == QEvent::Paint ) {
if ( watched == ui->lblHigh ) {
// paintHigh(); // 后边实现
qDebug() << "paint lblHigh";
}
if ( watched == ui->lblLow ) {
// paintLow(); // 后边实现
qDebug() << "paint lblLow";
}
}
return QWidget::eventFilter(watched, event);
}
首先,在 widget.h 中声明这两个函数,并声明两个数组,用来记录高温和低温
class Widget : public QWidget
{
public:
// 绘制高低温曲线
void paintHigh();
void paintLow();
private:
int mHighTemp[7] = {0};
int mLowTemp[7] = {0};
};
然后,在 widget.cpp 中实现这两个函数
paintHigh 实现如下:
// 温度曲线相关的宏
#define PADDING 50
#define INCREMENT 8 // 温度曲线像素增量
#define POINT_RADIUS 3 // 曲线描点的大小
#define TEXT_OFFSET_X 12 // 温度文本相对于点的偏移
#define TEXT_OFFSET_Y 10 // 温度文本相对于点的偏移
void Widget::paintHigh()
{
QPainter painter(ui->lblHigh);
painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
// 1. 计算 x 轴坐标
int pointX[7] = {0};
for ( int i = 0; i < 7; i++ ) {
pointX[i] = ui->lblHigh->pos().x() + PADDING + (ui->lblHigh->width() - PADDING * 2) / 6 * i;
}
// 2. 计算 y 轴坐标
// 2.1 计算平均值
int tempSum = 0;
int tempAverage = 0;
for ( int i = 0; i < 7; i++ ) {
tempSum += mHighTemp[i];
}
tempAverage = tempSum / 7; // 最高温平均值
// 2.2 计算 y 轴坐标
int pointY[7] = {0};
int yCenter = ui->lblHigh->height() / 2;
int increment = ui->lblHigh->height() / 20;
for ( int i = 0; i < 7; i++ ) {
pointY[i] = yCenter - ((mHighTemp[i] - tempAverage) * increment);
}
// 3. 开始绘制
// 3.1 初始化画笔
QPen pen = painter.pen();
pen.setWidth(1); //设置画笔宽度为1
pen.setColor(QColor(255, 170, 0)); //设置颜色
painter.setPen(pen);
painter.setBrush(QColor(255, 170, 0)); //设置画刷颜色
painter.setFont(QFont("Microsoft YaHei", 14));
// 3.2 画点、写文本
for ( int i = 0; i < 7; i++ ) {
painter.drawEllipse(QPoint(pointX[i], pointY[i]), POINT_RADIUS, POINT_RADIUS);
painter.drawText(QPoint(pointX[i] - TEXT_OFFSET_X, pointY[i] - TEXT_OFFSET_Y), QString::number(mHighTemp[i]) + "°");
}
// 3.3 绘制曲线
for ( int i = 0; i < 6; i++ ) {
if ( i == 0 ) {
pen.setStyle(Qt::DotLine); //虚线
painter.setPen(pen);
} else {
pen.setStyle(Qt::SolidLine); // 实线
painter.setPen(pen);
}
painter.drawLine(pointX[i], pointY[i], pointX[i + 1], pointY[i + 1]);
}
}
paintLow 实现如下:
void Widget::paintLowCurve()
{
QPainter painter(ui->lblLowCurve);
painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
// 1. 计算 x 轴坐标
int pointX[7] = {0};
for ( int i = 0; i < 7; i++ ) {
pointX[i] = ui->lblLowCurve->pos().x() + PADDING + (ui->lblLowCurve->width() - PADDING * 2) / 6 * i;
}
// 2. 计算 y 轴坐标
// 2.1 计算平均值
int tempSum = 0;
int tempAverage = 0;
for ( int i = 0; i < 7; i++ ) {
tempSum += mLowTemperature[i];
}
tempAverage = tempSum / 7; // 最高温平均值
// 2.2 计算 y 轴坐标
int pointY[7] = {0};
int yCenter = ui->lblLowCurve->height() / 2;
int increment = ui->lblLowCurve->height() / 20;
for ( int i = 0; i < 7; i++ ) {
pointY[i] = yCenter - ((mLowTemperature[i] - tempAverage) * increment);
}
// 3. 开始绘制
// 3.1 初始化画笔
QPen pen = painter.pen();
pen.setWidth(1); // 设置画笔宽度为1
pen.setColor(QColor(0, 255, 255)); // 设置颜色
painter.setPen(pen);
painter.setBrush(QColor(0, 255, 255)); //设置画刷颜色
painter.setFont(QFont("Microsoft YaHei", 14));
// 3.2 画点、写文本
for ( int i = 0; i < 7; i++ ) {
painter.drawEllipse(QPoint(pointX[i], pointY[i]), POINT_RADIUS, POINT_RADIUS);
painter.drawText(QPoint(pointX[i] - TEXT_OFFSET_X, pointY[i] - TEXT_OFFSET_Y), QString::number(mLowTemperature[i]) + "°");
}
// 3.3 绘制曲线
for ( int i = 0; i < 6; i++ ) {
if ( i == 0 ) {
pen.setStyle(Qt::DotLine); //虚线
painter.setPen(pen);
} else {
pen.setStyle(Qt::SolidLine); // 实线
painter.setPen(pen);
}
painter.drawLine(pointX[i], pointY[i], pointX[i + 1], pointY[i + 1]);
}
}
此时,由于数组 mHighTemp 和 mLowTemp 数组的初始值都是 0 ,因此绘制出来的是7条水平直线的连接,如下:
接下来,随机生成高温数组、低温数组
首先,在 widget.h 中声明一个 updateTemp 的函数
class Widget : public QWidget
{
public:
// 更新高低温
void updateTemp();
};
然后,在 widget.cpp 中实现 updateTemp
#include
void Widget::updateTemp()
{
for ( int i = 0; i < 7; i++ ) {
mHighTemp[i] = 20 + QRandomGenerator::global()->generate() % 10;
mLowTemp[i] = -5 + QRandomGenerator::global()->generate() % 10;
}
ui->lblHigh->update();
ui->lblLow->update();
}
最后,在 widget.cpp 的构造中,手动调用 updateTemp
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
updateTemp();
// 安装事件过滤器
ui->lblHigh->installEventFilter(this);
ui->lblLow->installEventFilter(this);
}
此时,第一次运行后,就可以生成高低温曲线了,如下:
在 eventFilter 中,拦截鼠标双击事件,如下:
bool Widget::eventFilter(QObject* watched, QEvent* event)
{
if ( event->type() == QEvent::Paint ) {
if ( watched == ui->lblHigh ) {
paintHigh();
// qDebug() << "paint lblHigh";
}
if ( watched == ui->lblLow ) {
paintLow();
// qDebug() << "paint lblLow";
}
} else if ( event->type() == QEvent::MouseButtonDblClick ) {
updateTemp();
}
return QWidget::eventFilter(watched, event);
}
此时,每次双击鼠标,就可以刷新高低温曲线了!