前言

上一篇文章讲到 QSS 在每次重绘时都要走 CSS 解析和盒模型计算,而 QStyle 直接用 C++ 计算。这只是冰山一角——Qt 的渲染管线远比"画出来"三个字复杂。这篇文章深入到像素之前的每一步:QPainter 的引擎架构、QWidget 的后备存储(backing store)、QML 的场景图(scene graph)机制,以及它们和原生 OpenGL 绘制的本质区别。

读完你会理解:

  • 为什么有时候 update() 了但看不到变化
  • QPainter 画的东西什么时候在你的 GPU 上
  • QML 的控件定义逻辑和 QStyle 到底有什么不同
  • 什么场景选 QWidgets + QStyle,什么场景选 QML

第一层:Qt 的绘制驱动链——从事件到像素

sequenceDiagram
    participant OS as 操作系统
    participant QPA as Qt Platform Abstraction
    participant W as QWidget
    participant BE as QBackingStore
    participant PE as QPaintEngine
    participant GPU as GPU / 帧缓冲

    OS->>QPA: Expose / WM_PAINT
    QPA->>W: paintEvent()
    W->>W: initStyleOption()
    W->>W: 调用 QStyle::drawControl()...
    W->>BE: beginPaint(region)
    BE->>PE: 创建 QPainter(绑定 paintDevice)
    W->>PE: QPainter::drawLine / drawRect / drawText ...
    Note over PE: 转化为内部指令<br/>(矢量操作 or 光栅操作)
    W->>BE: endPaint()
    BE->>QPA: flush()
    QPA->>OS: 提交帧缓冲
    OS->>GPU: 显示

关键概念:绘制是异步的。

当你调用 QWidget::update() 时,Qt 只是标记了一个"脏区域”。实际的 paintEvent() 会在事件循环的下一个周期才被调用。而且 Qt 会自动合并同一帧内的多次 update() 调用。

// 这段代码只会触发一次 paintEvent,不是两次
widget->update();  // 标记脏区域
widget->update();  // 合并到同一个脏区域
// paintEvent 稍后在事件循环中执行

第二层:QPainter 的 Paint Engine 架构

QPainter 本身不渲染任何东西。它是一个命令记录器,把绘制指令转发给底层的 QPaintEngine

graph TD
    QP["<b>QPainter</b><br/>(命令记录器)"]

    QP --> R["<b>QRasterPaintEngine</b><br/>CPU 光栅化<br/>最常用"]
    QP --> O["<b>QOpenGLPaintEngine</b><br/>OpenGL ES 2.0<br/>(Qt 5/6)"]
    QP --> PDF["<b>QPdfPaintEngine</b><br/>PDF 输出"]
    QP --> SVG["<b>QSvgPaintEngine</b><br/>SVG 输出"]

    R --> R1["绘制到 QImage"]
    R --> R2["绘制到 QPixmap"]
    R --> R3["绘制到 QBackingStore"]
    R --> R4["绘制到 QPrinter"]

    O --> O1["QOpenGLWidget"]
    O --> O2["QOpenGLWindow"]
    O --> O3["QQuickWindow(部分场景)"]

    style QP fill:#E67E22,color:#fff,stroke:#CA6F1E
    style R fill:#27AE60,color:#fff,stroke:#1E8449
    style O fill:#3498DB,color:#fff,stroke:#2471A3

2.1 QRasterPaintEngine:Qt 的主力引擎

这是 Qt Widgets 默认使用的引擎。所有绘制操作最终转化为 CPU 端的光栅化:

// 当你写:
QPainter p(widget);
p.setBrush(Qt::red);
p.drawRoundedRect(QRect(10, 10, 100, 50), 8, 8);

// QRasterPaintEngine 内部(简化):
// 1. 裁剪检查
// 2. 将圆角矩形拆解为扫描线
// 3. 计算每个扫描线的抗锯齿覆盖值
// 4. 写入 QImage 的像素缓冲区
// 5. 格式转换(如果需要)

重要限制:QRasterPaintEngine 默认运行在 CPU 上。每次 drawControl() 调用中的 QPainter 操作都会经过 CPU 光栅化,结果写入 QImage 像素缓冲区。这个缓冲区存在于系统内存中。只有当 QBackingStore::flush() 被调用时,才会将脏区域拷贝到 GPU 纹理供显示。

2.2 QOpenGLPaintEngine:GPU 加速的 QPainter

