【QT自定义2D控件】QGraphics绘制仪表盘

前言

积累一些常用需要绘制的控件。

效果图

支持3档变色,源代码在资源中。

QGraphics框架核心概念

组件/概念 功能描述 关键特性
QGraphics框架整体 Graphics View Framework - 管理2D图形对象的完整框架 专为大量自定义、可交互的2D图形设计
**QGraphicsScene(场景)**​ 不可见的画布,管理所有图形项 • 管理图形项集合 • 碰撞检测 • 事件传播
**QGraphicsView(视图)**​ 显示场景内容的可视化组件 • 多个视图可观察同一场景 • 支持缩放、旋转 • 滚动和变换
**QGraphicsItem(图形项)**​ 场景中的基本图形单元 • 基础图形(圆形、矩形等) • 可交互的自定义项 • 支持鼠标、键盘事件

详细讲解

本篇按照5个步骤实现效果图中的仪表盘:

  • 绘制背景(外圆和内圆)
  • 绘制进度弧
  • 绘制刻度线和刻度值
  • 绘制中心文本(分数和单位)
  • 绘制底部文字

绘制背景(外圆和内圆)

绘制要点:

  1. 先绘制一个大圆,这个大圆的边界加粗;

  2. 绘制一个小圆,不设边界 ,小圆会覆盖与大圆交集的部分;

  3. 大圆与小圆的中心点要一致。

    //绘制背景(外圆和内圆)
    void ScoreMeter::drawBackground(QPainter painter)
    {
    //创建径向渐变用于外圆背景
    //圆圈中渐变色
    QRadialGradient gradient(0, 0, m_radius+10);
    gradient.setColorAt(0, QColor("#5CF9FE"));//中心颜色(较亮)
    gradient.setColorAt(1, QColor("#14141E"));//边缘颜色(较暗)
    painter->setBrush(gradient);//设置画刷为渐变,设置画笔为深灰色边框
    //边界圈样式
    painter->setPen(QPen(QColor("#32323C"), 6));//6像素宽的边框
    //绘制外圆
    painter->drawEllipse(-m_radius, -m_radius, 2
    m_radius, 2*m_radius);

    复制代码
     //创建内圆的径向渐变---会覆盖与外圆交集的区域
     int inner=m_radius-30;//内外圆之间的距离
     QRadialGradient innerGradient(0, 0, inner);
     innerGradient.setColorAt(0, QColor("#282832"));//中心颜色(较暗)
     innerGradient.setColorAt(1, QColor("#1E1E28"));//边缘颜色(较暗)
     painter->setBrush(innerGradient);
     //不设置边界
     painter->setPen(Qt::NoPen);
     //绘制内圆
     painter->drawEllipse(-inner, -inner, 2*inner, 2*inner);

    }

绘制进度弧

绘制要点:

  1. 设置最高分数点,以此来做180°的刻画;

  2. 设置起始点在左边:-180 * 16;

  3. 设置3个阶段显示的颜色;

  4. 设置进度宽度;

  5. 设置透明色宽度;(可选的效果)

    //绘制进度弧
    void ScoreMeter::drawProgressArc(QPainter *painter)
    {
    int radius=m_radius-10;//与外圈的距离
    //进度条比例:当前分数除以100
    double progress = static_cast<double>(m_score) / 100.0;
    //计算圆弧的跨度角度(以1/16度为单位)
    //180度对应半圆,乘以16转换为Qt的角度单位
    int spanAngle = -static_cast<int>(progress * 180 * 16);
    int startAngle = -180 * 16; //左侧起始位置
    //根据分数值选择颜色
    QColor mainColor, glowColor;
    glowColor = QColor("#64B4FF96"); //半透明色

    复制代码
     if (m_score <= 10) {
         //10分以内显示红色
         mainColor = QColor("#FF4141");  //红色
     } else if (m_score < 60) {
         //10-60分显示绿色
         mainColor = QColor("#41FF41");  //绿色
     } else {
         //60分以上显示蓝色
         mainColor = QColor("#4196FF");  //蓝色
     }
    
     //创建主进度弧的画笔(蓝色,10像素宽)
     QPen arcPen(mainColor, 10);
     //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
     painter->setPen(arcPen);
     //绘制主进度弧:
     //-180 * 16表示从底部开始(Qt的角度0度在3点钟方向,-180度在6点钟方向)
     painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle,spanAngle);
     //以下是可选的效果,可以注释掉
     QPen glowPen(glowColor, 6);//#64B4FF96的96是透明度(150/255 ≈ 0.59)
     //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
     painter->setPen(glowPen);
     painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle, spanAngle);

    }

