Qt开发基础

开发概览:Qt类图专业技术宝典

可参考我的开源代码配合博客记录的内容查看:Qt Dev Essential

Tips1:很多设置项,都可以在Qt Creator中搜索文档看到具体说明,不论是不是初学都该在实际开发时翻阅文档确定内容。
Tips2:所有 UI 操作必须在主线程,Qt 严禁在子线程操作窗口。

Qt,肤浅地理解为一个做GUI的玩意儿,用C++进行开发任务,实际开发感觉不如叫Q++,C++常用的东西基本被它包了个遍。以往只有本地零散的记录,在此打算系统记录一下一些概念和设计,整理出来到博客上,有些东西搞忘记了开发的时候可能会一个头两个大,实用的内容该记还是得记。

PS:有部分内容是与AI问答,自己加以修改补充的,多是使用场景一类。

元对象系统(Meta-Object System)

这是对C++标准的一个扩展,为Qt提供了信号槽机制,实时类型信息,动态属性系统。

使用元对象系统有三个基本条件:类必须继承自QObject,类中声明Q_OBJECT宏(默认私有),元对象编译器moc(Meta_Object Compiler)。

在 Qt Creator 软件中,左下角🔎查找类信息,比如输入? QDialog,就可以查看QDialog的文档,可以看到他继承自哪儿。

翻看QObject文档内容,开头第一句是:

The QObject class is the base class of all Qt objects.

这句话让我不得不把元对象系统放第一个标题,描述中它有种不怒自威,傲视群雄的感觉。

元对象系统提供的最实用的功能特性:

  • 信号与槽:这是 Qt 最核心的特性。传统的 C++ 回调函数难以保证类型安全且容易产生耦合,而信号槽利用元对象系统提供的类型信息,实现了对象之间的松耦合通信。在运行时,moc 生成的代码维护了信号与槽的连接表,让调用变得安全。

  • 属性系统:通过 Q_PROPERTY 宏,可以在运行时动态地读写对象的属性。这是 Qt 中 QML 与 C++ 交互的基石,也是 Qt Designer 能够动态调整控件属性的原理所在。

  • 运行时类型信息:标准 C++ 虽然有 typeid,但功能有限。Qt 提供了:

    • inherits():判断一个对象是否属于某个特定类或继承链。

    • qobject_cast():类似于 C++ 的 dynamic_cast,但不需要开启 RTTI(运行时类型信息),而且速度更快,专门用于 QObject 的转换。

  • 动态翻译tr() 函数之所以能够动态切换界面语言,是因为元对象系统在运行时记录了字符串的上下文。

注意一些实际开发中的问题:

  • 信号和槽的语法演进

    • Qt5 之前:必须使用 SIGNAL()SLOT() 宏,属于字符串匹配,运行时才报错。

    • Qt5 及以后:推荐使用函数指针语法(如 &MyClass::signalName),编译期检查,性能更好,也更符合现代 C++ 风格。

  • moc 的局限性

    • 模板类问题:QObject 不能作为模板类的基类。因为 moc 不支持模板预处理,如果你的类既是模板又继承了 QObject,moc 无法正确生成元对象代码。

    • 多重继承:如果必须多重继承,QObject 必须出现在继承列表的第一个位置。这是因为 moc 生成的代码假设 this 指针与 QObject 子对象的偏移量为 0。

  • 编译链接问题:如果忘记写 Q_OBJECT 宏,信号槽连接可能会失败,或者编译时出现“undefined reference to vtable”的错误。解决办法是:确保头文件中有 Q_OBJECT,并清理项目重新编译(因为 moc 可能未重新生成)。

元对象编译器MOC

前面说了,Qt设计者设计了元对象系统给C++加了一套动态特性,用的时候要在类中写上 Q_OBJECT 宏,所以在编译前,编译器要能够扫描源码中标记了 Q_OBJECT 的类,自动生成额外的 C++ 代码以实现元对象系统,之后将生成的代码和用户的代码一起编译成可执行文件,这显然只靠 C++ 编译器办不到。

这就是 MOC 出现的原因,MOC 将 Qt 特有的语法(如信号、槽、属性等)转换成标准 C++ 代码,它为每个使用了 Q_OBJECT 的类构建了一张“元数据表”,这张表在运行时可以被查询和调用。