从 Qt 5 开始,QPainter 可以工作在 OpenGL 后端上:

#include <QOpenGLWidget>
#include <QPainter>

class MyGLWidget : public QOpenGLWidget {
protected:
    void paintGL() override {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing);
        p.setPen(QPen(Qt::white, 2));
        p.drawLine(0, 0, width(), height());
        // 这个 QPainter 实际上通过 QOpenGLPaintEngine 工作
        // 所有操作被转化为 OpenGL ES 2.0 调用
    }
};

QOpenGLPaintEngine 的限制

  • 只支持 OpenGL ES 2.0 子集
  • 某些高级 QPainter 特性降级为 CPU 端计算(composition modes、complex paths)
  • 带有复杂路径的抗锯齿文字在 OpenGL 引擎中效率低于 raster 引擎
  • 不支持 QPainter::drawPicture()

2.3 Paint Engine 的选择规则

// QPainter 根据 paintDevice 类型自动选择引擎
QPainter p1(widget);           // 普通 QWidget → QRasterPaintEngine
QPainter p2(&image);           // QImage → QRasterPaintEngine
QPainter p3(&pixmap);          // QPixmap → QRasterPaintEngine
QPainter p4(glWidget);         // QOpenGLWidget → QOpenGLPaintEngine
QPainter p5(&printer);         // QPrinter → 取决于打印机后端

第三层:QWidget 的 Backing Store 与双缓冲

3.1 什么是 Backing Store

每个顶层窗口(QWindow)都有一个 QBackingStore。它就是窗口的像素缓冲区

┌──────────────────────────────┐
│         屏幕显示              │
│  ┌────────────────────────┐  │
│  │  QBackingStore(后缓冲)│  │
│  │  ┌──────┬──────┬──────┐ │  │
│  │  │Widget│Widget│Widget│ │  │
│  │  │  1   │  2   │  3   │ │  │
│  │  └──────┴──────┴──────┘ │  │
│  │  共享同一个 QImage 缓冲  │  │
│  └────────────────────────┘  │
└──────────────────────────────┘

关键点:同一个顶层窗口内的所有 QWidget 共享同一个 QBackingStore。每个 widget 的 paintEvent 最终写入的是这个共享 QImage 的对应区域。

3.2 完整绘制周期

sequenceDiagram
    participant QPA as Qt Platform
    participant TBW as QWidgetWindow
    participant BS as QBackingStore
    participant W1 as QWidget A
    participant W2 as QWidget B

    QPA->>TBW: 需要重绘(expose / animation / update)

    Note over TBW: 收集所有脏区域

    TBW->>BS: beginPaint(dirtyRegion)
    BS->>BS: 分配 QImage buffer

    loop 遍历脏区域内的所有 widget
        TBW->>W1: paintEvent()
        W1->>BS: QPainter 写入 buffer
    end

    loop 遍历脏区域内的所有 widget
        TBW->>W2: paintEvent()
        W2->>BS: QPainter 写入 buffer
    end

    TBW->>BS: endPaint()
    BS->>QPA: flush(dirtyRegion)
    Note over QPA: 将脏区域复制到 GPU 纹理<br/>(或直接操作系统 bitblt)

3.3 为什么原生 Qt (Widgets) “不用 GPU”

这不是严格正确的说法。正确的说法是:Qt Widgets 的绘制是在 CPU 端完成光栅化的,最终显示时会经过 GPU 合成

Qt Widgets 路径:
  QPainter → CPU 光栅化 → QImage (RAM) → flush → GPU 纹理 → 合成器 → 显示
               ↑ 都在系统内存中    ↑ 这一步才进 GPU

原生 OpenGL 路径:
  glDrawArrays → GPU 顶点着色 → 光栅化 → 帧缓冲 → 显示
         ↑ 从一开始就在 GPU 上

这意味着:

  • QWidget 的绘制计算在 CPU 上(文字光栅化、路径填充、alpha 合成)
  • 最终的显示合成会用到 GPU(窗口合成、vsync)
  • 瓶颈在 CPU 端,尤其是大量控件的复杂样式

第四层:QPainter 与原生 OpenGL 的本质区别

4.1 编程模型