绘制刻度线和刻度值

绘制要点:

  1. 设置刻度从0~10;

  2. 设置没间隔一个刻度显示数字;

  3. 数字作为文本进行绘制;

    //绘制刻度线和刻度值
    void ScoreMeter::drawScaleMarks(QPainter *painter)
    {
    //设置刻度线的画笔(浅灰色,半透明,2像素宽)
    painter->setPen(QPen(QColor("#C8C8C864"), 2));
    //绘制11个刻度(0%到100%,每10%一个刻度)
    for (int i = 0; i <= 10; ++i) {
    double angle = -180 + (i * 18);
    //将角度转换为弧度
    double rad = angle * M_PI / 180.0;
    //计算刻度线的起点和终点坐标(在85-95半径范围内)
    double x1 = 85 * cos(rad);
    double y1 = 85 * sin(rad);
    double x2 = 95 * cos(rad);
    double y2 = 95 * sin(rad);
    //绘制刻度线
    painter->drawLine(x1, y1, x2, y2);
    //每2个刻度(即20%)绘制一个数字标签
    if (i % 2 == 0) {
    QString text = QString::number(i * 10);
    QFont font("Arial", 8);//设置刻度数字字体
    painter->setFont(font);
    painter->setPen(QPen(QColor("#969696")));//刻度数字颜色(灰色)

    复制代码
             double textX = 75 * cos(rad);//文本X坐标
             double textY = 75 * sin(rad);//文本Y坐标
    
             QFontMetrics metrics(font);
             QRect textRect = metrics.boundingRect(text);//获取文本的矩形区域
             //绘制文本,使其居中于计算的位置
             //textX - textRect.width()/2: 水平居中
             //textY + textRect.height()/4: 垂直位置调整(不是严格居中)
             painter->drawText(textX - textRect.width()/2, textY + textRect.height()/4, text);
         }
     }

    }

绘制中心文本(分数和单位)

绘制要点:

  1. 设置字体格式;

  2. 分数设置为水平居中;

  3. 单位设置与分数的底部平齐;

    //绘制中心文本
    void ScoreMeter::drawCenterText(QPainter *painter)
    {
    painter->save();//保存 painter 的当前状态(变换、画笔、画刷等)
    painter->setRenderHint(QPainter::TextAntialiasing, true);//启用文本抗锯齿

    复制代码
     //根据图片调整字体大小
     QFont scoreFont("Arial", 36, QFont::Bold); //数值大小,QFont::Bold 粗体
     QFont unitFont("Arial", 18);               //单位大小
    
     QFontMetrics scoreMetrics(scoreFont);
     QFontMetrics unitMetrics(unitFont);
    
     QString scoreText = QString::number(m_score);
    
     //获取文本的边界矩形
     QRect scoreRect = scoreMetrics.boundingRect(scoreText);
     QRect unitRect = unitMetrics.boundingRect(m_unittext);
    
     //计算组合文本的总宽度
     int spacing = 2;  //稍微增加间距
     int totalWidth = scoreRect.width() + spacing + unitRect.width();
    
     int scoreBaselineY = (scoreMetrics.ascent() - scoreMetrics.descent()) / 2;//垂直居中
     int unitBaselineY = scoreBaselineY;//与分数的底部平齐
     //水平居中位置
     int startX = -totalWidth / 2;//数值的X坐标
    
     //绘制分数数字
     painter->setFont(scoreFont);
     painter->setPen(QPen(Qt::white));
     painter->drawText(startX, scoreBaselineY, scoreText);
    
     //绘制单位
     painter->setFont(unitFont);
     painter->setPen(QPen(QColor("#AAAABE")));//调整颜色更接近图片
    
     int unitX = startX + scoreRect.width() + spacing;//单位的x坐标,在分数的后面
     painter->drawText(unitX, unitBaselineY, m_unittext);
    
     painter->restore();//恢复 painter 的之前状态

    }

绘制底部文字