功能 MOC 生成的代码负责
信号 为每个信号生成一个函数,该函数调用 qt_metacall 来激活所有连接到该信号的槽。
在元对象表中注册槽函数的索引和签名,使得 qt_metacall 可以根据索引调用正确的槽函数。
属性系统 生成 qt_metacall 中对 Q_PROPERTY 的读/写处理,支持动态获取和设置属性。
运行时类型信息 生成 staticMetaObject 静态对象,以及 metaObject()inherits()qobject_cast 所需的类型比较数据。
动态翻译 标记 tr() 中的字符串上下文,使翻译系统能准确定位。

MOC工作流程,其中生成的 moc_xxx.cpp 代码可以在 build 目录里找到,里面有很多”元调用”(上面表格记录的调用函数):

graph TD
    A[项目源文件] --> B{头文件含有 Q_OBJECT?}
    B -->|是| C[MOC 处理]
    B -->|否| D[常规编译]
    C --> E[生成 moc_xxx.cpp]
    E --> F[与原始源文件一起编译]
    D --> F
    F --> G[链接生成可执行文件]

当然,作为一个编译器,我们开发时对它的存在是“无感”的,只是了解一下可以对 Q_OBJECT 扩展出来的奇奇怪怪的东西获得一分通透感。

信号与槽

信号与槽(Signals & Slots)的灵活性,概括起来就九个字:松耦合,一对多,多对一;下面把它和传统回调对比同时感受一下灵活性。

信号与槽相比传统回调(如函数指针、观察者模式),主要有以下优势:

  1. 类型安全:编译期检查参数类型,避免回调签名不匹配导致的崩溃。
  2. 松耦合:发送者无需知道接收者是谁,只需发射信号;接收者只需连接信号,无需修改发送者代码。
  3. 多对多通信:一个信号可连接多个槽,一个槽可接收多个信号,自动管理连接列表。
  4. 自动生命周期管理:当发送者或接收者被销毁时,连接自动断开,避免悬空指针。

对比场景实例:按钮点击时,同时更新日志和播放声音。

传统回调(函数指针)方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 回调函数类型
typedef void (*ClickCallback)();

class Button {
std::vector<ClickCallback> callbacks;
public:
void addCallback(ClickCallback cb) { callbacks.push_back(cb); }
void click() {
for (auto cb : callbacks) { cb(); }
}
};

void updateLog() { /* ... */ }
void playSound() { /* ... */ }

Button btn;
btn.addCallback(updateLog);
btn.addCallback(playSound);
btn.click();

缺点:类型不安全(可传入任何返回void无参的函数),无法传递上下文,手动管理回调列表易出错。

Qt 信号槽方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Button : public QObject {
Q_OBJECT
public:
void click() { emit clicked(); }
signals:
void clicked();
};

class App : public QObject {
Q_OBJECT
public slots:
void updateLog() { /* ... */ }
void playSound() { /* ... */ }
};

Button btn;
App app;
QObject::connect(&btn, &Button::clicked, &app, &App::updateLog);
QObject::connect(&btn, &Button::clicked, &app, &App::playSound);
btn.click();

优点:类型安全(连接时签名必须匹配),发送者与接收者完全解耦,断开连接自动处理。

例子中信号槽的连接关系

graph LR
    subgraph 发送者
        B[Button]
    end
    subgraph 接收者
        A[App]
    end
    B -- clicked() --> A
    A -->|调用 playSound 和 updateLog| A

图中可见一个信号可以触发多个槽,且信号与槽之间没有直接依赖。

connect 和 disconnect

上面我们看到了信号槽的松耦合和一对多特性,多对一也是类似,设置好 connect 就行了,需要注意的是,一对多 connect 时槽方法参数要匹配。

而且,信号还能触发信号(信号 connect 信号),这更加验证了信号槽的灵活性。

断开信号与槽连接使用 disconnect,可以指定需要操作的对象、信号和槽,特定参数位为0则表示:所有对象,所有信号,所有槽。

比如,断开A对象和B对象的信号槽连接:

1
disconnect(&A, &A::signal, &B, &B::slot);

断开A对象信号和B对象所有槽的连接:

1
disconnect(&A, &A::signal, &B, 0);

