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 宏语法虽然仍可用,但类型错误只能在运行时发现。
四、元对象系统与 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
六、常见误区与复盘
初学者在 Qt 入门阶段容易遇到以下问题:
- 忘记在类中加入 Q_OBJECT,导致信号槽连接失败。
- 在 connect 中写错参数类型,旧式宏语法不会在编译期报错。
- 在嵌入式设备上直接运行桌面版 Qt 程序,缺少平台插件或显示后端。
- 没有裁剪模块,导致镜像体积过大,启动缓慢。
- 跨线程直接操作 UI,引发崩溃或界面卡死。
建议在项目初期就明确 Qt 版本、编译方式、平台插件和模块裁剪策略,并用一个最小程序验证交叉编译与板端运行链路。只有第一个程序在目标板上稳定运行,后续的界面开发、串口通信和数据处理才有可靠基础。