绘制要点:

  1. 设置字体格式;

  2. 从底部开始把位置上移。

    //绘制底部文字
    void ScoreMeter::drawBottomText(QPainter *painter)
    {
    QFont font("Arial", 10);
    painter->setFont(font);
    painter->setPen(QPen(QColor("#AAAABE")));

    复制代码
     QFontMetrics metrics(font);
     QRect textRect = metrics.boundingRect(m_Bottomtext);
     //在底部绘制文字,Y轴坐标:-20(外环距离)-10(字体大小)-10(字体与内环边界的距离)
     painter->drawText(-textRect.width()/2, m_radius-20-10-10, m_Bottomtext);

    }

控件源码

复制代码
//scoremeter.h
#ifndef SCOREMETER_H
#define SCOREMETER_H

#include <QGraphicsItem>
#include <QPainter>
#include <QDebug>
#include <iostream>
class ScoreMeter : public QGraphicsItem
{
public:
    explicit ScoreMeter(int score, QString unit, QString Bottom,QGraphicsItem *parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
    void setScore(int score);//设置分数的方法
private:
    int m_score;
    QString m_unittext;
    QString m_Bottomtext;
    int m_radius;//外圆半径

    void drawBackground(QPainter *painter);
    void drawProgressArc(QPainter *painter);
    void drawScaleMarks(QPainter *painter);
    void drawCenterText(QPainter *painter);
    void drawBottomText(QPainter *painter);
};

#endif // SCOREMETER_H

//scoremeter.cpp
#include "scoremeter.h"
#include <QLinearGradient>
#include <QRadialGradient>
#include <QFont>
#include <QFontMetrics>
#include <cmath>

ScoreMeter::ScoreMeter(int score, QString unit, QString Bottom, QGraphicsItem *parent)
    : QGraphicsItem(parent), m_score(score), m_unittext(unit), m_Bottomtext(Bottom)
{
    m_radius=100;
    setFlag(QGraphicsItem::ItemIsSelectable, false);//禁止选择该图形项
    setFlag(QGraphicsItem::ItemIsMovable, false);//禁止移动该图形项
}

//返回图形项的边界矩形
//这个矩形定义了图形项的绘制区域和碰撞检测区域
QRectF ScoreMeter::boundingRect() const
{
    //返回一个以(0,0)为中心,宽高各240的矩形
    //实际绘制区域是200x200的圆,额外留出20像素的边距
    return QRectF(-120, -120, 240, 240);
}

//绘制函数 - 主要的绘制逻辑在这里实现
//参数: painter - 用于绘制的画笔, option - 样式选项, widget - 关联的部件
void ScoreMeter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option)//声明不使用
    Q_UNUSED(widget)//声明不使用
    //启用抗锯齿,使图形边缘更平滑
    painter->setRenderHint(QPainter::Antialiasing, true);

    //按照从底层到顶层的顺序绘制各个组件:
    drawBackground(painter);     //1.绘制背景(外圆和内圆)
    drawProgressArc(painter);    //2.绘制进度弧(蓝色半圆)
    drawScaleMarks(painter);     //3.绘制刻度线和刻度值
    drawCenterText(painter);     //4.绘制中心文本(分数和单位)
    drawBottomText(painter);     //5.绘制底部文字
}

//绘制背景(外圆和内圆)
void ScoreMeter::drawBackground(QPainter *painter)
{
    //创建径向渐变用于外圆背景
    //圆圈中渐变色
    QRadialGradient gradient(0, 0, m_radius+10);
    gradient.setColorAt(0, QColor("#5CF9FE"));//中心颜色(较亮)
    gradient.setColorAt(1, QColor("#14141E"));//边缘颜色(较暗)
    painter->setBrush(gradient);//设置画刷为渐变,设置画笔为深灰色边框
    //边界圈样式
    painter->setPen(QPen(QColor("#32323C"), 6));//6像素宽的边框
    //绘制外圆
    painter->drawEllipse(-m_radius, -m_radius, 2*m_radius, 2*m_radius);

    //创建内圆的径向渐变---会覆盖与外圆交集的区域
    int inner=m_radius-30;//内外圆之间的距离
    QRadialGradient innerGradient(0, 0, inner);
    innerGradient.setColorAt(0, QColor("#282832"));//中心颜色(较暗)
    innerGradient.setColorAt(1, QColor("#1E1E28"));//边缘颜色(较暗)
    painter->setBrush(innerGradient);
    //不设置边界
    painter->setPen(Qt::NoPen);
    //绘制内圆
    painter->drawEllipse(-inner, -inner, 2*inner, 2*inner);
}

//绘制进度弧(蓝色半圆进度条)
void ScoreMeter::drawProgressArc(QPainter *painter)
{
    int radius=m_radius-10;//与外圈的距离
    //进度条比例:当前分数除以100
    double progress = static_cast<double>(m_score) / 100.0;
    //计算圆弧的跨度角度(以1/16度为单位)
    //180度对应半圆,乘以16转换为Qt的角度单位
    int spanAngle = -static_cast<int>(progress * 180 * 16);
    int startAngle = -180 * 16; //左侧起始位置
    //根据分数值选择颜色
    QColor mainColor, glowColor;
    glowColor = QColor("#64B4FF96"); //半透明色

    if (m_score <= 10) {
        //10分以内显示红色
        mainColor = QColor("#FF4141");  //红色
    } else if (m_score < 60) {
        //10-60分显示绿色
        mainColor = QColor("#41FF41");  //绿色
    } else {
        //60分以上显示蓝色
        mainColor = QColor("#4196FF");  //蓝色
    }

    //创建主进度弧的画笔(蓝色,10像素宽)
    QPen arcPen(mainColor, 10);
    //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
    painter->setPen(arcPen);
    //绘制主进度弧:
    //-180 * 16表示从底部开始(Qt的角度0度在3点钟方向,-180度在6点钟方向)
    painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle,spanAngle);

    QPen glowPen(glowColor, 6);//#64B4FF96的96是透明度(150/255 ≈ 0.59)
    //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
    painter->setPen(glowPen);
    painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle, spanAngle);
}

//绘制刻度线和刻度值
void ScoreMeter::drawScaleMarks(QPainter *painter)
{
    //设置刻度线的画笔(浅灰色,半透明,2像素宽)
    painter->setPen(QPen(QColor("#C8C8C864"), 2));
    //绘制11个刻度(0%到100%,每10%一个刻度)
    for (int i = 0; i <= 10; ++i) {
        double angle = -180 + (i * 18);
        //将角度转换为弧度
        double rad = angle * M_PI / 180.0;
        //计算刻度线的起点和终点坐标(在85-95半径范围内)
        double x1 = 85 * cos(rad);
        double y1 = 85 * sin(rad);
        double x2 = 95 * cos(rad);
        double y2 = 95 * sin(rad);
        //绘制刻度线
        painter->drawLine(x1, y1, x2, y2);
        //每2个刻度(即20%)绘制一个数字标签
        if (i % 2 == 0) {
            QString text = QString::number(i * 10);
            QFont font("Arial", 8);//设置刻度数字字体
            painter->setFont(font);
            painter->setPen(QPen(QColor("#969696")));//刻度数字颜色(灰色)

            double textX = 75 * cos(rad);//文本X坐标
            double textY = 75 * sin(rad);//文本Y坐标

            QFontMetrics metrics(font);
            QRect textRect = metrics.boundingRect(text);//获取文本的矩形区域
            //绘制文本,使其居中于计算的位置
            //textX - textRect.width()/2: 水平居中
            //textY + textRect.height()/4: 垂直位置调整(不是严格居中)
            painter->drawText(textX - textRect.width()/2, textY + textRect.height()/4, text);
        }
    }
}

//绘制中心文本
void ScoreMeter::drawCenterText(QPainter *painter)
{
    painter->save();//保存 painter 的当前状态(变换、画笔、画刷等)
    painter->setRenderHint(QPainter::TextAntialiasing, true);//启用文本抗锯齿

    //根据图片调整字体大小
    QFont scoreFont("Arial", 36, QFont::Bold); //数值大小,QFont::Bold 粗体
    QFont unitFont("Arial", 18);               //单位大小

    QFontMetrics scoreMetrics(scoreFont);
    QFontMetrics unitMetrics(unitFont);

    QString scoreText = QString::number(m_score);

    //获取文本的边界矩形
    QRect scoreRect = scoreMetrics.boundingRect(scoreText);
    QRect unitRect = unitMetrics.boundingRect(m_unittext);

    //计算组合文本的总宽度
    int spacing = 2;  //稍微增加间距
    int totalWidth = scoreRect.width() + spacing + unitRect.width();

    int scoreBaselineY = (scoreMetrics.ascent() - scoreMetrics.descent()) / 2;//垂直居中
    int unitBaselineY = scoreBaselineY;//与分数的底部平齐
    //水平居中位置
    int startX = -totalWidth / 2;//数值的X坐标

    //绘制分数数字
    painter->setFont(scoreFont);
    painter->setPen(QPen(Qt::white));
    painter->drawText(startX, scoreBaselineY, scoreText);

    //绘制单位
    painter->setFont(unitFont);
    painter->setPen(QPen(QColor("#AAAABE")));//调整颜色更接近图片

    int unitX = startX + scoreRect.width() + spacing;//单位的x坐标,在分数的后面
    painter->drawText(unitX, unitBaselineY, m_unittext);

    painter->restore();//恢复 painter 的之前状态
}