断开A对象所有信号和所有对象所有槽的连接:

1
disconnect(&A, 00, 0);

断开所有对象信号和槽的连接:

1
disconnect(0, 00, 0);

属性系统(Q_PROPERTY

属性系统(Q_PROPERTY)是 Qt 元对象系统的重要组成部分,它让你能够在运行时动态地读取、写入对象的属性,并且支持属性变化通知。这在自定义控件、QML 与 C++ 交互、Qt Designer 可调属性等场景中极其常用。

简单来说,它把一个 C++ 类的成员变量或方法“暴露”给 Qt 的元对象系统,让外部可以:

  • QObject::property()setProperty() 动态读写
  • Qt Designer 中直接修改(用于自定义控件)
  • QML 中绑定和自动更新
  • 配合信号实现属性变化的自动通知

Q_PROPERTY 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
// ↑类型 ↑属性名 ↑读方法 ↑写方法 ↑变化信号(可选)
public:
int value() const { return m_value; }
void setValue(int v) {
if (m_value != v) {
m_value = v;
emit valueChanged(v);
}
}

signals:
void valueChanged(int newValue);

private:
int m_value = 0;
};

关键部分

  • READ:必选,提供一个 const 的 getter 方法。
  • WRITE:可选,提供一个 setter 方法。如果没有,属性就是只读的。
  • NOTIFY:可选,当属性值改变时发射的信号。强烈建议加上,这样外部才能监听到变化(比如 QML 自动刷新)。
  • 其他可选关键字:DESIGNABLE(是否在 Designer 中可见)、STORED(是否持久化)等,但工作中最常用的就是 READ、WRITE、NOTIFY。

常用场景

  • 自定义控件 + Qt Designer

如果你写一个自定义控件,想让用户在设计器里调整它的外观属性,就用 Q_PROPERTY 声明,并加上 DESIGNABLE true(默认就是 true)。这样 Designer 会自动生成属性编辑器。

  • QML 与 C++ 交互

将 C++ 对象注册到 QML 后,QML 可以直接读写该对象的 Q_PROPERTY 属性,并且当 NOTIFY 信号触发时,QML 中绑定的界面会自动更新。

  • 动态保存/恢复状态

QObject::property()setProperty() 可以在不修改类的情况下临时存储一些额外数据。比如给一个按钮附加一个 id 值:

1
2
button->setProperty("value", 100);
int id = button->property("value").toInt();

这在一些动态界面、数据传递时很实用。

注意事项

  • 属性名不要和已有的方法名冲突,通常用小写开头。
  • NOTIFY 信号尽量在属性真正变化时才发射,避免在 setter 中无脑发射(先比较新旧值)。
  • 属性类型可以是 int、bool、QString、枚举(需用 Q_ENUM 注册)等。如果是自定义类型,需要用 Q_DECLARE_METATYPE 注册。
  • 如果你只是临时想给一个对象附加点数据,不需要专门定义子类,直接用 setProperty 动态添加即可,元对象系统会帮你在运行时管理这些动态属性。

对象树

每个QObject内部有一个子对象列表,父对象析构时自动删除所有子对象,层层传递,最终由顶层对象释放整棵树。

核心要点

  1. 父子关系通过构造函数参数或 setParent() 建立new QObject(parent)
  2. 有父对象,绝不手动 delete,否则会导致双重释放。
  3. 所有 QObject 子类都支持对象树(不需要 Q_OBJECT 宏,但你要用信号槽就得加)。
  4. 顶层窗口(如 QMainWindow)通常没有父对象,由 QApplication 间接管理,一般不需要手动删除。
  5. 可以通过 findChild<T>() 按类型/名字查找子对象

常见错误

  • 在栈上创建 QObject 子类(如 QPushButton btn(this);)→ 双重释放。
  • 给有父对象的子对象手动 delete → 崩溃。
  • 忘记给布局或控件指定父对象 → 内存泄漏。

示例

添加析构函数声明(在 public 区域):

1
2
// colorcircle.h
~ColorCircle();

添加析构函数实现,并包含QDebug

1
2
3
4
5
6
// colorcircle.cpp
#include <QDebug>

ColorCircle::~ColorCircle() {
qDebug() << "ColorCircle 被销毁了";
}

