一.QT基础知识
1.控件与事件
1.控件
Qt 控件又称组件或者部件,指用户看到的所有可视化界面以及界面中的各个元素,比如按钮、文本框、输入框等。
为了方便程序员开发,Qt 提供了很多现成的控件。打开某个带 ui 文件的 Qt Widgets Application 项目,ui 文件的 Widget Box 一栏展示了 Qt 提供的几乎所有控件:
Qt 中的每个控件都由特定的类表示,每个控件类都包含一些常用的属性和方法,所有的控件类都直接或者间接继承自 QWidget 类。实际开发中,我们可以使用 Qt 提供的这些控件,也可以通过继承某个控件类的方式自定义一个新的控件。
Qt 中所有可视化的元素都称为控件,我们习惯将带有标题栏、关闭按钮的控件称为窗口。例如,下图展示了两种常用的窗口,实现它们的类分别是 QMainWindow 和 QDialog。
- QMainWindow 类生成的窗口自带菜单栏、工具栏和状态栏,中央区域还可以添加多个控件,常用来作为应用程序的主窗口;
- QDialog 类生成的窗口非常简单,没有菜单栏、工具栏和状态栏,但可以添加多个控件,常用来制作对话框。
刚开始在这里我还很疑惑,QMainWindow 类生成的窗口自带菜单栏、工具栏和状态栏在哪里?难道就是上面那一行吗,后来我查了资料,QMainWindow 类生成的窗口的菜单栏、工具栏和状态栏默认是不可见的,需要通过代码进行设置才能显示出来。一般情况下,我们在 QMainWindow 的构造函数中对这些控件进行初始化。
例如,在 QMainWindow 的构造函数中可以使用以下代码创建一个菜单栏并将其添加到 QMainWindow 中:
QMenuBar *menuBar = new QMenuBar(this);
setMenuBar(menuBar);
类似地,可以使用以下代码创建一个工具栏并将其添加到 QMainWindow 中:
QToolBar *toolBar = addToolBar("File");
最后,可以使用以下代码创建一个状态栏并将其添加到 QMainWindow 中:
QStatusBar *statusBar = new QStatusBar(this);
setStatusBar(statusBar);
窗口很少单独使用,它的内部往往会包含很多控件。例如图 中,我们分别往 MainWindow 和 Dialog 窗口中放置了一个按钮控件,根据需要还可以放置更多的控件。当窗口弹出时,窗口包含的所有控件会一同出现;当窗口关闭时,窗口上的所有控件也会随之消失。
实际开发中,制作应用程序的主窗口可以用 QMainWindow 或者 QWdiget;制作一个提示信息的对话框就用 QDialog 或 QWidget;如果暂时无法决定,后续可能作为窗口,也可能作为控件,就选择 QWidget。
2.事件
简单地理解,Qt 事件指的是控件和用户之间的交互过程,例如用户按下某个按钮,点击某个输入框等等。实际上除了用户会与应用程序进行交互外,操作系统也会与应用程序进行交互,例如当某个定时任务触发时,操作系统会关闭应用程序,这也是一个事件。
在上一篇博客中,在main.cpp中我没有解释的两行,其实是Qt 界面程序的 main() 主函数中首先要创建一个 QApplication 类的对象,函数执行结束前还要调用 QApplication 对象的 exec() 函数。一个 Qt 界面程序要想接收事件,main() 函数中就必须调用 exec() 函数,它的功能就是使程序能够持续不断地接收各种事件。
Qt 程序可以接收的事件种类有很多,例如鼠标点击事件、鼠标滚轮事件、键盘输入事件、定时事件等。每接收一个事件,Qt 会分派给相应的事件处理函数来处理。所谓事件处理函数,本质就是一个普通的类成员函数,以用户按下某个 QPushButton 按钮为例,Qt 会分派给 QPushButton 类中的 mousePressEvent() 函数处理。
还不能理解,看完下面的信号与槽,会好一些。
2.信号与槽
1.基础认识
信号和槽是 Qt 特有的消息传输机制,它能将相互独立的控件关联起来。
举个简单的例子,按钮和窗口本是两个独立的控件,点击按钮并不会对窗口造成任何影响。通过信号和槽机制,我们可以将按钮和窗口关联起来,实现“点击按钮会使窗口关闭”的效果。
在 Qt 中,用户和控件的每次交互过程称为一个事件,比如“用户点击按钮”是一个事件,“用户关闭窗口”也是一个事件。每个事件都会发出一个信号,例如用户点击按钮会发出“按钮被点击”的信号,用户关闭窗口会发出“窗口被关闭”的信号。
Qt 中的所有控件都具有接收信号的能力,一个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。例如,按钮所在的窗口接收到“按钮被点击”的信号后,会做出“关闭自己”的响应动作。在 Qt 中,对信号做出的响应动作就称为槽。
信号与槽这种机制,一般有四个参数考虑问题,分别是信号的发起方、发出什么信号(信号函数)、信号的执行方(接收信号并作出处理的目标)、执行的槽函数(做出怎么样的处理)。
信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如,“按钮被按下”这个信号可以用 clicked() 函数表示,“窗口关闭”这个槽可以用 close() 函数表示,信号和槽机制实现“点击按钮会关闭窗口”的功能,其实就是 clicked() 函数调用 close() 函数的效果。
信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:
- 信号函数用 signals 关键字修饰,槽函数用 public slots、protected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专门用来指明信号函数和槽函数;
- 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。
为了提高程序员的开发效率,Qt 的各个控件类都提供了一些常用的信号函数和槽函数。例如 QPushButton 类提供了 4 个信号函数和 5 个 public slots 属性的槽函数,可以满足大部分场景的需要。
实际开发中,可以使用 Qt 提供的信号函数和槽函数,也可以根据需要自定义信号函数和槽函数。
2.connect()函数实现信号和槽
connect() 是 QObject 类中的一个静态成员函数,专门用来关联指定的信号函数和槽函数。
关联某个信号函数和槽函数,需要搞清楚以下 4 个问题:
- 信号发送者是谁?
- 哪个是信号函数?
- 信号的接收者是谁?
- 哪个是接收信号的槽函数?
在 Qt5 版本之前,connect() 函数最常用的语法格式是:
QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
各个参数的含义分别是:
- sender:指定信号的发送者;
- signal:指定信号函数,信号函数必须用 SIGNAL() 宏括起来;
- reveiver:指定信号的接收者;
- method:指定接收信号的槽函数,槽函数必须用 SLOT() 宏括起来;
- type 用于指定关联方式,默认的关联方式为 Qt::AutoConnection,通常不需要手动设定。
第五个参数大多数情况下我们暂不考虑,所以使用connect时我们一般只输入四个参数。
connect() 函数将 But 按钮的 clicked() 信号函数和 widget 窗口的 close() 槽函数关联起来,实现代码如下:
connect(&But, SIGNAL(clicked()), &widget, SLOT(close()));
如此就实现了“按下按钮会关闭窗口”的功能。
Qt5 版本中,connect() 函数引入了新的用法,常用的语法格式是:
QObject::connect(const QObject sender, PointerToMemberFunction signal, const QObject receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
和旧版本相比,新版的 connect() 函数改进了指定信号函数和槽函数的方式,不再使用 SIGNAL() 和 SLOT() 宏。
例如,用新版 connect() 函数关联 But 按钮的 clicked() 信号函数和 widget 窗口的 close() 槽函数,实现代码为:
connect(&But, &QPushButton::clicked, &widget, &QWidget::close);
可以看到,新版 connect() 函数指定信号函数和槽函数的语法格式是&+函数所在类+函数名
。
一个 connect() 函数只能关联一个信号函数和一个槽函数,程序中可以包含多个 connect() 函数,能实现以下几种效果:
- 关联多个信号函数和多个槽函数;
- 一个信号函数可以关联多个槽函数,当信号发出时,与之关联的槽函数会一个接一个地执行,但它们执行的顺序是随机的,无法人为指定哪个先执行、哪个后执行;
- 多个信号函数可以关联同一个槽函数,无论哪个信号发出,槽函数都会执行。
此外,connect() 函数的 method 参数还可以指定一个信号函数,也就是说,信号之间也可以相互关联,这样当信号发出时,会随之发出另一个信号。
二.QT常用控件
1.QLabel
文本框
从字面上理解,QLabel 可以解释为“Qt 的 Label”,即 Qt 提供给我们的一种文本控件,它的基础功能是显示一串文本。除了显示一串文本外,QLabel 控件上还可以放置图片、超链接、动画等内容。比如我们上一篇博客实现图片查看器里面放置的就是图片。
下面是该类控件常用的属性:
属性 | 含义 |
---|---|
alignment | 保存 QLabel 控件中内容的对齐方式,默认情况下,QLabel 控件中的内容保持左对齐和垂直居中。可以通过调用alignment() 方法获取该属性的值,并可通过setAlignment() 方法进行修改。 |
text | 保存 QLabel 控件中的文本,如果 QLabel 控件中没有文本,则 text 的值为空字符串。可以通过调用text() 方法获取该属性的值,并可通过setText() 方法进行修改。 |
pixmap | 保存 QLabel 控件内显示的图片,如果控件内没有设置图片,pixmap 的值为 0。可以通过调用pixmap() 方法获取该属性的值,并可通过setPixmap() 方法进行修改。 |
selectedText | 保存 QLabel 控件中被选择了的文本,当没有文本被选择时,selectedText 的值为空字符串。可以通过调用selectedText() 方法获取该属性的值。 |
hasSelectedText | 判断用户是否选择了 QLabel 控件内的部分文本,如果是则返回 true,反之则返回 false。默认情况下,该属性的值为 false。 |
indent | 保存 QLabel 控件内文本的缩进量,文本的缩进方向和 alignment 属性的值有关。可以通过调用indent() 方法获取该属性的值,并可通过setIndent() 方法进行修改。 |
margin | 保存 QLabel 控件中内容与边框之间的距离(边距),margin 的默认值为 0。margin 的默认值为0。可以通过调用margin() 方法获取该属性的值,并可通过setMargin() 方法进行修改。 |
wordWrap | 保存 QLabel 控件内文本的换行策略。当该属性的值为 true 时,控件内的文本会在必要时自动换行。默认情况下,控件内的文本是禁止自动换行的。可以通过调用wordWrap() 方法获取该属性的值,并可通过setWordWrap() 方法进行修改。 |
除了上表中提到了获取和修改属性值得成员方法外,下表给大家罗列了一些常用的操作 QLabel 控件的成员方法,它们有些定义在 QLabel 类内,有些是通过继承父类得到的:
成员方法 | 功能 |
---|---|
hide() | 隐藏文本框。 |
clear() | 清空QLabel控件内所有显示的内容。 |
setToolTip(QString) | 设置信息提示,当用户将鼠标悬停在QLabel文本框上时会自动显示文字提示。 |
setToolTipDuration(int) | 设置提示信息显示的持续时间,单位为毫秒。 |
setStyleSheet(QString) | 设置QLabel文本框的样式。 |
setGeometry(int x, int y, int w, int h) | 设置QLabel文本框的位置(x,y)和尺寸(w,h)。把这一段文字和上面一样处理。 |
QLabel 控件只用来显示文本、图像等内容,很好与用户交互。但是,当 QLabel 控件内包含超链接内容时,可以使用 QLabel 类提供的两个信号函数:
信号函数 | 功 能 |
---|---|
linkActivated(const QString &link) | 用户点击超链接时触发,link 参数用于向槽函数传输超链接的 URL。 |
linkHovered(const QString &link) | 用户的鼠标悬停到超链接位置时触发,link 参数用于向槽函数传输超链接的 URL。 |
QLabel 控件提供了很多槽函数,如下表所示:
槽函数 | 功 能 |
---|---|
clear() | 清空 QLabel 控件内所有的内容。 |
setMovie(QMovie *movie) | 清空 QLabel 控件内所有的内容,改为显示指定的 movie 动画。 |
setNum(int num) | 清空 QLabel 控件内所有的内容,改为显示 num 整数的值。 |
setNum(double num) | 清空 QLabel 控件内所有的内容,改为显示 num 小数的值。 |
setPicture(const QPicture &picture) | 清空 QLabel 控件内所有的内容,改为显示经 QPicture 类处理的图像。 |
setPixmap(const QPixmap &) | 清空 QLabel 控件内所有的内容,改为显示经 QPixmap 类处理的图像。 |
setText(const QString &) | 清空 QLabel 控件内所有的内容,改为显示指定的文本。 |
2.QPushButton
按钮
QPushButton 类间接继承自 QWidget 类,它的继承关系如下:QPushButton -> QAbstractButton -> QWidget
。QAbstractButton 类是所有按钮控件类的基类,包含很多通用的按钮功能。
QPushButton 按钮上除了可以放置一串文本,文本左侧还可以放置图标,必要时还可以在按钮上放置图片。
QPushButton 类提供了 3 个构造函数,分别是:
QPushButton(QWidget *parent = Q_NULLPTR)
QPushButton(const QString &text, QWidget *parent = Q_NULLPTR)
QPushButton(const QIcon &icon, const QString &text, QWidget *parent = Q_NULLPTR)
parent 参数用于指定父窗口;text 参数用于设置按钮上要显示的文字;icon 参数用于设置按钮上要显示的图标。
QPushButton 类提供了很多实用的属性和方法,它还从父类继承了很多属性和方法。下表给大家罗列了一些比较常用的属性和方法:
属 性 | 含 义 |
---|---|
text | 保存按钮上要显示的文字。 该属性的值可以通过 text() 方法获取,也可以通过 setText(const QString &text) 方法修改。 |
icon | 保存按钮左侧要显示的图标。 该属性的值可以通过 icon() 方法获取,也可以通过 setIcon(const QIcon &icon) 方法修改。 |
iconsize | 保存按钮左侧图标的尺寸。 该属性的值可以通过 iconSize() 方法获取,也可以通过 setIconSize(const QSize &size) 方法修改。 |
size | 保存按钮的尺寸。 该属性的值可以通过 size() 方法获取,也可以通过 resize(int w, int h) 或者 resize(const QSize &) 方法修改。 |
font | 保存按钮上文字的字体和大小。 该属性的值可以通过 font() 方法获取,也可以通过 setFont(const QFont &) 方法修改。 |
flat | 初始状态下,按钮是否显示边框。flat 属性的默认值为 flase,表示按钮带有边框。 该属性的值可以通过 isFlat() 方法获取,也可以通过 setFlat(bool) 方法修改。 |
enabled | 指定按钮是否可以被按下。 该属性的默认值为 true,表示按钮可以被按下,即按钮处于启用状态。当该属性的值为 false 时,按钮将不能被点击,按钮处于禁用状态。 该属性的值可以通过 isEnabled() 方法获取,也可以通过 setEnabled(bool) 方法进行修改。 |
autoDefault | 当用户按下 Enter 回车键时,是否触发点击按钮的事件。 当按钮的父窗口为 QDialog 窗口时,该属性的值为 true;其它情况下,该属性的默认值为 false。 该属性的值可以通过 autoFault() 方法获取,也可以通过 setAutoFault(bool) 方法修改。 |
UI 程序中,按钮的主要任务是完成和用户之间的交互,下表罗列了 QPushButton 类常用的信号函数和槽函数:
信号函数 | 功 能 |
---|---|
clicked() clicked(bool checked = false) | 用户点击按钮并释放(或者按下按钮对应的快捷键)后,触发此信号。 |
pressed() | 用户按下按钮时会触发此信号。 |
released() | 用户松开按钮时会触发此信号。 |
槽函数 | 功 能 |
click() | 单击指定的按钮。 |
setIconSize() | 重新设置按钮上图片的尺寸。 |
hide() | 隐藏按钮控件。 |
setMenu(QMenu *menu) | 弹出与按钮关联的菜单。 |
3.QLineEdit
单行输入框
QLineEdit 是 Qt 提供的一个控件类,它直接继承自 QWdiget 类,专门用来创建单行输入框。
下表列出了 QLineEdit 类对象经常调用的一些属性以及它们各自的含义:
属 性 | 含 义 |
---|---|
text | 保存输入框中的文本。 该属性的值可以通过 text() 方法获取,也可以通过 setText(const QString &) 方法修改。 |
maxLength | 设置输入框中最多可以放置的文本长度。当文本长度超出最大限度后,超出部分将被丢弃。 默认情况下,maxLength 的值为 32767。该属性的值可以通过 maxLength() 函数获得,也可以通过 setMaxLength(int) 方法修改。 |
placeholderText | 设置提示信息,例如当用户未选中输入框时,输入框中显示“请输入...”,而用户选中输入框时,"请输入..." 随之消失。 该属性的值可以通过 placeholderText() 方法获取,也可以通过 setPlaceholderText(const QString &) 方法修改。 |
clearButtonEnabled | 当输入框中有文本时,输入框的右侧可以显示一个“一键清除”按钮。该属性的默认值为 false,即输入框中不会自动显示清除按钮。 该属性的值可以通过 isClearButtonEnabled() 方法获取,也可以通过 setClearButtonEnabled(bool enable) 方法修改。 |
echoMode | 设定输入框中文本的显示样式,该属性的可选值有以下几个:QLineEdit::Normal:正常显示所输入的字符,此为默认选项。QLineEdit::NoEcho:不显示任何输入的字符,常用于密码类型的输入,且长度保密QLineEdit::Password:显示与平台相关的密码掩饰字符,而不是实际输入的字符。当用户重新点击输入框时,可以紧接着之前的文本继续输入。QLineEdit::PasswordEchoOnEdit:编辑时正常显示输入的字符,编辑完成后改为用密码掩饰字符显示。当用户重新点击输入框时,不能紧接着之前的文本继续输入。 该属性的是可以通过 echoMode() 方法获取,也可以通过 setEchoMode(EchoMode) 方法修改。 |
frame | 控制输入框的边框。默认情况下,输入框是带有边框的。 该属性的值可以通过 hasFrame() 方法获取,也可以通过 setFrame(bool) 方法修改。 |
QLineEdit 类提供了几个信号函数,分别对应用户的几种输入状态。
信号函数 | 功 能 |
---|---|
textEdited(const QString &text) | 当用户编辑输入框中的文本时,此信号就会触发,text 参数即为用户新编辑的文本。 注意,当程序中试图通过 setText() 方法修改输入框中的文本时,不会触发此信号函数。 |
textChanged(const QString &text) | 只要输入框中的文本内容发生变化,就会触发此信息。 |
returnPressed() | 用户按下回车键时,会触发此信号。 |
editingFinished() | 用户按下回车键,或者鼠标点击输入框外的其它位置时,会触发此信号。 |
QLineEdit 类常用的槽函数有以下几个:
槽函数 | 功 能 |
---|---|
clear() | 清空文本框中的内容。 |
setText(const QString &) | 重新指定文本框中的内容。 |
这里有很多控件,我们都需要掌握,我就不一一列举,我们要用什么我们就去学什么。
三.其他特殊控件
1.布局管理
实际开发中,一个界面上可能包含十几个控件,手动调整它们的位置既费时又费力。作为一款成熟的 GUI 框架,Qt 提供了很多摆放控件的辅助工具(又称布局管理器或者布局控件),它们可以完成两件事:
- 自动调整控件的位置,包括控件之间的间距、对齐等;
- 当用户调整窗口大小时,位于布局管理器内的控件也会随之调整大小,从而保持整个界面的美观。
总之借助布局管理器,我们无需再逐个调整控件的位置和大小,可以将更多的精力放在软件功能的实现上。
Qt 共提供了 5 种布局管理器,每种布局管理器对应一个类,分别是 QVBoxLayout(垂直布局)、QHBoxLayout(水平布局)、QGridLayout(网格布局)、QFormLayout(表单布局)和 QStackedLayout(分组布局),它们的继承关系:
垂直布局:
程序中使用 QVBoxLayout 布局控件,需提前引入<QVBoxLayout>
头文件。每个 QVBoxLayout 控件本质都是 QVBoxLayout 类的实例对象,该类提供了两个构造函数,分别是:
QVBoxLayout()
QVBoxLayout(QWidget *parent)
创建 QVBoxLayout 控件的同时可以指定父窗口,那么它将作为父窗口中管理其它控件的工具;也可以暂时不指定父窗口,待全部设置完毕后再将其添加到某个窗口中。
事实上,我们无论使用什么控件都要导入对应的头文件,所以最好的办法就是一次性导入所有控件。
QVBoxLayout 类没有新增任何成员方法,它只能使用从父类继承的成员方法,下表给大家罗列了常用的一些:
成员方法 | 功 能 |
---|---|
void QBoxLayout::addWidget(QWidget *widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()) | 向布局管理器中添加指定的 widget 控件。 默认情况下,stretch 拉伸系数为 0,表示 widget 控件的尺寸为默认值;alignment 是一个枚举类型参数,默认值也是 0,表示该控件会填满占用的整个空间。 |
void QBoxLayout::addStretch(int stretch = 0) | 添加一个空白行,整个窗口中除了控件占用的区域外,其它区域可以由多个(≥0)空白行分摊,分摊比例取余于各个空白行设置的 stretch 参数的值。 strech 参数的默认值为 0,表示当窗口很小时,空白行可以不占据窗口空间。当窗口中包含多个 strech 值为 0 的空白行时,它们会平分窗口中的空白区域。 |
void QBoxLayout::addSpacing(int size) | 添加一个 size 大小的固定间距。 |
void QLayout::setMargin(int margin) | 设置布局管理器中所有控件的外边距,上、下、左、右外边距的大小都为 margin。默认情况下,所有方向的外边距为 11 px。 |
void QLayout::setContentsMargins(int left, int top, int right, int bottom) | 设置布局管理器中所有控件的外边距,和 setMargin() 的区别是,此方法可以自定义上、下、左、右外边距的值。 |
void QBoxLayout::setDirection(Direction direction) | 设置布局管理器中控件的布局方向,Direction 是一个枚举类型,对于 QVBoxLayout 布局管理器,direction 参数的值通常选择 QBoxLayout::TopToBottom(从上到下依次摆放)或者 QBoxLayout::BottomToTop(从下到上依次摆放)。 |
bool QBoxLayout::setStretchFactor(QWidget *widget, int stretch) | 设置布局管理器中某个控件的拉伸系数。 |
bool QBoxLayout::setStretchFactor(QLayout *layout, int stretch) | 布局管理器内部可以再放置一个布局管理器,该方法用来设置内部某个布局管理器的拉伸系数。 |
2.项目文件
在上一篇的博客中,我只是简单说了一下.prp项目文件,今天在这里详细解释一下每一句代码:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = Demo
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
打开一个空的QT项目文件,你的.pro项目文件的内容应该是上面这样的
四.解析上篇(计算器)代码
1.创建按钮
第一种创建方式,不修改.h头文件:
#include "widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(500,800); //设置窗口属性
QPushButton *BUTTON = new QPushButton(this); //定义一个按钮,用指针
BUTTON->setText("="); //修改按钮的属性
BUTTON->move(100,500);
}
Widget::~Widget()
{
}
- 1.导入按钮的头文件
- 2.定义一个按钮
- 3.修改一个按钮的属性
五.初学令人懵逼的bug
1.中文乱码问题
该问题你在其他地方肯定也遇到过,原因你也了解,编码方式不支持中文,所以你应该修改编码方式: