Qt: 基本用法
Qt基础
在Qt5后,Qt不断开发QML和Qt Quick,同时在抛弃传统的基于C++的Qt Widgets,致力于更快捷、直观地绘制应用
当然,Qt的优势在于极致的跨平台和前后端的顺畅结合,Qt Widgets和Qt Quick的设计思路是相似的;多是QML/C++混合开发,纯粹的Qt Widgets在前端开发上是较为低效的
本文并不想介绍QML,而是介绍Qt Widgets
元对象系统
简介
元对象系统(Meta-Object System, MOS)是Qt的核心机制,元对象即用于描述对象的对象;它提供以下功能:
- 信号-槽:允许对象间安全的通信,方便响应事件
- 反射系统:允许程序自己获取(运行时获取)对象的信息,在
java、dotNet中也有类似的概念 - 动态属性:允许程序在运行时添加属性
所有继承QObject的类中,只要私有声明了宏Q_OBJECT,就可使用MOS
信号与槽
关键字:借助
MOC(元对象编译器),Qt为了实现信号槽机制,添加了slots,signals关键字(宏)信号和槽都是成员函数,一般声明如下:
1
2
3
4
5
6
7class Class : public QObject {
Q_OBJECT
signals:
...
private slots:
...
};signals宏定义为public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal),一定是公有的slots则可以在任意域中,但一般是保护或私有的一般,每个
QObject的子类中的信号都应该有对应的用于发送该信号的成员函数(Qt自带类中一般定义为公有槽函数)一般控件有继承自父类的
Qt实现的信号-槽和发送-响应机制
自定义信号:
- 无返回值,可以有参数
- 只需声明,无需实现(
MOC自动实现)
自定义槽:
- 返回值及参数必须和相连接的信号一致
- 一旦声明,必须实现
发送信号:
通常在成员函数中,可以使用
emit关键字发送自定义信号,同时传递参数:1
emit mySignal(...);
需要主动发送信号时,通常调用封装好的发送函数,而不是直接使用
emit1
2
3
4
5// 官方命名习惯(例如按钮类):
public slots:
click(); // 发送信号.
signals:
clicked(); // 信号.
连接方式:
手动连接:调用
connect(),四个必须填写的参数依次为发送者指针、信号、接收者指针、槽有三种输入信号-槽参数的方式:
旧版本:调用宏函数
SIGNAL()和SLOT(),处理输入的函数名后返回特定字符串连接失败后无反馈,例:
1
2
3// 参数不用添加限定符.
connect(ui->lineEdit, SIGNAL(textChanged(QString)),
this, SLOT(doSomething(QString)));新版本:传递函数指针,好处是连接失败后会报错,如:
1
2connect(ui->lineEdit, &QLineEdit::textChanged,
this, &MainWindow::doSomething);lambda表达式(匿名函数):好处是不用额外声明槽函数1
2
3connect(ui->button, SIGNAL(textChanged(QString)), [this](const QString&) {
...
});
连接函数的第五个参数表示连接方式,默认为自动关联,常用的有:
AutoConnection:信号槽在同一线程则为DirectionConnection,否则为QueuedConnectionDirectionConnection:信号发射后直接调用槽,槽函数执行完毕后再执行emit语句后的代码QueuedConnection:槽在接收者的线程中执行(若在不同线程,则发送线程继续进行)
默认连接:
Qt编译器会尝试让所有名为on_objName_signalName()的槽函数进行连接类似于调用:
1
2connect(&objName, &SenderClass::signalName,
this, &thisClass::on_objName_signalName);注:
objName命名不能太长,否则连接失败;连接失败后不会终止程序,但会打印错误信息
常用控件的信号:
1
2
3
4
5
6
7
8
9
10
11
12
13QPushButton: # 按钮
clicked() # 被点击
QLineEdit: # 行编辑器
textChanged(const QString&) # 文本被修改, 同时发送修改后的文本
returnPressed() # 焦点在控件上时, 按下回车
cursorPositionChanged() # 光标移动
editingFinished() # 失去焦点/焦点在该控件上时, 按下回车
selectionChanged() # 选中区域被修改
QDialog: # 对话框
accepted()/rejected() # 因用户确认/拒绝对话框而被关闭
finished() # 被关闭
QStackedWidget/QToolBox: # 堆栈窗口(多窗口器)/工具箱
currentChanged(int idx) # 当前窗口/选项修改, 同时发送新窗口的索引可能用到的控件的信号:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63QSplitter: # 窗口分割器
splitterMoved(int pos, int idx) # 分割条被移动, 同时发送移动后的位置、被移动条的索引
QAction/QToolButton: # 动作/工具按钮
triggered() # 被触发
QSpinBox/QDoubleSpinBox: # 整数旋钮/浮点数旋钮
valueChanged(int/double val) # 值被修改, 同时发送新的值
### 事件系统
事件和信号槽的区别:
- 信号本身没有和底层硬件、操作系统交流的能力,部分信号还需要通过事件发出(如`clicked()`)
- 关注点:
- 信号槽:发出、接收信号的**对象**
- 事件处理:发生的是**哪种事件**,并不关注对象间的通信
- 来源:
- 信号:程序里的函数,由对象发出
- 事件:**系统的底层消息**
- 本质:
- 信号槽:成员函数
- 事件:对象
`Qt`的事件循环:
- 产生:应用启动(`QApplication::exec()`)后,`Qt`即开启事件循环,不断检测底层消息并将它们转换为事件
- 分发:事件通过`QApplication::notify()`传入到**事件队列**,按队列的顺序取出事件,调用事件分发函数`bool event(QEvent* event)`
- 处理:`event()`会分情况调用不同的事件处理函数
- 事件循环里,事件的产生和处理是异步的
事件也可手动发送,它们不经过事件循环
有些类(如按钮类)的处理函数已经封装好了,而有些类(如窗口类)的键鼠事件处理函数没有语句,因此有需要时,需要**重写**它们,一般有两种处理方式:
- 发送信号,将事件的处理解耦成多个槽函数,可扩展性强,便于跨对象、跨线程通信
- 直接在函数内部处理,可用于事件处理较为简单的情况
常见的事件处理函数:
```python
# 启用某种属性的函数,某些事件需要特定属性才能产生
setAttribute(Qt::WidgetAttribute, bool)
# 以下函数返回值为void
键盘:
keyPressEvent(QKeyEvent* event) # 按键按下
keyReleaseEvent(QKeyEvent* event) # 按键松开
鼠标:
mousePressEvent(QMouseEvent* event) # 鼠标按下
mouseReleaseEvent(QMouseEvent* event) # 鼠标松开
mouseDoubleClickEvent(QMouseEvent* event) # 鼠标双击
wheelEvent(QWheelEvent* event) # 鼠标滚轮滑动
enterEvent(QEvent* event) # 鼠标移进
leaveEvent(QEvent* event) # 鼠标移出
hoverMoveEvent(QHoverEvent* event) # 鼠标悬停(需启用WA_Hover)
焦点:
focusInEvent(QFocusEvent* event) # 获得焦点
focusOutEvent(QFocusEvent* event) # 失去焦点
拖动:
setAcceptDrops(bool) # 设置控件能否拖动
dragEnterEvent(QDragEnterEvent* event) # 被拖动的物体进入可放置区域
dragMoveEvent(QDragMoveEvent* event) # 被拖动的物体移动
dragLeaveEvent(QDragLeaveEvent* event) # 被拖动的物体离开可放置区域
dropEvent(QDropEvent* event) # 放下
传入的事件本身是带有信息的,例如:
1 | QKeyEvent: |
关于事件分发函数event()的重写:
1 | // event()是事件分发函数, 是有返回值的(表示事件是否已处理) |
项目初始化
通过项目初始化来介绍Qt在控件上的一些机制和默认
.ui文件
在创建Qt项目时,应该自选生成.ui文件,能轻易地在Qt Designer中修改它,并定义窗口和控件对象、以及它们的初始化
该文件使用
xml语言,在Qt Designer上修改比直接编辑快的多其能在
C++程序中起作用的原因是,Qt编译器会通过uic(ui编译器)将name.ui翻译成头文件ui_name.h,其中定义了类Ui_name:该类定义了所有在
QD设计的、除窗口类外的控件定义了成员函数
setupUi(QMainWindow*),用于初始化各种控件自动生成的项目主窗口类中,默认定义
Ui_name类的指针ui所有构造函数的第一行应调用
ui->setupUi(this),以确保能安全调用ui中的控件
主窗口类及内存机制
学习自动创建的主窗口头文件格式,便于学会自定义控件类
设创建项目时定义的主窗口类为MainWindow,则应有头文件定义:
1 | class MainWindow : public QMainWindow { |
Q_OBJECT:Qt定义的宏,只有声明了它,才能使用元对象系统任何继承了
QObject类的子类都应私有声明该宏简单窗口类可直接继承自
QWidget复杂窗口类(如包含菜单栏、对话框)应继承
QMainWindow,才能正常显示
构造函数的实现至少是这样的:
1 | MainWindow::MainWindow(QWidget* parent) |
作为参数传入的parent和this是有必要的,因为Qt有自己的一套回收内存的机制:
半自动回收:所有
QObject类及其子类对象都定义了parent和链表(用于存储子控件的指针)习惯上,所有控件类对象都使用堆内存,并在构造时指定父控件
当释放父控件的内存时,会先释放所有子控件的内存,因此只要记得释放根控件的内存,就不会出现内存泄露
而释放子控件的内存后,它会从其父控件的链表中删去,因此不会出现重复释放内存的情况
所有这些对象都有
QObject::setParent()方法,用于设置父控件
手动释放:对于父控件在不同线程、或非
QObject系的占用堆内存的对象,无法半自动回收,应在其所在线程里调用deleteLater()方法,调用后会在线程结束时释放其内存
外部资源管理
Qt中通过.qrc(Qt Resource)文件管理外部资源,该文件使用xml语言,在QC中能更直观地使用
1 |
|
直接编码也不难理解,这些标签的含义是:
RCC:其内容会被rcc(Qt的资源编译器)识别,并把资源编译到可执行文件里qresource:用于指定其所属文件的前缀,前缀默认为"/"file:其内容为资源的真正的路径,例如上述语句引入了处于./images/name.jpg路径(相对于该资源文件)的图片
.qrc文件通过独特的相对路径管理资源,即冒号+前缀+文件路径,例如通过":/Icons/images/name.jpg"访问上述语句中的文件
使用资源的方式:
1 | setWindowIcon(QIcon(路径)) // 设置窗口图标,仅窗口类可使用 |
样式
关于样式,推荐通过QD进行初始化,或一律通过外部样式表统一,而不是通过C++编码修改
如果有CSS基础,许多属性会很熟悉
控件
所有QWidget系对象都有(由以下类组合而成):
font:存储字体族、字体大小、粗体、斜体等(构造函数的参数就是这四个属性)对象可调用
setFont(QFont)方法修改sizePolicy:存储大小和伸缩的策略伸展策略:实质是枚举
QSizePolicy::Policy,可通过setSizePolicy()修改基于
sizeHint有不同的策略,以下称其为默认值Fixed:控件大小始终为默认值Minimun/Maximun:控件的默认值为最小/最大尺寸,大小可以大于/小于默认值最小/最大尺寸可通过
setMinimunSize()/setMaximunSize()设置Preferred:控件大小是默认值,但可以放大/缩小Expanding:控件可以随意放大/缩小当
Preferred和Expanding的控件同时存在于布局中时,前者会把空间让给后者且尽量保持默认值
伸展因子:一般通过布局来管理,伸展因子越大,拉伸控件时改变的空间越多
focusPolicy:存储获取焦点的策略,实质是枚举Qt::FocusPolicyStrongFocus:默认值,也最常用,可通过鼠标点击、Tab键获取焦点TabFocus/ClickFocus:Tab键/鼠标点击获取焦点NoFocus:不可通过键鼠获取焦点
geometry:不重要,一般通过布局来控制它
布局
Qt提供以下几种布局管理器:
QHBoxLayout/QVBoxLayout:水平/垂直布局QGridLayout:网格布局QFormLayout:表单布局
布局有助于控件随窗口拉伸而自动拉伸,以下操作可在QD快速实现:
- 窗口调用
setCentralWidget()设置中心控件 - 该中心控件调用
setLayout()设置布局
窗口
如要限制窗口的放大/缩小键,可通过setWindowFlags()设置,参数为枚举Qt::WindowType
WindowMaximunButtonHint/WindowMinimunButtonHint:最大化/最小化WindowCloseButtonHint:关闭键
更多常用方法
1 | QWidget: |