3. Qt框架入门

在嵌入式Linux项目开发中,Qt 是构建图形用户界面与上层应用逻辑的主流框架之一。它既能运行在桌面环境,也能通过 LinuxFB、EGLFS、Wayland 等后端运行在嵌入式设备上。本章从 Qt 的发展脉络、模块组成、信号与槽机制、元对象系统出发,最终完成第一个 Qt 程序,为后续交叉编译与板端部署打下基础。

一、Qt 的发展历史与版本选择

Qt 由挪威 Trolltech 公司于 1991 年前后开始开发,最初用于跨平台 C++ GUI 开发。此后经历多次归属变化:Trolltech 被 Nokia 收购,后又转至 Digia,最终由 Qt Company 负责商业与开源版本的维护。Qt 的版本演进大致可分为以下几个阶段:

  • Qt 4 时代:引入更成熟的图形视图框架与信号槽语法,长期用于嵌入式 Linux 项目。
  • Qt 5 时代:模块化程度更高,QML 与 Qt Quick 成熟,支持 OpenGL ES、EGLFS 等嵌入式后端,是当前嵌入式项目中使用最广的版本之一。
  • Qt 6 时代:进一步统一图形栈,强化 QML 与 C++ 的协作,但对编译器和硬件资源要求更高。

在嵌入式 Linux 项目中,选择版本时要综合考虑交叉编译工具链、芯片 GPU 支持、内存占用以及第三方库依赖。很多量产项目仍以 Qt 5.12、Qt 5.15 为主,因为其资料丰富、驱动适配稳定。

二、Qt 的模块组成

Qt 并不是一个单一库,而是由多个模块组成。理解模块划分,有助于在工程中按需裁剪,减少嵌入式设备的存储与内存占用。

模块类别 代表模块 作用
核心基础 QtCore 对象模型、事件循环、容器、线程、文件与时间等基础能力
图形界面 QtGui、QtWidgets 窗口、控件、绘图、字体、图像与事件处理
声明式 UI QtQml、QtQuick QML 引擎与面向触摸屏的流畅界面开发
网络与通信 QtNetwork TCP/UDP、HTTP、SSL 等网络通信能力
多媒体与图形 QtMultimedia、QtOpenGL 音视频播放、OpenGL 渲染与硬件加速
嵌入式平台 QtPlatformSupport、EGLFS 板端显示、输入设备与平台插件适配

在嵌入式项目中,通常不会全量编译 Qt,而是通过 configure 选项裁剪不需要的模块,例如关闭 WebEngine、关闭示例与测试,只保留 QtCore、QtGui、QtWidgets 或 QtQuick 等必要部分。

三、信号与槽机制

信号与槽是 Qt 最具代表性的对象间通信机制。当某个对象状态发生变化时,它会发出信号;与之连接的槽函数会被自动调用。这种机制解耦了事件发送者与接收者,使界面逻辑更清晰。

信号与槽的核心特点包括:

  • 一个信号可以连接多个槽,一个槽也可以接收多个信号。
  • 连接可以是直连、队列连接或自动连接,跨线程时通常使用队列连接。
  • 连接可以在运行时建立或断开,使用 QObject::connect 与 disconnect。
  • 信号只需声明,不需要实现;槽函数需要实现。

下面是一个典型的按钮点击连接示例:

QPushButton *btn = new QPushButton("开始采集");
connect(btn, &QPushButton::clicked,
        this, &MainWindow::onStartCollect);

在 Qt 5 之后,推荐使用函数指针语法,因为它能在编译期检查参数类型,减少运行时错误。旧式 SIGNAL/SLOT 宏语法虽然仍可用,但类型错误只能在运行时发现。

在嵌入式触摸屏项目中,按钮点击、串口数据到达、定时器超时等都可以通过信号与槽统一处理,避免在 while 循环中轮询,从而降低 CPU 占用。

四、元对象系统与 moc

Qt 的元对象系统为 C++ 增加了运行时类型信息、动态属性、信号槽以及对象树管理能力。其核心是 QObject 基类、Q_OBJECT 宏和 moc(Meta-Object Compiler)。

凡是需要使用信号与槽、属性系统或动态类型转换的类,都应在类声明中加入 Q_OBJECT 宏。moc 会扫描头文件,生成额外的 C++ 源文件,其中包含信号实现、元数据表和 qt_metacall 等代码。构建系统会自动调用 moc,因此开发者通常不需要手动执行。

class Collector : public QObject
{
    Q_OBJECT
public:
    explicit Collector(QObject *parent = nullptr);

signals:
    void dataReady(const QByteArray &data);

public slots:
    void readSerial();
};

如果没有加入 Q_OBJECT,connect 可能编译失败或运行时报“no such signal/slot”。这是初学者最常见的错误之一。

五、第一个 Qt 程序

下面是一个最小可运行的 Qt Widgets 程序,它创建一个窗口并显示一行文字。该程序既可在桌面运行,也可在配置好交叉编译环境后部署到嵌入式板端。

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel label("Hello Qt on Embedded Linux");
    label.resize(320, 120);
    label.show();

    return app.exec();
}

对应的工程文件 hello.pro 可以这样写:

QT += core gui widgets
TARGET = hello
TEMPLATE = app
SOURCES += main.cpp

构建与运行命令如下:

qmake hello.pro
make
./hello

在嵌入式板端运行时,通常需要指定平台插件,例如使用 LinuxFB 或 EGLFS:

./hello -platform linuxfb
./hello -platform eglfs
如果板端提示 “This application failed to start because no Qt platform plugin could be initialized”,通常是因为平台插件未部署或环境变量未设置。可检查 plugins/platforms 目录,并设置 QT_QPA_PLATFORM_PLUGIN_PATH。

六、常见误区与复盘

初学者在 Qt 入门阶段容易遇到以下问题:

  1. 忘记在类中加入 Q_OBJECT,导致信号槽连接失败。
  2. 在 connect 中写错参数类型,旧式宏语法不会在编译期报错。
  3. 在嵌入式设备上直接运行桌面版 Qt 程序,缺少平台插件或显示后端。
  4. 没有裁剪模块,导致镜像体积过大,启动缓慢。
  5. 跨线程直接操作 UI,引发崩溃或界面卡死。

建议在项目初期就明确 Qt 版本、编译方式、平台插件和模块裁剪策略,并用一个最小程序验证交叉编译与板端运行链路。只有第一个程序在目标板上稳定运行,后续的界面开发、串口通信和数据处理才有可靠基础。

Qt 框架核心组成与第一个程序运行链路 QtCore 对象模型 / 事件循环 QtGui / Widgets 窗口 / 控件 / 绘图 QtQml / Quick 声明式触摸界面 QtNetwork TCP / UDP / HTTP 元对象系统:QObject + Q_OBJECT + moc 运行时类型信息 · 动态属性 · 信号槽 · 对象树 信号 signal 状态变化时发出 connect 连接 编译期类型检查 槽 slot 响应函数被调用 第一个程序:QApplication → QLabel → show() → app.exec() 桌面运行 / 交叉编译 / 板端 -platform linuxfb 或 eglfs