关闭主窗口将会看到这个打印,实际上我们new QObject[Son] {this}的地方都会被对象树管理。

组件基类 QWidget

如果是非自定义控件,布局和样式还可以在设计模式中通过属性表配置。

QWidget类关系图 - ꧁坚持很酷꧂(CSDN)

QWidget类关系图

窗体和控件风格

只需掌握 最常用的几种设置样式表(QSS)基础,就能应对大部分界面美化需求。

窗口标志(Qt::WindowFlags)—— 控制窗口的“长相”和“行为”

窗口标志决定了窗口是否有标题栏、能否最大化、是否为工具窗等。常用组合:

效果 代码(在构造或 setWindowFlags 中使用)
普通窗口(默认) Qt::Widget
顶级窗口(带标题栏、关闭按钮) Qt::Window
对话框(带问号?可设置) Qt::Dialog
无边框窗口 Qt::FramelessWindowHint
始终置顶 Qt::WindowStaysOnTopHint
工具窗口(窄标题栏,不在任务栏显示) Qt::Tool

实际工作中最常用的是 无边框窗口(自定义标题栏和拖动)和 置顶窗口(如悬浮工具)。

比如我们可以这样设置主窗口无边框:

1
2
// 在 MainWindow 构造函数中
setWindowFlags(Qt::FramelessWindowHint);

如果同时需要多个标志,用 | 组合:

1
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);

注意:改变窗口标志后,通常需要调用 show() 才能生效(或在构造函数末尾调用 setWindowFlags 后调用 show())。如果窗口已显示,可以先 hide()show()

样式表(QSS)—— 让控件变漂亮

Qt 样式表语法类似 CSS,可以快速改变控件的外观(背景色、边框、圆角、字体等)。

最常用的一些样式设置

给整个窗口设置背景色和圆角

1
setStyleSheet("QWidget { background-color: #2c3e50; border-radius: 10px; }");

给按钮设置样式

1
2
3
4
5
6
7
8
9
10
11
12
13
QPushButton {
background-color: #3498db;
color: white;
border: none;
border-radius: 5px;
padding: 8px 16px;
}
QPushButton:hover {
background-color: #2980b9;
}
QPushButton:pressed {
background-color: #1c5a7a;
}

给一个特定控件单独设置样式(用对象名):

1
2
button->setObjectName("myButton");
button->setStyleSheet("#myButton { background-color: red; }");

实用技巧

  • 全局样式:通常在 main() 中给 QApplication 设置样式表,所有控件都会继承。
  • 边框圆角border-radius: 5px; 需要配合 border 使用,否则不生效。
  • 背景透明background-color: transparent;background: transparent;
  • 无边框窗口的阴影:无边框窗口默认没有阴影,但可以用 QGraphicsDropShadowEffect 模拟。

无边框窗口的拖动实现

无边框窗口没有标题栏,无法拖动。你需要自己实现鼠标拖动逻辑:

在 MainWindow 中添加成员变量

1
2
3
private:
QPoint m_dragPos;
bool m_dragging = false;

重写鼠标事件(在 MainWindow 中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QMouseEvent>

void MainWindow::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
m_dragging = true;
m_dragPos = event->globalPos() - frameGeometry().topLeft();
event->accept();
}
}

void MainWindow::mouseMoveEvent(QMouseEvent *event) {
if (m_dragging && (event->buttons() & Qt::LeftButton)) {
move(event->globalPos() - m_dragPos);
event->accept();
}
}

void MainWindow::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
m_dragging = false;
event->accept();
}
}

半透明窗口

1
setWindowOpacity(0.8);   // 0.0 完全透明,1.0 不透明

注意:无边框窗口开启半透明后,可能需要设置 setAttribute(Qt::WA_TranslucentBackground) 来实现真正的透明背景。

样式设置 api 概览

需求 代码
无边框 setWindowFlags(Qt::FramelessWindowHint);
置顶 setWindowFlags(Qt::WindowStaysOnTopHint);
全局样式 qApp->setStyleSheet("...");
单控件样式 widget->setStyleSheet("...");
无边框拖动 重写 mousePressEvent / mouseMoveEvent
半透明 setWindowOpacity(0.8);

窗口和控件尺寸和尺寸策略

三种基础尺寸设置方法