维度 QPainter 原生 OpenGL
范式 即时模式(Immediate Mode) 保留模式 + 命令缓冲(现代 OpenGL)
状态管理 栈式 save/restore 全局状态机(需手动管理)
坐标系统 逻辑坐标 + 自动变换矩阵 手动管理模型/视图/投影矩阵
绘制单元 几何形状(线、矩形、路径) 顶点缓冲区 + 着色器
缓冲管理 自动(QBackingStore) 手动(VBO、VAO、FBO)
抗锯齿 自动(子像素覆盖) 手动(MSAA 或 shader 实现)

4.2 同一效果,两条路径

QPainter 方式:

void MyWidget::paintEvent(QPaintEvent *) {
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    
    // 画渐变圆角矩形
    QLinearGradient grad(0, 0, 0, height());
    grad.setColorAt(0, QColor("#667eea"));
    grad.setColorAt(1, QColor("#764ba2"));
    
    p.setBrush(grad);
    p.setPen(Qt::NoPen);
    p.drawRoundedRect(rect().adjusted(10,10,-10,-10), 16, 16);
    
    // 只需要 3 行代码
}

原生 OpenGL 方式(等效效果):

// 1. 顶点着色器
const char *vertexShader = R"(
    #version 330 core
    layout(location = 0) in vec2 position;
    uniform mat4 mvp;
    void main() {
        gl_Position = mvp * vec4(position, 0.0, 1.0);
    }
)";

// 2. 片段着色器(圆角矩形 SDF)
const char *fragmentShader = R"(
    #version 330 core
    uniform vec2 resolution;
    uniform vec2 rectSize;
    uniform float radius;
    uniform vec4 colorTop;
    uniform vec4 colorBottom;
    out vec4 fragColor;
    
    float roundedBoxSDF(vec2 p, vec2 b, float r) {
        vec2 d = abs(p) - b + vec2(r);
        return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
    }
    
    void main() {
        vec2 p = (gl_FragCoord.xy - resolution * 0.5) / resolution.y;
        vec2 b = rectSize / resolution.y;
        float d = roundedBoxSDF(p, b, radius / resolution.y);
        float alpha = 1.0 - smoothstep(-1.0, 1.0, d * resolution.y);
        vec4 color = mix(colorBottom, colorTop, gl_FragCoord.y / resolution.y);
        fragColor = color * alpha;
    }
)";

// 3. 编译着色器、创建 program、设置 uniforms、创建 VAO/VBO...
// 4. glDrawArrays(GL_TRIANGLES, 0, 6);

同样的视觉效果,QPainter 用 3 行,OpenGL 需要 40+ 行着色器和一堆管线设置。

4.3 性能对比

graph LR
    subgraph "简单场景(100 个矩形 + 文字)"
        A1["QPainter"] -->|"~0.5ms<br/>全部 CPU"| R1["适合"]
        A2["OpenGL"] -->|"~0.2ms<br/>但开销更大"| R2["过度工程"]
    end

    subgraph "复杂场景(10000 个圆角矩形动画)"
        B1["QPainter"] -->|"~30ms<br/>CPU 计算瓶颈"| R3["卡顿"]
        B2["OpenGL"] -->|"~2ms<br/>GPU 并行"| R4["流畅"]
    end

    subgraph "极高复杂度(粒子系统)"
        C1["QPainter"] -->|"不可行"| R5["放弃"]
        C2["OpenGL"] -->|"~1ms"| R6["唯一选择"]
    end

    style R1 fill:#27AE60,color:#fff
    style R2 fill:#E67E22,color:#fff
    style R3 fill:#C0392B,color:#fff
    style R4 fill:#27AE60,color:#fff
    style R5 fill:#C0392B,color:#fff
    style R6 fill:#27AE60,color:#fff

量化数据(实测近似值,取决于硬件)

场景 QPainter (Raster) OpenGL (native)
单个按钮重绘 < 0.1ms ~0.05ms
复杂表单(50 控件) 1-3ms 0.5-1ms
1000 个动画条 15-25ms → 卡 1-3ms
大量文字渲染 ⭐ 优秀(FreeType/harfbuzz) 一般(需纹理图集)
渐变/阴影 中等 ⭐ 极快(GPU 并行)

QPainter 的优势项:文字渲染。Qt 使用 FreeType 和 HarfBuzz 做 CPU 端文字光栅化,Subpixel 抗锯齿效果极好。GPU 做文字渲染需要纹理图集,而且 SubmitPixel 效果不如 CPU。

第五层:QML / Qt Quick 的场景图(Scene Graph)

5.1 架构概览

