例1:嵌入式风格 - 轻量级工业仪表盘
widget.h
#ifndef WIDGET_H // 防止头文件重复包含
#define WIDGET_H
#include <QWidget> // Qt窗口基类
#include <QTimer> // 定时器类
class Widget : public QWidget // 继承QWidget的自定义窗口类
{
Q_OBJECT // Qt元对象宏,启用信号槽机制
public:
Widget(QWidget *parent = nullptr); // 构造函数,parent指定父窗口
~Widget(); // 析构函数
protected:
void paintEvent(QPaintEvent *event) override; // 重写绘制事件
private slots:
void updateValue(); // 私有槽函数:更新数值
private:
double m_value; // 当前仪表盘数值 (0-100)
QTimer *m_timer; // 定时器指针,用于周期性更新
};
#endif // WIDGET_H // 结束头文件保护
widget.cpp
#include "widget.h" // 引入自定义头文件
#include <QPainter> // 绘图类
#include <QtMath> // 数学函数库
#include <QRandomGenerator> // 随机数生成器
Widget::Widget(QWidget *parent) // 构造函数实现
: QWidget(parent), m_value(50) // 初始化父类和成员变量m_value为50
{
setFixedSize(300, 320); // 设置窗口固定大小,模拟嵌入式屏幕分辨率
m_timer = new QTimer(this); // 创建定时器对象,指定父对象为this以便自动释放
connect(m_timer, &QTimer::timeout, this, &Widget::updateValue); // 连接定时器超时信号到更新槽
m_timer->start(1000); // 启动定时器,间隔1000毫秒(1秒)
}
Widget::~Widget() {} // 析构函数,Qt会自动删除子对象,无需手动释放
void Widget::paintEvent(QPaintEvent *) // 重写绘制事件,参数未使用故省略名称
{
QPainter painter(this); // 创建画家对象,绘制目标为本窗口
painter.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿渲染
// 绘制黑色背景
painter.fillRect(rect(), Qt::black); // 用黑色填充整个窗口区域
// 计算表盘中心点和半径
int centerX = width() / 2; // 中心X坐标
int centerY = height() / 2 + 10; // 中心Y坐标,向下偏移10像素
int radius = qMin(centerX, centerY) - 20; // 表盘半径,取宽高较小值减20
// 绘制白色外圈
painter.setPen(QPen(Qt::white, 4)); // 设置画笔:白色,宽度4像素
painter.drawEllipse(QPoint(centerX, centerY), radius, radius); // 画椭圆(正圆)
// 绘制刻度线和数字
painter.setPen(QPen(Qt::cyan, 2)); // 设置画笔:青色,宽度2像素
for (int i = 0; i <= 8; ++i) { // 循环9个刻度(0到8)
double angle = (180.0 + i * 22.5) * M_PI / 180.0; // 角度从180度开始,步长22.5度,转换为弧度
int x1 = centerX + (radius - 15) * cos(angle); // 刻度线起点X
int y1 = centerY - (radius - 15) * sin(angle); // 刻度线起点Y(Y轴向下为正,故用减法)
int x2 = centerX + (radius - 30) * cos(angle); // 刻度线终点X
int y2 = centerY - (radius - 30) * sin(angle); // 刻度线终点Y
painter.drawLine(x1, y1, x2, y2); // 绘制刻度线
// 显示刻度对应的数值(0-200)
QString numStr = QString::number(i * 25); // 将索引乘以25得到数值字符串
painter.setFont(QFont("Arial", 10)); // 设置字体:Arial,字号10
painter.setPen(Qt::green); // 设置画笔颜色:绿色
int nx = centerX + (radius - 45) * cos(angle) - 10; // 数字显示位置的X坐标
int ny = centerY - (radius - 40) * sin(angle) + 5; // 数字显示位置的Y坐标
painter.drawText(nx, ny, numStr); // 在指定位置绘制文本
}
// 绘制红色指针
double pointerAngle = (180.0 + m_value * 18.0 / 10.0) * M_PI / 180.0; // 将数值映射到180度范围(0对应180°,100对应360°)
int pointerLen = radius - 35; // 指针长度
int px = centerX + pointerLen * cos(pointerAngle); // 指针尖端X坐标
int py = centerY - pointerLen * sin(pointerAngle); // 指针尖端Y坐标
painter.setPen(QPen(Qt::red, 3)); // 设置画笔:红色,宽度3
painter.drawLine(centerX, centerY, px, py); // 从中心画线到指针尖端
// 绘制中心黄色圆点
painter.setBrush(Qt::yellow); // 设置画刷:黄色
painter.drawEllipse(QPoint(centerX, centerY), 6, 6); // 画半径为6的实心圆
// 显示当前数值文本
painter.setPen(Qt::white); // 设置画笔:白色
painter.setFont(QFont("Arial", 14, QFont::Bold)); // 设置字体:Arial,14号,粗体
painter.drawText(QRect(0, height()-40, width(), 30), Qt::AlignCenter, // 在底部区域居中显示
QString("Speed: %1 km/h").arg(m_value, 0, 'f', 1)); // 格式化数值,保留一位小数
}
void Widget::updateValue() // 槽函数实现:更新数值
{
// 模拟传感器数据波动:生成10~90之间的随机浮点数
m_value = QRandomGenerator::global()->bounded(80.0) + 10.0;
update(); // 调用update()请求重绘,触发paintEvent
}
例2:图形渲染基础 - 动态粒子系统
particlewidget.h
#ifndef PARTICLEWIDGET_H // 防止头文件重复包含
#define PARTICLEWIDGET_H
#include <QWidget> // Qt窗口基类
#include <QList> // Qt列表容器
#include <QPointF> // 浮点坐标点
#include <QColor> // 颜色类
// 定义粒子结构体
struct Particle {
QPointF pos; // 粒子当前位置(浮点坐标)
QPointF vel; // 粒子速度向量
float size; // 粒子大小(半径)
float alpha; // 透明度(0~255)
QColor color; // 粒子颜色
};
class ParticleWidget : public QWidget // 继承QWidget的自定义窗口类
{
Q_OBJECT // Qt元对象宏
public:
explicit ParticleWidget(QWidget *parent = nullptr); // 显式构造函数
protected:
void paintEvent(QPaintEvent *event) override; // 重写绘制事件
void mousePressEvent(QMouseEvent *event) override; // 重写鼠标按下事件
private:
void addParticles(const QPointF ¢er); // 在指定位置生成一批粒子
void updateParticles(); // 更新所有粒子的状态
QList<Particle> m_particles; // 存储所有存活粒子的列表
QTimer *m_timer; // 定时器指针,驱动动画帧
};
#endif // PARTICLEWIDGET_H
particlewidget.cpp
cpp
#include "particlewidget.h" // 引入自定义头文件
#include <QPainter> // 绘图类
#include <QMouseEvent> // 鼠标事件类
#include <QTimer> // 定时器类
#include <QRandomGenerator> // 随机数生成器
#include <QtMath> // 数学函数库
#include <QtGlobal>
ParticleWidget::ParticleWidget(QWidget *parent) // 构造函数实现
: QWidget(parent) // 初始化父类
{
setWindowTitle("Qt 粒子系统"); // 设置窗口标题
resize(600, 400); // 设置窗口初始大小
setMouseTracking(true); // 启用鼠标追踪(即使不按下也跟踪移动)
m_timer = new QTimer(this); // 创建定时器对象
connect(m_timer, &QTimer::timeout, [this](){ // 连接定时器超时信号到lambda表达式
updateParticles(); // 更新粒子状态
update(); // 请求重绘
});
m_timer->start(16); // 启动定时器,间隔16毫秒,约60帧每秒
}
void ParticleWidget::addParticles(const QPointF ¢er) // 在指定中心位置生成粒子
{
auto *gen = QRandomGenerator::global(); // 获取全局随机数生成器指针
for (int i = 0; i < 30; ++i) { // 每次生成30个粒子
Particle p; // 创建临时粒子对象
// 随机方向,速度2-6
double angle = gen->bounded(2 * M_PI); // 随机角度0~2π
double speed = gen->bounded(4.0) + 2.0; // 随机速度2~6
p.pos = center; // 粒子起始位置为点击点
p.vel = QPointF(cos(angle) * speed, sin(angle) * speed); // 根据角度和速度计算速度向量
p.size = gen->bounded(6.0) + 2.0; // 随机大小2~8
p.alpha = 255; // 初始透明度完全不透明
// 随机亮色(RGB各分量0~255)
p.color = QColor(gen->bounded(256), gen->bounded(256), gen->bounded(256));
m_particles.append(p); // 将粒子添加到列表中
}
}
void ParticleWidget::updateParticles() // 更新所有粒子状态
{
for (auto &p : m_particles) { // 遍历每一个粒子(引用方式,可修改)
// 物理运动
p.pos += p.vel; // 位置增加速度
p.vel *= 0.98; // 速度乘以阻尼系数0.98,模拟空气阻力
p.size += 0.3; // 粒子逐渐膨胀
p.alpha -= 5; // 透明度逐渐降低(淡出)
}
// 移除死亡粒子:透明度<=0 或 大小>30
m_particles.erase(std::remove_if(m_particles.begin(), m_particles.end(),
[](const Particle &p){ return p.alpha <= 0 || p.size > 30; }),
m_particles.end());
}
void ParticleWidget::paintEvent(QPaintEvent *) // 重写绘制事件
{
QPainter painter(this); // 创建画家对象
painter.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿
painter.fillRect(rect(), Qt::black); // 填充黑色背景
for (const auto &p : m_particles) { // 遍历所有存活粒子
QColor c = p.color; // 获取粒子颜色
float clamped = qBound(0.0f, p.alpha, 255.0f);
c.setAlpha(static_cast<int>(clamped));
painter.setBrush(c); // 设置画刷颜色
painter.setPen(Qt::NoPen); // 取消边框
painter.drawEllipse(p.pos, p.size, p.size); // 绘制圆形粒子
}
}
void ParticleWidget::mousePressEvent(QMouseEvent *event) // 重写鼠标按下事件
{
if (event->button() == Qt::LeftButton) { // 如果是左键按下
addParticles(event->pos()); // 在鼠标位置生成粒子
}
}
例3:行业应用 - 医学影像窗口调整
dicomwidget.h
#ifndef DICOMWIDGET_H // 防止头文件重复包含
#define DICOMWIDGET_H
#include <QWidget> // Qt窗口基类
#include <QImage> // 图像类
#include <QPoint> // 整数坐标点
class DicomWidget : public QWidget // 继承QWidget的自定义窗口类
{
Q_OBJECT // Qt元对象宏
public:
explicit DicomWidget(QWidget *parent = nullptr); // 显式构造函数
protected:
void paintEvent(QPaintEvent *event) override; // 重写绘制事件
void mousePressEvent(QMouseEvent *event) override; // 重写鼠标按下事件
void mouseMoveEvent(QMouseEvent *event) override; // 重写鼠标移动事件
void wheelEvent(QWheelEvent *event) override; // 重写滚轮事件
private:
QImage m_source; // 原始图像(模拟医学影像)
QImage m_display; // 经过窗宽窗位处理后的显示图像
int m_windowWidth; // 窗宽(Window Width)
int m_windowLevel; // 窗位(Window Level)
bool m_isDragging; // 是否正在拖拽鼠标
QPoint m_lastPos; // 上次鼠标位置
void updateDisplay(); // 根据当前窗宽窗位更新显示图像
};
#endif // DICOMWIDGET_H
dicomwidget.cpp
#include "dicomwidget.h" // 引入自定义头文件
#include <QPainter> // 绘图类
#include <QMouseEvent> // 鼠标事件类
#include <QWheelEvent> // 滚轮事件类
#include <cmath> // 标准数学函数
DicomWidget::DicomWidget(QWidget *parent) // 构造函数实现
: QWidget(parent), m_windowWidth(256), m_windowLevel(128), m_isDragging(false) // 初始化成员变量
{
// 创建一个渐变灰度图作为模拟医学图像(512x512,8位灰度)
m_source = QImage(512, 512, QImage::Format_Grayscale8);
for (int y = 0; y < 512; ++y) { // 遍历每一行
for (int x = 0; x < 512; ++x) { // 遍历每一列
// 模拟不同组织密度:使用正弦余弦组合,并加入两个矩形区域模拟病灶
int val = static_cast<int>(
128 + 64 * sin(x * 0.02) + 32 * cos(y * 0.03) + // 基础纹理
(x < 150 ? 40 : 0) + // 左侧高密度区
(y > 350 ? -30 : 0) // 下方低密度区
);
m_source.setPixel(x, y, qBound(0, val, 255)); // 设置像素值,限制在0~255
}
}
updateDisplay(); // 初始化显示图像
}
void DicomWidget::updateDisplay() // 根据当前窗宽窗位重新计算显示图像
{
// 创建与源图像同尺寸的32位ARGB图像(用于显示)
m_display = QImage(m_source.size(), QImage::Format_ARGB32);
// 计算窗宽窗位对应的灰度映射范围
double minVal = m_windowLevel - m_windowWidth / 2.0; // 窗的下界
double maxVal = m_windowLevel + m_windowWidth / 2.0; // 窗的上界
for (int y = 0; y < m_source.height(); ++y) { // 遍历每一行
for (int x = 0; x < m_source.width(); ++x) { // 遍历每一列
int pixel = qGray(m_source.pixel(x, y)); // 获取源图像该点的灰度值(0~255)
// 将像素值映射到0~255之间:超出窗范围的被截断
double mapped = (pixel - minVal) / (maxVal - minVal) * 255.0;
int gray = qBound(0, static_cast<int>(mapped), 255); // 限制在0~255
m_display.setPixel(x, y, qRgb(gray, gray, gray)); // 设置为灰度颜色
}
}
update(); // 请求重绘窗口
}
void DicomWidget::paintEvent(QPaintEvent *) // 重写绘制事件
{
QPainter painter(this); // 创建画家对象
painter.setRenderHint(QPainter::SmoothPixmapTransform); // 开启平滑缩放变换
// 将显示图像按比例缩放以适应窗口,保持宽高比,使用平滑变换
QImage scaled = m_display.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
// 计算居中显示的左上角坐标
int x = (width() - scaled.width()) / 2;
int y = (height() - scaled.height()) / 2;
painter.drawImage(x, y, scaled); // 绘制缩放后的图像
// 在左上角显示当前窗宽窗位参数
painter.setPen(Qt::cyan); // 设置画笔颜色:青色
painter.setFont(QFont("Consolas", 12)); // 设置等宽字体
painter.drawText(10, 30, QString("WW: %1 WL: %2") // 显示文本
.arg(m_windowWidth).arg(m_windowLevel));
}
void DicomWidget::mousePressEvent(QMouseEvent *event) // 重写鼠标按下事件
{
if (event->button() == Qt::LeftButton) { // 如果是左键按下
m_isDragging = true; // 标记开始拖拽
m_lastPos = event->pos(); // 记录当前鼠标位置
}
}
void DicomWidget::mouseMoveEvent(QMouseEvent *event) // 重写鼠标移动事件
{
if (m_isDragging) { // 如果处于拖拽状态
QPoint delta = event->pos() - m_lastPos; // 计算鼠标位移
// 水平拖动改变窗宽,垂直拖动改变窗位
m_windowWidth += delta.x(); // 向右增加窗宽
m_windowLevel -= delta.y(); // 向上增加窗位(注意Y轴方向)
// 限制窗宽和窗位的合理范围
m_windowWidth = qBound(1, m_windowWidth, 511); // 窗宽至少1,最大511
m_windowLevel = qBound(0, m_windowLevel, 255); // 窗位0~255
m_lastPos = event->pos(); // 更新上次位置
updateDisplay(); // 重新计算显示图像
}
}
void DicomWidget::wheelEvent(QWheelEvent *event) // 重写滚轮事件
{
// 滚轮滚动改变窗宽:每格120单位,每步增减10
int steps = event->angleDelta().y() / 120; // 获取滚轮步数(正值向上,负值向下)
m_windowWidth += steps * 10; // 调整窗宽
m_windowWidth = qBound(1, m_windowWidth, 510); // 限制范围
updateDisplay(); // 重新计算显示图像
}