//绘制底部文字
void ScoreMeter::drawBottomText(QPainter *painter)
{
    QFont font("Arial", 10);
    painter->setFont(font);
    painter->setPen(QPen(QColor("#AAAABE")));

    QFontMetrics metrics(font);
    QRect textRect = metrics.boundingRect(m_Bottomtext);
    //在底部绘制文字,Y轴坐标:-20(外环距离)-10(字体大小)-10(字体与内环边界的距离)
    painter->drawText(-textRect.width()/2, m_radius-20-10-10, m_Bottomtext);

}

//添加setScore方法实现
void ScoreMeter::setScore(int score)
{
    m_score = score;
    update(); //触发重绘
}

效果图中的MainWindow的代码

如下图,UI上添加3个控件,并且按钮右键菜单"转到槽..."

以下是MainWindow的代码:

复制代码
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "scoremeter.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    QGraphicsScene *scene;
    ScoreMeter *scoreMeter;
    void setupScoreMeter();
};

#endif // MAINWINDOW_H

//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , scene(nullptr)
    , scoreMeter(nullptr)
{
    ui->setupUi(this);
    setWindowTitle("QGraphicsView圆形表盘");
    //创建图形场景
    scene = new QGraphicsScene(this);
    //设置场景到graphicsView
    ui->graphicsView->setScene(scene);

    //设置视图属性
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);//抗锯齿
    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//禁用水平滚动条
    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//禁用垂直滚动条
    //去掉边框,背景设为透明
    ui->graphicsView->setStyleSheet("QGraphicsView { border: none;background: transparent; }");

    //初始化ScoreMeter
    setupScoreMeter();

}

MainWindow::~MainWindow()
{
    delete ui;
    delete scene;
}

void MainWindow::setupScoreMeter()
{
    //如果已存在ScoreMeter,先移除
    if (scoreMeter) {
        scene->removeItem(scoreMeter);
        delete scoreMeter;
    }

    //获取初始分数(默认为0)
    int initialScore = 0;
    if (!ui->score->text().isEmpty()) {
        bool ok;
        int score = ui->score->text().toInt(&ok);
        if (ok && score >= 0 && score <= 100) {
            initialScore = score;
        }
    }

    //创建新的ScoreMeter
    scoreMeter = new ScoreMeter(initialScore, "分", "性能评分");
    scene->addItem(scoreMeter);

    //居中显示
    ui->graphicsView->centerOn(scoreMeter);

    //可选:调整视图大小
    ui->graphicsView->setFixedSize(210, 210);
}

void MainWindow::on_pushButton_clicked()
{
    //获取文本框中的分数
    if (ui->score->text().isEmpty()) {
        QMessageBox::warning(this, "输入错误", "请输入分数(0-100)");
        return;
    }

    bool ok;
    int newScore = ui->score->text().toInt(&ok);

    if (!ok || newScore < 0 || newScore > 100) {
        QMessageBox::warning(this, "输入错误", "请输入0-100之间的有效分数");
        return;
    }

    //更新ScoreMeter的分数
    if (scoreMeter) {
        scoreMeter->setScore(newScore);
    }
}

.pro文件

复制代码
QT       += core gui widgets
相关推荐
IvorySQL3 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·3 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德3 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫4 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i4 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.4 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn4 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露5 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
冰暮流星5 小时前
sql语言之分组语句group by
java·数据库·sql
符哥20085 小时前
Ubuntu 常用指令集大全(附实操实例)
数据库·ubuntu·postgresql