QML 的渲染系统与 QWidget 完全不同。它不经过 QPainter、不经过 QStyle、不经过 QBackingStore。它有自己的渲染管线:

graph TD
    subgraph "QML 声明层"
        QML["QML 代码<br/>Rectangle { color: 'red' }"]
    end

    subgraph "C++ 对象层"
        QML --> ITEM["QQuickItem<br/>(C++ 对象)"]
        ITEM --> GEOM["几何属性<br/>x, y, width, height, color..."]
    end

    subgraph "场景图层"
        ITEM --> SG["QQuickWindow<br/>触发场景图同步"]
        SG --> SYNC["QSGNode 树同步<br/>(主线程)"]
        SYNC --> RN["QSGRenderNode<br/>(渲染线程)"]
    end

    subgraph "GPU 层"
        RN --> GAPI["RHI 抽象层<br/>(Qt 6)"]
        GAPI --> GL["OpenGL"]
        GAPI --> VK["Vulkan"]
        GAPI --> MT["Metal"]
        GAPI --> D3D["Direct3D"]
    end

    style QML fill:#3498DB,color:#fff
    style ITEM fill:#8E44AD,color:#fff
    style SG fill:#E67E22,color:#fff
    style GAPI fill:#27AE60,color:#fff

关键差异:QML 的场景图运行在独立的渲染线程上。主线程只负责同步属性到场景图节点,实际的 OpenGL/Vulkan/Metal 调用在渲染线程中执行。这就是 QML 的动画能够跑到 60fps+ 的原因——主线程阻塞不会影响渲染。

5.2 场景图同步机制

// QQuickItem::updatePaintNode() —— QML 控件的核心虚函数
QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode,
                                       UpdatePaintNodeData *) {
    QSGSimpleRectNode *node = static_cast<QSGSimpleRectNode*>(oldNode);
    if (!node) {
        node = new QSGSimpleRectNode();
    }
    
    // 把这些属性同步到 GPU 节点
    node->setRect(boundingRect());
    node->setColor(m_color);
    
    return node;
}

updatePaintNode() 在主线程调用。它不渲染任何东西,只是创建/更新场景图节点对象。实际的 GPU 渲染调用在另一个线程中发生。

sequenceDiagram
    participant MT as 主线程
    participant SG as 场景图
    participant RT as 渲染线程
    participant GPU as GPU

    Note over MT: 帧 N
    MT->>MT: 属性变更 → 触发 update()
    MT->>SG: updatePaintNode()<br/>更新 QSGNode 属性
    Note over MT: 主线程继续处理事件

    Note over RT: 帧 N 渲染
    RT->>SG: 遍历 QSGNode 树
    SG->>RT: 生成绘制命令
    RT->>GPU: glDraw* / vkCmdDraw*
    GPU->>GPU: 执行着色器

    Note over MT: 帧 N+1(同时)
    MT->>MT: 处理新的事件...
    Note over RT: 帧 N+1(同时)
    RT->>GPU: 渲染下一帧

5.3 QML 的控件属性体系 vs QStyle 的绘制体系

这是最核心的对比:

维度 QML / Qt Quick QWidgets + QStyle
控件定义方式 声明式属性组合(Rectangle { color; radius } C++ 虚函数重写(drawControl()
外观定义者 QML 文件自身 / QtQuick.Controls 的样式文件 QStyle 类的 drawControl() 等虚函数
主题机制 qtquickcontrols2.conf + QML 样式委托 C++ 的 QStyle 子类
属性系统 QML 内置属性绑定(width: parent.width * 0.5 Q_PROPERTY + 手动 notify
渲染机制 场景图 → GPU QPainter → CPU 光栅化 → QBackingStore
动画 场景图属性动画(GPU 线程) QPropertyAnimation(主线程,触发 repaint)
布局 QML Anchor / Layout / Positioner QLayout 系统
文字渲染 纹理图集(distance field 或 bitmap) FreeType CPU 光栅化

5.4 QML 的控件实现机制

以 QML 的 Button 为例——它和 QPushButton 的结构完全不同:

QML Button 的分层结构:

Button {
    id: control
    
    // 背景委托(可替换)
    background: Rectangle {
        color: control.down ? "#357ABD" : 
               control.hovered ? "#4A90D9" : 
               "#5BA0E9"
        radius: 4
    }
    
    // 内容项
    contentItem: Text {
        text: control.text
        color: "white"
        font: control.font
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}

每一层都可以被替换QtQuick.Controls 的样式系统支持:

  • 全局样式文件覆盖
  • 单个控件的 inline 委托
  • 与父控件继承
// 全局样式文件(qtquickcontrols2.conf)
[Controls]
Style=Material

[Material]
Theme=Dark
Accent=DeepPurple

// 单个控件覆盖
Button {
    text: "Custom"
    background: Rectangle {
        gradient: Gradient { ... }
    }
}

对比 QStyle 的 Button 定制

// QStyle 方式:必须重写 drawControl(CE_PushButton)
void MyStyle::drawControl(ControlElement elem, ...) {
    if (elem == CE_PushButton) {
        // 50 行 QPainter 代码
        drawPrimitive(PE_PanelButtonCommand, ...);
        // 处理图标
        // 处理文字
        // 处理焦点
    }
}

QML 的声明式委托替换比 QStyle 的 C++ 虚函数重写灵活得多——但代价是运行时开销更大。

5.5 RHI (Rendering Hardware Interface) — Qt 6 的统一抽象

Qt 6 引入 RHI 取代了 Qt 5 的直接 OpenGL 依赖:

// Qt 5(直接 OpenGL)
QSGNode *node;
// 场景图直接发送 OpenGL 调用

// Qt 6(通过 RHI)
QRhi *rhi = window->rhi();  // 可能是 OpenGL / Vulkan / Metal / D3D
// 场景图通过 RHI 发送抽象绘制命令
// RHI 将其翻译为具体图形 API 调用

RHI 支持的后端:

后端 平台 状态
OpenGL ES 2.0 / 3.0 所有平台 主要 / 回退
Vulkan 1.0+ Windows, Linux, Android 实验性
Metal macOS, iOS 良好支持
Direct3D 11 Windows 良好支持
Direct3D 12 Windows Qt 6.6+

第六层:性能深度对比

6.1 渲染性能

graph LR
    subgraph "QWidgets + QStyle"
        W1[paintEvent] --> W2["CPU 光栅化<br/>QPainter → QImage"] --> W3["flush → GPU 纹理"] --> W4[显示]
    end

    subgraph "QML / Qt Quick"
        Q1["属性变更"] --> Q2["场景图同步<br/>(主线程)"] --> Q3["GPU 渲染<br/>(渲染线程)"] --> Q4[显示]
    end

    subgraph "原生 OpenGL"
        G1["更新 uniforms"] --> G2["glDraw*"] --> G3[显示]
    end

6.2 适合场景对照表

场景 QWidgets + QStyle QML 原生 OpenGL
传统桌面应用(表单、表格、对话框) ⭐⭐⭐ ⭐⭐ ❌(不实用)
移动端 / 嵌入式触摸 UI ⭐⭐⭐ ❌(不实用)
复杂数据可视化(图表、仪表盘) ⭐⭐ ⭐⭐⭐ ⭐⭐(灵活但工作量大)
3D 渲染 / 游戏 ⭐⭐⭐
高密度文字(代码编辑器、文档阅读器) ⭐⭐⭐
动画密集型 UI ⭐⭐⭐ ⭐⭐
跨平台一致性要求高 ⭐⭐⭐ ⭐⭐
低端硬件 / 嵌入式 ⭐⭐ ⭐(RHI 有开销) ⭐⭐⭐(但开发成本高)
自定义控件的灵活度 中等(C++ 虚函数) 高(QML 委托替换) 极高(但工作量大)

6.3 内存占用对比

QWidgets + QStyle:
  - 每个 widget 一个 C++ 对象(~200B+ 元数据)
  - 一个共享 QBackingStore(窗口大小的 QImage)
  - 无额外 GPU 资源(除 flush 后的纹理)

QML:
  - 每个 Item 一个 C++ 对象(~300B+) + QML 元对象
  - 场景图节点(QSGNode)每项 ~200B+
  - GPU 纹理、顶点缓冲、uniform 缓冲
  - 图标和文字缓存在 GPU 纹理图集中

原生 OpenGL:
  - 只有你分配的资源
  - 0 额外框架开销

粗略对比(1000 个控件)

指标 QWidgets QML
C++ 对象内存 ~200KB ~300KB
额外 GPU 内存 ~5MB(窗口缓存) ~15-30MB(纹理图集+节点)
创建时间 ~10ms ~50-100ms(含 QML 编译)
60fps 动画可行性 部分可行(简单控件) ✅ 设计目标

第七层:QML 属性绑定 vs QStyle 查询——两种"动态化"思路

QStyle 的方式:查询模式

// QStyle 的"动态":每次绘制时查询状态
void MyStyle::drawControl(CE_PushButton, ...) {
    // 从 QStyleOption 读取当前状态
    bool hovered = opt->state & State_MouseOver;
    bool pressed = opt->state & State_Sunken;
    
    // 根据状态计算颜色
    QColor bg = pressed ? m_pressedColor : 
                hovered ? m_hoverColor : m_normalColor;
    
    p->fillRect(opt->rect, bg);
}

// 状态变化 → widget->update() → paintEvent → drawControl 重新查询

QML 的方式:属性绑定

Rectangle {
    // 声明式绑定:color 自动跟随 control.down 和 control.hovered
    color: control.down ? "#357ABD" : 
           control.hovered ? "#4A90D9" : 
           "#5BA0E9"
    
    // 绑定自动工作——任何依赖变化都会触发重新求值
    // 不需要手写 update() 或 paintEvent
}

本质区别

QStyle QML
范式 命令式查询(“此时状态是什么?") 声明式绑定(“我的值永远等于这个表达式”)
更新机制 事件驱动(paintEvent → 查询) 依赖追踪(属性变化 → 重新求值 → 场景图更新)
线程 主线程 主线程绑定 + 渲染线程绘制
过度更新 可能(paintEvent 重绘整个控件) 精确(只更新变化的节点属性)

第八层:混合使用——何时在 QWidget 中用 QML,反之亦然

QQuickWidget:在 QWidget 中嵌入 QML

#include <QQuickWidget>
#include <QQmlContext>

// 在 QWidget 应用中嵌入 QML 场景
QQuickWidget *quickWidget = new QQuickWidget;
quickWidget->setSource(QUrl("qrc:/MyChart.qml"));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);

// 从 C++ 向 QML 暴露数据
quickWidget->rootContext()->setContextProperty("chartData", &m_dataModel);

QQuickWidget 的限制

  • 渲染通过 FBO(帧缓冲对象)中转,额外一次纹理拷贝
  • 不能与普通 QWidget 在半透明方式下完美叠加
  • 性能不如纯 QML 窗口

QWidget::createWindowContainer():在 QML 中嵌入 QWidget

import QtQuick.Controls 2.15

ApplicationWindow {
    // 在 QML 中嵌入 Qt 3D 或 OpenGL widget
    // (主要用于嵌入不能移植到 QML 的遗留控件)
}

一般不推荐这种方向——从 QML 嵌 QWidget 比反过来更困难和低效。

总结:技术选型决策树

graph TD
    Q{"你的应用是?"}
    Q -->|"传统桌面应用<br/>(表单/表格/编辑器)"| A1["QWidgets + QStyle"]
    Q -->|"移动端/嵌入式<br/>触摸优先"| A2["QML"]
    Q -->|"需要大量动画<br/>视觉效果丰富"| A3["QML"]
    Q -->|"高性能 3D/游戏"| A4["原生 OpenGL/Vulkan"]
    Q -->|"混合:桌面主体<br/>+ 部分动画面板"| A5["QWidgets 主体<br/>+ QQuickWidget 嵌动画"]
    Q -->|"重用大量现有<br/>QWidget 代码"| A1

    style A1 fill:#27AE60,color:#fff
    style A2 fill:#3498DB,color:#fff
    style A3 fill:#3498DB,color:#fff
    style A4 fill:#C0392B,color:#fff
    style A5 fill:#E67E22,color:#fff
如果看重… 选这个
像素级精度 + 文字渲染质量 QWidgets + QStyle
动画流畅度 + 现代视觉效果 QML
跨平台 widget 外观一致性 QWidgets + Fusion QStyle
开发速度(UI 部分) QML
C++ 集成深度 QWidgets(天然纯 C++)
低端硬件支持 QWidgets
未来 Qt 的方向 QML(Qt 官方重点投入)

最终忠告:如果你在做Logic’s Lab 这样的组件库和课程,QWidgets + QStyle 仍然是最佳选择。它的架构透明性、可控性和 C++ 的集成深度,是学习和展示底层原理的理想环境。QML 适合做产品,QStyle 适合学原理。


这篇文章是 Logic’s Lab「Qt 深度定制 UI 框架」系列的第四篇。下一篇将回到实战——从零构建一个完整的自定义 QStyle。


参考资料