方法 作用 常用场景
setFixedSize(w, h) 固定大小,用户不能拖动改变 按钮、图标、固定大小的控件
setMinimumSize(w, h) 设置最小尺寸(可以更大) 窗口、可缩放的控件
setMaximumSize(w, h) 设置最大尺寸 限制控件过大
resize(w, h) 设置当前尺寸(用户仍可改变) 窗口初始大小
示例:固定按钮大小
1
2
QPushButton *btn = new QPushButton("确定");
btn->setFixedSize(80, 30);
示例:让窗口最小 400x300,最大 800x600
1
2
setMinimumSize(400, 300);
setMaximumSize(800, 600);

布局中的尺寸策略(sizePolicy

当你使用布局管理器(QVBoxLayout 等)时,控件的大小由布局和控件的 尺寸策略 共同决定。

常用策略(QSizePolicy::Policy
策略 含义 常见控件默认策略
Fixed 大小固定,不拉伸 按钮、标签(水平方向)
Minimum 可以扩展,但不能小于 sizeHint() 输入框
Expanding 优先扩展,占据多余空间 文本编辑、列表框
Preferred sizeHint(),可以扩展或收缩 大多数控件
Maximum 可以收缩,但不能大于 sizeHint() 不常用
让按钮水平方向自动拉伸
1
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

第一个参数是水平策略,第二个是垂直策略。

重要概念:sizeHint()minimumSizeHint()
  • sizeHint():控件的“建议大小”,基于内容(如按钮文字、图片)。
  • minimumSizeHint():控件能正常显示的最小大小。

你很少需要重写这些函数,但要理解布局会参考它们。

让控件随窗口自动缩放(最常用)

当你把控件放入布局(QVBoxLayoutQHBoxLayoutQGridLayout)后,布局会自动调整控件大小。想让某个控件在窗口放大时“抢”到更多空间,有两个办法:

方法1:设置 sizePolicyExpanding
1
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
方法2:设置布局的拉伸因子(stretch

addWidget 时指定拉伸系数:

1
2
layout->addWidget(widget1, 1);  // 拉伸因子1
layout->addWidget(widget2, 2); // 拉伸因子2 → widget2 获得两倍多余空间

尺寸设置 api 概览

需求 代码
固定大小 setFixedSize(w, h)
最小/最大限制 setMinimumSize(w, h) / setMaximumSize(w, h)
布局中让控件可扩展 setSizePolicy(Expanding, Expanding)
布局中按比例分配空间 addWidget(widget, stretch)
窗口初始大小 resize(w, h)

常用控件 QDialog

只需要掌握 模态 vs 非模态标准对话框如何做自定义对话框,就能应对大部分场景。

QDialog 是什么?

QDialogQWidget 的子类,专门用来做 临时交互窗口(比如提示、设置、选择)。它的核心特点是 可以以“模态”方式运行,阻塞其他窗口直到对话框关闭。

模态 vs 非模态(最重要)

类型 效果 常用场景
模态(Modal) 弹出后,用户必须关闭对话框才能操作其他窗口 文件选择、确认删除、登录框
非模态(Modeless) 弹出后,可以同时操作主窗口 查找替换、属性面板、工具窗口

代码写法

模态(阻塞)

1
2
QDialog dialog(this);
dialog.exec(); // 阻塞,直到对话框关闭

非模态(不阻塞)

1
2
3
QDialog *dialog = new QDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动删除
dialog->show(); // 不阻塞

工作中 模态对话框更常用,因为它简单且符合大多数用户预期。

在 Qt 中,设置对话框的模态/非模态行为有多种方式。下表总结了最常用的方法及其效果:

方式 代码示例 模态/非模态 说明
exec() dialog.exec(); 模态(阻塞) 启动独立的事件循环,阻塞所有同应用窗口,直到对话框关闭。返回值 Accepted / Rejected
setModal(true) + show() dialog.setModal(true); dialog.show(); 模态(非阻塞) 设置为模态后调用 show(),不阻塞代码执行,但对话框模态阻止其他窗口交互。
setModal(false) + show() dialog.setModal(false); dialog.show(); 非模态 对话框不阻塞其他窗口,可同时操作。
open() dialog.open(); 模态(非阻塞) 相当于 setModal(true) + show(),且提供了 finished 信号(可连接槽接收结果)。不阻塞代码。
setWindowModality() dialog.setWindowModality(Qt::ApplicationModal); dialog.show(); 按参数决定 细粒度控制:Qt::NonModal(非模态)、Qt::WindowModal(阻塞父窗口及祖先)、Qt::ApplicationModal(阻塞整个应用)。
show() 默认 如果父窗口存在且未设置 WA_ShowModal 属性 通常非模态 默认 QDialogshow() 是非模态的,除非设置了模态属性或调用了 exec()

Qt 内置的标准对话框(拿来即用)

对话框 静态方法 用途
消息框 QMessageBox::information(), warning(), critical(), question() 提示信息、询问确认
文件对话框 QFileDialog::getOpenFileName(), getSaveFileName() 打开/保存文件
颜色对话框 QColorDialog::getColor() 选择颜色
字体对话框 QFontDialog::getFont() 选择字体
输入对话框 QInputDialog::getText(), getInt(), getDouble() 获取用户输入

示例:消息框询问

1
2
3
4
QMessageBox::StandardButton reply = QMessageBox::question(this, "确认", "确定要退出吗?");
if (reply == QMessageBox::Yes) {
close();
}

示例:获取颜色

1
2
3
4
QColor color = QColorDialog::getColor(Qt::red, this, "选择颜色");
if (color.isValid()) {
// 使用 color
}

自定义对话框(最实用)

当标准对话框不能满足需求时,自己做一个对话框很简单:

1. 新建一个类继承 QDialog

myDialog.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>

class MyDialog : public QDialog {
Q_OBJECT
public:
explicit MyDialog(QWidget *parent = nullptr);
QString getInputText() const { return m_lineEdit->text(); }
private:
QLineEdit *m_lineEdit;
QPushButton *m_okBtn;
QPushButton *m_cancelBtn;
};

myDialog.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
MyDialog::MyDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle("自定义输入");
QVBoxLayout *layout = new QVBoxLayout(this);
m_lineEdit = new QLineEdit(this);
m_okBtn = new QPushButton("确定", this);
m_cancelBtn = new QPushButton("取消", this);
layout->addWidget(m_lineEdit);
layout->addWidget(m_okBtn);
layout->addWidget(m_cancelBtn);

connect(m_okBtn, &QPushButton::clicked, this, &QDialog::accept);
connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
}

2. 使用自定义对话框

1
2
3
4
5
MyDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.getInputText();
// 处理输入
}

关键点:

  • 使用 exec() 模态运行。
  • 点击确定调用 accept(),点击取消调用 reject()
  • 通过 exec() 的返回值判断用户操作。

作用机制

一、exec() 做了什么?

当你调用 dialog.exec() 时,Qt 会:

  1. 显示对话框(如果还没显示)。

  2. 启动一个新的事件循环(QEventLoop),这个循环会不断处理窗口消息(鼠标点击、键盘输入等)。

  3. 阻塞当前代码(模态),直到事件循环退出。

  4. 返回一个结果:QDialog::Accepted 或 QDialog::Rejected。

那事件循环什么时候退出?当对话框内部调用了 accept()reject() 时。

二、accept() 和 reject() 的作用

accept():告诉对话框“用户确认了”,内部会设置结果为 Accepted,然后退出事件循环。

reject():告诉对话框“用户取消了”,内部设置结果为 Rejected,然后退出事件循环。

这两个函数是 QDialog 的槽,也是普通的成员函数。你可以随时调用它们。

所以我们需要事先connect信号槽(clicked和accept/reject),后面用 dialog.exec() 调用就可以生效了。

QDialog api 概览

需求 代码
模态对话框 dlg.exec()
非模态对话框 dlg.show() + setAttribute(Qt::WA_DeleteOnClose)
标准消息框 QMessageBox::question(...)
标准颜色对话框 QColorDialog::getColor(...)
自定义对话框 继承 QDialog,使用 accept() / reject()
判断用户确认 if (dlg.exec() == QDialog::Accepted) {...}

常用控件 QLabel

QLabel 是最常用的显示控件之一,用来显示文本、数字、图片或富文本。它不能编辑,但可以响应点击等事件(需额外处理)。

基础用法:显示文本

1
QLabel *label = new QLabel("Hello Qt", this);

常用设置:

  • setText(QString):改变显示内容。
  • text():获取当前文本。
  • setAlignment(Qt::AlignCenter):设置对齐(居中、左、右等)。
  • setWordWrap(bool):是否自动换行。

示例:创建并设置标签

1
2
3
QLabel *info = new QLabel(this);
info->setText("用户名:");
info->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

对齐方式常用组合:

  • Qt::AlignLeft / AlignRight / AlignHCenter(水平)
  • Qt::AlignTop / AlignBottom / AlignVCenter(垂直)
  • 可以用 | 组合,如 AlignCenter 即水平垂直居中。

显示数字或其它类型

QLabel 只接受 QString,所以需要将数字转换:

1
2
3
int count = 10;
label->setText(QString::number(count)); // "10"
label->setText(QString("数值:%1").arg(count)); // "数值:10"

显示图片(QPixmap)

1
2
3
4
5
QLabel *imageLabel = new QLabel(this);
QPixmap pixmap("./images/logo.png"); // 资源文件或绝对路径
imageLabel->setPixmap(pixmap);
// 可选:缩放图片以适应标签大小(会改变像素)
imageLabel->setScaledContents(true);

如果你想保持图片比例,可以调整标签大小或使用 QPixmap::scaled()

富文本(HTML 子集)

QLabel 支持简单的 HTML 标签,如 <b><i><font color=...><img> 等。

1
2
label->setText("<b>粗体</b> 和 <i>斜体</i> 以及 <font color=red>红色</font>");
label->setText("<a href='http://qt.io'>Qt官网</a>");

处理链接点击:需要设置 setOpenExternalLinks(true) 才会自动打开浏览器。

1
2
label->setText("<a href='https://www.qt.io'>Qt官网</a>");
label->setOpenExternalLinks(true);

常用信号(QLabel 本身信号很少)

QLabel 不直接提供点击信号。要检测鼠标点击,需要重写 mousePressEvent 或使用事件过滤器。但简单做法是用一个按钮替代,或者将 QLabel 放在一个 QPushButton 内部(不推荐)。

如果你需要可点击的文本,可以用 QPushButton 设置扁平样式,或用 QLabel + 事件过滤器。新手阶段可以先用按钮。

QLabel api 概览

功能 代码
设置文本 setText(QString)
获取文本 text()
设置对齐 setAlignment(Qt::AlignCenter)
显示数字 setText(QString::number(num))
显示图片 setPixmap(QPixmap(...))
缩放图片适应 setScaledContents(true)
支持HTML 直接 setText("<b>bold</b>")
自动打开链接 setOpenExternalLinks(true)

常用控件 Button

在 Qt 中,所有按钮都继承自 QAbstractButton,它提供了按钮的通用功能(点击、状态、文本等)。实际工作中最常用的按钮子类有四种:QPushButton(普通按钮)、QToolButton(工具按钮)、QRadioButton(单选按钮)、QCheckBox(复选框)。

按钮家族总览

按钮类型 典型用途 是否互斥 是否可选中
QPushButton 执行命令(确定、取消、提交) 通常不可选中(除非设置 checkable)
QToolButton 工具栏中的图标按钮 同左
QRadioButton 单选选项(性别、模式) 是(同一父控件内) 是,选中后保持
QCheckBox 多选选项(兴趣爱好) 是,独立选中/取消

逐个击破(实用级)

1. QPushButton:最常用的按钮

1
2
3
4
5
QPushButton *btn = new QPushButton("点击我", this);
// 连接点击信号
connect(btn, &QPushButton::clicked, this, [](){
qDebug() << "按钮被点了";
});

常用设置

  • setEnabled(bool):禁用/启用(变灰)。
  • setCheckable(true):变成开关按钮(按下/弹起状态)。
  • setIcon(QIcon(":/icon.png")):添加图标。

工作中QPushButton 占按钮使用的 80% 以上,记住它的点击信号就够了。

2. QToolButton:工具栏按钮(通常只显示图标)

1
2
3
4
QToolButton *toolBtn = new QToolButton(this);
toolBtn->setIcon(QIcon(":/save.png"));
toolBtn->setToolTip("保存"); // 悬停提示
connect(toolBtn, &QToolButton::clicked, this, &saveFile);

区别QToolButton 默认没有文字边框,适合放在工具栏;QPushButton 适合放在窗口主体。

3. QRadioButton:单选按钮

同一组内只能选一个。要自动互斥,需要将它们放在同一个父控件(如 QWidget)或使用 QButtonGroup

1
2
3
QRadioButton *male = new QRadioButton("男", this);
QRadioButton *female = new QRadioButton("女", this);
male->setChecked(true); // 默认选中男

获取选中状态

1
if (male->isChecked()) { /* 选中的是男 */ }

注意:如果不希望它们互斥(比如两组单选按钮),用 QButtonGroup 分组。

4. QCheckBox:复选框

可以独立选中/取消,也支持“三态”(半选,用于树形节点)。

1
2
3
4
5
QCheckBox *agree = new QCheckBox("我同意协议", this);
// QCheckBox::stateChanged 已弃用
connect(agree, &QCheckBox::checkStateChanged, this, [](int state){
// state: Qt::Checked, Qt::Unchecked, Qt::PartiallyChecked
});

常用信号:checkStateChangedtoggled

Button 核心要点

  • 90% 的按钮需求QPushButton + clicked 信号。
  • 开关效果setCheckable(true) + toggled 信号。
  • 单选按钮组:把几个 QRadioButton 放在同一个父控件里就自动互斥。
  • 复选框stateChanged 信号,isChecked() 判断。
  • 工具按钮:通常用于工具栏,可以只放图标。

常用控件 QLineEdit

QLineEdit 是最常用的输入控件,用于让用户输入一行文本(用户名、密码、数字等)。工作中 90% 的单行输入场景都由它完成。

基础用法

1
2
3
4
QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setPlaceholderText("请输入用户名"); // 灰色提示文字
QString text = lineEdit->text(); // 获取输入内容
lineEdit->setText("初始值"); // 设置内容

常用设置(实用级)

功能 代码
清空按钮(右侧X) lineEdit->setClearButtonEnabled(true);
密码模式(显示圆点) lineEdit->setEchoMode(QLineEdit::Password);
只读模式 lineEdit->setReadOnly(true);
限制最大输入长度 lineEdit->setMaxLength(16);
输入掩码(如手机号) lineEdit->setInputMask("999-9999-9999");

常用回声模式

  • QLineEdit::Normal:正常显示
  • QLineEdit::Password:显示密码圆点
  • QLineEdit::NoEcho:完全不显示(密码类,防偷窥)

注:手机号、IP 地址、MAC 地址、日期格式、产品序列号等固定格式输入,掩码比较有用,需要用的时候查表编写代码就好。

QLineEdit设置掩码ip - FreeLikeTheWind.(CSDN)

常用信号

信号 触发时机 常用场景
textChanged(const QString &) 内容改变(包括代码设置) 实时搜索、输入统计
textEdited(const QString &) 用户编辑触发(代码设置不触发) 用户输入验证
returnPressed() 按下回车键 登录、搜索提交
editingFinished() 编辑完成(焦点离开或回车) 保存数据

示例:实时显示输入长度

1
2
3
connect(lineEdit, &QLineEdit::textChanged, this, [](const QString &text){
qDebug() << "当前长度:" << text.length();
});

输入验证(常用)

  • QIntValidator 限制整数范围
1
2
QLineEdit *ageEdit = new QLineEdit(this);
ageEdit->setValidator(new QIntValidator(0, 150, this));
  • QDoubleValidator 限制浮点数
1
2
QLineEdit *doubleEdit = new QLineEdit(this);
doubleEdit->setValidator(new QDoubleValidator(0.0, 100.0, 2, this));
  • QRegularExpressionValidator 自定义规则(如手机号):
1
2
3
QRegularExpression regex("^1[3-9]\\d{9}$");
QRegularExpressionValidator *validator = new QRegularExpressionValidator(regex, this);
phoneEdit->setValidator(validator);

LineEdit 核心要点

  • 获取文本:text()
  • 设置提示:setPlaceholderText()
  • 清空按钮:setClearButtonEnabled(true)
  • 密码模式:setEchoMode(QLineEdit::Password)
  • 输入限制:setValidator()
  • 常用信号:textChanged(实时)、returnPressed(回车)