【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
相关推荐
yookay zhang2 小时前
达梦数据库监听进程
网络·数据库·oracle
Archy_Wang_12 小时前
centos7的mysql做定时任务备份所有数据库
数据库·mysql
Java 码农2 小时前
MySQL基础操作案例设计
数据库·mysql
开心-开心急了3 小时前
关于Flutter与Qt for python 的一些技术、开源、商用等问题
开发语言·python·qt·flutter
友友马3 小时前
『 QT 』按钮类控件属性解析
开发语言·数据库·qt
vvw&3 小时前
如何在 Ubuntu 上安装 PostgreSQL
linux·运维·服务器·数据库·ubuntu·postgresql
qq_5470261793 小时前
Canal实时同步MySQL数据到Elasticsearch
数据库·mysql·elasticsearch
YJlio4 小时前
自动化实践(7.25):把 PsTools 接入 PowerShell / 批处理 / Ansible
microsoft·自动化·ansible
QT 小鲜肉4 小时前
【Git、GitHub、Gitee】按功能分类汇总Git常用命令详解(超详细)
c语言·网络·c++·git·qt·gitee·github