编辑
2025-03-18
工具
00

目录

Qt的信号和槽、QML、QSS
Qt的信号和槽机制是什么?
1. 语法
2. Qt信号和槽的底层实现
(1)MOC 预处理
(2)信号本质
(3)槽的调用
3. 信号和槽的连接方式
4. 什么时候用信号和槽?
5. 何时不用信号和槽?
6. 总结
QML的作用
QML 是什么?
QML 依赖什么解析?
总结
QSS的作用
1. QSS 是什么?
2. QSS 解析原理
3. QSS 解析的局限性
4. 适用场景
5. 总结
Qt vs Vue:从底层机制看信号槽、组件通信与 UI 渲染
1. 信号槽 vs 事件通信:底层实现分析
1.1 Qt 信号和槽的底层实现
示例
底层机制
优缺点
1.2 Vue 事件通信的底层实现
事件触发流程
示例
底层实现
优缺点
2. UI 渲染架构对比
2.1 Qt Scene Graph 渲染
代码示例
底层机制
优缺点
2.2 Vue Virtual DOM 渲染
示例
底层机制
优缺点
3. 总结

在Web开发中,Vue 通过 $emit()$on()实现组件间通信,而 Qt 通过 信号和槽(Signal & Slot) 机制实现解耦的对象通信。与此同时,QML + QSS 和 HTML + CSS 也存在类似的结构,分别用于 UI 构建和样式管理。本文将探讨 Qt 和 Vue 在 事件通信 和 UI 设计 方面的异同点。

在探讨Qt与Vue的异曲同工之处,先回顾一下Qt的信号和槽、QML、QSS。

Qt的信号和槽、QML、QSS

Qt的信号和槽机制是什么?

Qt的信号(Signal)和槽(Slot)是一种 观察者模式 的实现,主要用于对象之间的 解耦通信。当某个对象的状态发生变化时,可以发射(emit)信号,所有连接到该信号的槽函数都会自动调用,类似于事件回调机制。

1. 语法

Qt 使用 QObject 提供信号和槽机制:

cpp
class MyClass : public QObject { Q_OBJECT public: MyClass(QObject* parent = nullptr) : QObject(parent) {} signals: void mySignal(int value); // 信号 public slots: void mySlot(int value) { // 槽 qDebug() << "Received value:" << value; } };

连接信号和槽:

cpp
MyClass obj1, obj2; QObject::connect(&obj1, &MyClass::mySignal, &obj2, &MyClass::mySlot); obj1.mySignal(42); // 发射信号,obj2 的 mySlot() 被调用

2. Qt信号和槽的底层实现

Qt 的信号槽依赖于 元对象系统(Meta-Object System, MOC),其核心机制包括:

(1)MOC 预处理

Qt 不会使用 C++ 直接提供的函数指针或标准回调,而是使用 MOC 预处理 Q_OBJECT 宏,生成 信号槽映射表。编译时,MOC 工具会解析 .h 文件,生成一个 .moc 代码文件,其中包含:

  • 信号槽的 注册 信息
  • 事件派发逻辑
  • 反射调用函数(基于 QMetaObject

(2)信号本质

信号是 类的成员函数,但不需要实现体。MOC 生成的 .moc 文件中,会为每个信号创建 QMetaObject::activate() 调用,用于触发信号。

cpp
void MyClass::mySignal(int value) { void *args[] = { &value }; QMetaObject::activate(this, &MyClass::staticMetaObject, 0, args); }

(3)槽的调用

MOC 解析 connect() 语句,并在 QMetaObject 内部维护 信号槽连接表,当信号触发时:

  • 通过 QMetaObject::activate() 解析连接表
  • 直接调用 目标对象的槽函数(如果是 Direct 连接)
  • 投递到事件队列(如果是 Queued 连接)

3. 信号和槽的连接方式

Qt 支持四种连接方式:

连接方式说明
Qt::AutoConnection默认方式,自动选择 Direct 或 Queued
Qt::DirectConnection直接调用槽(类似普通函数调用)
Qt::QueuedConnection通过事件队列异步调用槽
Qt::BlockingQueuedConnection同步调用槽,阻塞当前线程

示例:

cpp
QObject::connect(&obj1, &MyClass::mySignal, &obj2, &MyClass::mySlot, Qt::QueuedConnection);

4. 什么时候用信号和槽?

信号和槽适用于以下情况:

  1. 对象之间的通信
    • 例如 GUI 组件之间的交互(按钮点击、窗口更新等)
  2. 解耦模块间的依赖
    • 不需要知道对方的具体类型,只需通过信号通知
  3. 跨线程通信
    • 使用 Qt::QueuedConnection 进行线程间消息传递
  4. 动态事件响应
    • 例如插件系统,动态添加槽函数进行事件处理

5. 何时不用信号和槽?

  • 性能关键代码
    信号和槽有一定 运行时开销(如 QMetaObject 查表、事件队列处理),如果需要极致性能,可以考虑直接使用函数调用或回调函数。

  • 简单的函数调用
    如果是 同一类内部的调用,或者两个对象绑定紧密,可以直接调用成员函数,而不必使用信号和槽。


6. 总结

  • 信号和槽用于解耦通信,提供了类似 观察者模式 的机制。
  • 基于 MOC 预处理,通过 QMetaObject::activate() 进行信号分发。
  • 支持跨线程通信,可使用 Qt::QueuedConnection 在不同线程间投递消息。
  • 适用于 GUI 事件、模块解耦、动态交互,但在性能敏感场景需谨慎使用。

QML的作用

QML 是什么?

QML(Qt Modeling Language)是一种 声明式 UI 语言,用于 构建 Qt Quick 界面。它的语法类似 JSON,支持 动画、数据绑定、JavaScript 逻辑,并能与 C++ 交互。

QML 示例

qml
Rectangle { width: 200; height: 100 color: "red" }

数据绑定

qml
Rectangle { width: parent.width / 2 }

动画

qml
Rectangle { width: 100; height: 100 color: "blue" NumberAnimation on width { from: 100; to: 300 duration: 1000 } }

C++ 交互(绑定对象):

cpp
QQmlApplicationEngine engine; engine.load(QUrl("qrc:/main.qml"));

QML 依赖什么解析?

QML 依赖 Qt QML 模块(QQmlEngine) 进行解析,包括:

1. 解析阶段QQmlEngine

  • QML 代码 → AST(抽象语法树)
  • 解析 .qml 文件,转换为对象结构

2. 对象创建阶段QQmlComponent

  • 解析后的 AST 转换为 QObject 对象树
  • 组件实例化,进行属性绑定

3. 绑定 & 渲染阶段

  • QML 绑定引擎 计算属性(width: parent.width / 2
  • Qt Quick(QQuick) 负责 UI 渲染(Scene Graph)

QML 解析核心 API

cpp
QQmlApplicationEngine engine; engine.load(QUrl("qrc:/main.qml"));

手动解析 QML 文件

cpp
QQmlEngine engine; QQmlComponent component(&engine, QUrl("qrc:/main.qml")); QObject *object = component.create();

总结

  • QML 是 Qt Quick 的声明式 UI 语言,用于构建界面。
  • QML 依赖 QQmlEngine 解析,转换成 QObject 对象,再由 Qt Quick 渲染
  • 解析过程:QML 代码 → AST → 对象创建 → 绑定 → Qt Quick 渲染。
  • 支持 JavaScript 逻辑 & C++ 交互,更适合现代 UI。

QSS的作用

1. QSS 是什么?

QSS(Qt Style Sheets)是 Qt 提供的 样式表机制,用于 美化 QWidget 界面,但 不适用于 QML

作用

  • 统一 Qt 窗口、控件(QWidget、QPushButton 等) 的样式

  • 支持 选择器、伪类、属性设置

  • 动态修改样式,支持 代码或 QSS 文件加载

    QSS 示例

css
QPushButton { background-color: blue; color: white; border-radius: 5px; } QPushButton:hover { background-color: lightblue; }

QSS 加载方式(C++ 代码):

cpp
QFile file("style.qss"); file.open(QFile::ReadOnly); QString style = file.readAll(); qApp->setStyleSheet(style);

2. QSS 解析原理

Qt 解析 QSS 的过程包括:

  1. QSS 语法解析(Lexical Parsing)
    • Qt 内部 解析 QSS 语法,构建 CSS 规则树(类似 AST)。
    • 解析 选择器、属性、值(如 QPushButton { color: red; })。
  2. 匹配控件(Widget Matching)
    • Qt 遍历 所有 QWidget,匹配 QSS 选择器
    • 例如:
      • QPushButton {} → 作用于所有 QPushButton
      • QPushButton#myButton {} → 仅作用于 objectName="myButton" 的按钮
      • QPushButton:hover {} → 仅在鼠标悬停时生效
  3. 应用样式(Style Application)
    • Qt 动态修改控件的属性(颜色、字体、边框),并 重绘 UI
    • 但 QSS 不能修改 QWidget 布局,仅适用于样式调整。

QSS 解析核心 API

cpp
qApp->setStyleSheet(qssContent);

3. QSS 解析的局限性

仅支持 QWidget,不支持 QML(QML 直接用 color: "red") ❌ 无法修改窗口布局(只影响控件外观) ❌ 性能较低(大量 QSS 可能影响 UI 响应速度)


4. 适用场景

传统桌面应用(QWidget) → 需要统一样式 ✅ 不涉及动画的静态 UI → QSS 简单易用 ❌ Qt Quick(QML)项目 → 直接用 colorborder 定义样式


5. 总结

  • QSS 是 QWidget 的样式表,但不支持 QML
  • Qt 解析 QSS 语法,匹配控件,再应用样式
  • 适用于 QWidget 界面美化,但性能一般,复杂 UI 用 QML更佳

Qt vs Vue:从底层机制看信号槽、组件通信与 UI 渲染

1. 信号槽 vs 事件通信:底层实现分析

Qt 的 信号和槽(Signal & Slot) 机制和 Vue 的 事件通信(Event Bus) 机制虽然都基于 观察者模式(Observer Pattern),但它们的底层实现方式完全不同。

1.1 Qt 信号和槽的底层实现

Qt 使用 元对象系统(Meta-Object System, MOC) 处理信号和槽的映射关系。主要流程如下:

  1. MOC 编译阶段

    • Qt 在编译时,MOC 工具会解析 Q_OBJECT 宏,生成 .moc 文件。
    • 这个文件包含了 信号槽映射表(QMetaObject)事件分发函数
  2. 注册信号槽

    • QObject::connect(sender, SIGNAL(sig()), receiver, SLOT(slot())) 会将信号-槽关系存入 QMetaObject
    • Qt 运行时维护一个 哈希表,存储对象与信号槽的映射。
  3. 发射信号

    • emit signal() 触发后,Qt 通过 QMetaObject::activate() 查找所有连接的槽,并调用它们。

示例

cpp
class Sender : public QObject { Q_OBJECT signals: void mySignal(int value); }; class Receiver : public QObject { Q_OBJECT public slots: void mySlot(int value) { qDebug() << "Received:" << value; } }; Sender sender; Receiver receiver; QObject::connect(&sender, &Sender::mySignal, &receiver, &Receiver::mySlot); sender.mySignal(42); // 触发信号,槽函数被调用

底层机制

cpp
void QMetaObject::activate(QObject *sender, int signal_index, void **argv) { // 遍历所有已注册的槽函数,并逐一调用 for (Slot *slot : signalToSlots[sender][signal_index]) { slot->invoke(argv); } }
优缺点

完全解耦,对象之间无需互相认识
支持跨线程通信Qt::QueuedConnection
运行时反射机制有开销QMetaObject::invoke() 使用动态查找)
必须使用 Q_OBJECT,不支持模板类

1.2 Vue 事件通信的底层实现

Vue 的事件机制本质上是 发布-订阅模式(Pub-Sub),由一个全局 Event Bus 维护所有事件的监听者。

事件触发流程

  1. this.$emit(eventName, data) 触发事件,Vue 会在 this._events[eventName] 查找所有注册的回调函数。
  2. 遍历所有监听者 this._events[eventName],依次执行回调。

示例

vue
// 子组件 <template> <button @click="sendData">发送数据</button> </template> <script> export default { methods: { sendData() { this.$emit("dataChanged", 42); } } } </script>
vue
// 父组件 <template> <ChildComponent @dataChanged="onDataChanged"/> </template> <script> export default { methods: { onDataChanged(value) { console.log("收到子组件的数据:", value); } } } </script>

底层实现

js
Vue.prototype.$emit = function (event, ...args) { const listeners = this._events[event] || []; listeners.forEach(listener => listener.apply(this, args)); };
优缺点

轻量级,运行时开销小(基于 JS 的对象映射)
支持动态注册事件(无需预定义)
父子组件紧耦合(必须通过 $parent$children 获取组件实例)
不支持跨线程通信

2. UI 渲染架构对比

Qt 和 Vue 的 UI 渲染方式完全不同,Qt 采用 Scene Graph + OpenGL,Vue 依赖 DOM + Virtual DOM

2.1 Qt Scene Graph 渲染

Qt 采用 场景图(Scene Graph) 进行 GPU 加速渲染,渲染过程如下:

  1. 构建 QML 组件树,每个 Item 对应一个 QSGNode
  2. 更新场景图,在 update() 触发时,将 QSGNode 传递到 渲染线程
  3. OpenGL 绘制QSGRenderer 将节点转换为 OpenGL 指令,最终渲染到屏幕。

代码示例

qml
Rectangle { width: 200; height: 100 color: "lightgray" Button { text: "Click Me" anchors.centerIn: parent font.pixelSize: 16 } }

底层机制

cpp
void QQuickWindow::renderSceneGraph() { for (QSGNode *node : sceneGraphNodes) { node->renderGL(); } }
优缺点

GPU 加速,适合高性能 UI
跨平台,适用于桌面和嵌入式
C++ 代码复杂度高

2.2 Vue Virtual DOM 渲染

Vue 采用 Virtual DOM 进行高效的 UI 更新,渲染流程如下:

  1. 解析 template 生成 VNode 虚拟节点树
  2. VNode Diff 计算最小更新路径
  3. 通过 document.createElement() 更新真实 DOM

示例

vue
<template> <div class="box">Hello Vue</div> </template>

底层机制

js
function renderVNode(vnode) { if (vnode.type === "text") { return document.createTextNode(vnode.value); } const el = document.createElement(vnode.tag); vnode.children.forEach(child => el.appendChild(renderVNode(child))); return el; }
优缺点

轻量级,适合 Web 页面
支持服务端渲染(SSR)
依赖 DOM,性能不如 Qt Scene Graph

3. 总结

  1. Qt 信号槽 vs Vue 事件

    • Qt 采用 QMetaObject 维护信号-槽映射,适合跨线程通信,但运行时开销大。
    • Vue 事件基于 Event Bus,轻量级但仅适用于前端组件通信。
  2. Qt Scene Graph vs Vue Virtual DOM

    • Qt 采用 GPU 加速,适用于高性能 UI。
    • Vue 依赖 Virtual DOM,适合 Web 应用。
  3. 如何选择?

    • 桌面应用 / 嵌入式Qt(更高效)

    • Web 前端 / H5Vue(更轻量)

本文作者:phae

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!