GUI应用程序
UI文件设计与运行机制
在VS2019中新建一个QT Widgets项目(如果有GUI项目可以创建GUI项目),选择QWidget作为窗体基类。
用VS创建的项目和QT Creater创建的项目文件目录稍有不同,下面来看看各个文件:
项目管理文件
后缀为.pro的文件是项目管理文件,用于记录一些设置。VS创建的项目没有.pro文件,这个步骤在VS创建项目的选项中就已经设置过了。
在这个文件中是可以后期添加各种QT模块的,在VS中如果要添加需要在扩展->Qt VS Tools->Qt Project Settings中设置。
界面文件
后缀为.ui的文件是可视化设计的窗体的定义文件,双击这个文件会打开Qt Designer对窗体进行可视化设计。
这个UI设计器具有以下功能区:
- 左侧的Widget Box组件面板,界面设计的常见组件都可以在这里找到。
- 中间区域是待设计的窗体。如果需要将某个组件放置到窗体上,直接从组件面板拖放即可。
- 信号与槽编辑器和动作编辑器位于右下方,信号/槽编辑器用于可视化的进行信号与槽的关联,动作编辑器用于可视化设计动作。
- 对象查看器:在右上方,用树状视图显示窗体上各个组件之间的布局包含关系。视图有两列,显示每个组件的对象名称和类名称。
- 属性编辑器:在对象查看器下面,是界面设计常用到的编辑器。属性编辑器显示某个选中的组件或窗体的各个属性及其取值,可以在这里修改属性值。下面详细介绍这个编辑器:
在窗体上添加一个lable,点击这个lable观察这个属性编辑器。最上方显示的文字“lable:QLable”表示这个组件是一个QLable类型的组件。属性编辑器分为两列,分别为属性的名称和属性的值。可以看到属性分为了好几个组,其实这个分组表明了类的继承关系,QLable的继承关系是:“QObject”➡“QWidget”➡“QFrame”→“QLable”。
QObjectName表明了这个组件对象的名称,界面上每个组件都需要唯一的对象名称以便被引用。
设置其他属性值只需在这个编辑器内直接修改即可。
我们还可以添加一个按钮,让这个按钮点击后可以推出窗口。拖拽一个push button到窗口,在信号与槽工具栏中单击“➕”,在出现的条目中,发送者选择这个按钮,信号选择clicked(),接收者选择这个窗体,槽选择close(),这样设置表示按钮点击后执行Widget的close()函数。
主函数文件
main.cpp是实现main()函数的文件,内容如下:
#include "samp2_1.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv); //定义并创建应用程序
samp2_1 w; //定义并创建窗口
w.show(); //显示窗口
return a.exec(); //应用程序运行
}
窗体相关文件
头文件
在VS中项目的头文件和QT Creater中不太一样。简化了很多东西,但是其余必须的东西都没有变。
#pragma once
#include <QtWidgets/QWidget>
#include "ui_samp2_1.h"
class samp2_1 : public QWidget
{
Q_OBJECT
public:
samp2_1(QWidget *parent = Q_NULLPTR);
private:
Ui::samp2_1Class ui;
};
Q_OBJECT这个宏很重要,这是使用Qt的信号槽机制都必须使用的一个宏。
剩下的就是构造函数
cpp文件
#include "samp2_1.h"
samp2_1::samp2_1(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
}
cpp文件实现了构造函数。构造函数只有一行语句ui->setupUi(this)它是执行了Ui:Widget类的setupUi()函数,这个函数实现窗口的生成与各种属性的设置、信号与槽的关联。
ui_项目名称.h文件
这个文件是对.ui文件编译生成后的一个文件,这个文件并不会自动添加到目录树中,可以手动添加现有项。这个文件并不需要我们手动修改,所有UI操作可以直接在QT Designer中设置,我们只是看看他做了什么操作:
- 定义自身的类,用于封装可视化设计的界面。
- 自动生成界面各个组件的成员变量定义。在public部分为每个组件都定义了一个指针变量,这个变量就是设置的变量名称objectName.
- 定义setupUi()函数。这个函数用于创建各个界面组件,并设置属性,设置信号与槽的关联。
- 定义namespace Ui,并定义一个从自身继承的类。
可视化UI设计
界面属性设置需要注意:
- objectName是窗体上创建的组件的实例名称,界面上每个组件都需要有一个唯一的objectName,程序访问界面组件都是通过其objectName访问的,自动生成的槽函数名称里也有objectName,所以组件的objectName需要在设计程序之前就设置好,之后不要改动。
- 不要在UI设计器里修改窗体的objectName,窗体的实例名称需要在使用窗体的代码中定义。
界面组件布局
QT的界面设计使用了布局(Layout)功能。布局就是组件的排列方式。使用布局可以使组件规则分布,并且随着窗体大小调整自身。
界面组件层次
我们会使用一些容器类,如QgoupBox、QtabWidget、QFrame等。可以把组件都放进一个容器中,移动这个容器将会移动内部所有组件。对象查看器中显示了各个组件之间的关系。
布局管理
UI设计器中,组件面板里有Layouts和Spacers两个组件面板,窗体上方的工具栏中也有布局管理按钮。
Layouts和spacers功能表:
布局组件 | 功能 |
---|---|
Vertical Layout | 垂直方向布局 |
Horizontal Layout | 水平方向布局 |
Grid Layout | 网格状布局,布局大小改变,每个网格大小都改变 |
Form Layout | 窗体布局,与网格布局相似,但是只有最右侧的网格改变大小 |
Horizontal Spacer | 一个用于水平分隔的空格 |
Vertical Spacer | 一个用于垂直分隔的空格 |
在设计窗口上方有个工具栏,用于调整计时器进入不同状态,以及进行布局设计,功能如下:
按钮 | 功能 |
---|---|
编辑窗口(F3) | 界面设计进入编辑状态,就是正常的设计状态 |
编辑信号/槽(F4) | 进入信号与槽的可视化设计状态 |
编辑伙伴 | 进入伙伴编辑状态,可以设置一个label与一个组件成为伙伴关系 |
编辑tab顺序 | 进入Tab顺序编辑状态,Tab顺序是在键盘上按Tab键时输入在焦点界面各组件之间的跳动顺序 |
水平布局(ctrl+H) | 将窗体上所选组件水平布局 |
垂直布局(ctrl+L) | 将窗体上所选组件垂直布局 |
使用分裂器水平布局 | 将窗体上所选组件用一个分割条进行水平分割布局 |
使用分裂器垂直布局 | 将窗体上所选组件用一个分割条进行垂直分割布局 |
栅格布局 | 将窗体上所选组件按网格布局 |
窗体布局 | 将窗体上所选组件按照窗体布局 |
打破布局 | 解除窗体上所选组件的布局 |
调整大小 | 自动调整所选组件的大小 |
使用工具栏布局按钮,只需要在窗体上选中需要设计布局的组件,然后点击某个布局按钮即可。
在窗体上选择组件时按住ctrl键可以实现组件多选。
选择某个容器类,相当于选择其中的所有组件。
我们还可以为整个窗体选择布局。不选中任何组件,选中窗体,点击工具栏中 的布局按钮。当窗体大小改变时,各个组件都会相应改变大小
伙伴关系与Tab
点击工具栏编辑伙伴会进入伙伴编辑状态。
伙伴关系指界面上一个Label和一个组件相关联。在伙伴编辑状态下,单击一个label,按住鼠标左键拖向一个组件,就建立了这个label和另一个组件之间的伙伴关系。
伙伴关系是为了在运行程序时,在窗体上用快捷键将焦点切换到某个组件上。
点击编辑Tab顺序进入Tab顺序编辑状态。Tab顺序是指在程序运行时,按键盘Tab键时焦点的移动顺序。
信号与槽
信号与槽是Qt编程的基础。
- 信号(Signal)就是在特定情况下被发射的事件。GUI程序设计的主要内容就是对界面上各个组件的信号的响应。我们只需要知道什么情况下发射哪些信号,然后去响应这些信号就可以了。
- 槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般C++函数是一样的,可以定义在类的任何部分,可以具有任何参数。也可以被直接调用。
信号与槽的关联是用==QObject::connect()==函数实现的:
QObject::connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
参数分别为发射信号的对象;信号函数;接收信号的对象;槽函数。
SIGNAL和SLOT是Qt的宏,用于指明信号和槽,并将它们的参数转化为相应的字符串。假设我们给按钮添加信号,点击按钮关闭窗口,我们在setupUi()函数中可以看到如下语句:
QObject::connect(pushButton, SIGNAL(clicked()), QWDialogClass, SLOT(close()));
有关信号与槽,有以下注意事项:
- 当信号和槽函数带有参数时,connect()函数要写明参数类型,但是可以不屑=写参数名称;
- 一个信号可以连接多个槽。即一个信号可以触发多个响应。当一个信号与多个槽相关联的时候,槽函数按照建立时连接的顺序依次执行;
- 多个信号可以连接同一个槽。
- 一个信号可以连接另一个信号。当一个信号发射时,也会发射另一个信号,实现 某些特殊功能;
- 信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数;
- 使用信号和槽,必须加入QObject宏;
- 当一个信号被发射,与其关联的槽函数立即被执行,只有槽函数执行完毕后才会继续执行发射信号处后面的代码。
在VS的QT Designer,功能并没有QT Creater齐全,我们要添加自定义的信号和槽还是需要我们自己去手写。并在QWDialog的构造函数中手工进行关联。
我们在QT Designer添加了系统的槽函数,但是发现我们的构造函数还是仅仅只有一条setupUi()语句。没有看到connect操作。我们去包含这个setupUi()函数的ui_xxxx.h文件也没有看到connect操作。但是有一条关键语句:
QMetaObject::connectSlotsByName(QWDialogClass);
这个函数将搜索QWDialog界面上所有的组件,将信号和槽关联起来。这就是用UI设计器可视化设计某个组件的响应信号槽函数,而不用手工将其关联起来的原因。都是在类界面的构造函数中调用setupUi()自动完成了关联。
代码化UI设计
界面设计的底层都是C++代码实现的,底层实现的功能比可视化设计更强大和灵活。
我们自己写一个界面,点击按钮关闭窗口。
在头文件中创建界面
#pragma once
//源码必须保存成带BOM的UTF-8格式,不然会有中文乱码
#pragma execution_character_set("utf-8")
#include <QtWidgets/QDialog>
#include "ui_QWDlgManual.h"
#include<qpushbutton.h>
class QWDlgManual : public QDialog
{
Q_OBJECT
public:
QWDlgManual(QWidget *parent = Q_NULLPTR);
private:
Ui::QWDlgManualClass ui;
QPushButton *btn; //创建按钮
void iniUI(); //UI创建与初始化
void iniSignalSlots(); //初始化信号与槽的链接
};
界面的创建,以及信号与槽函数的关联都在构造里完成
QWDlgManual::QWDlgManual(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
iniUI(); //创建界面与布局
iniSignalSlots(); //信号与槽的关联
setWindowTitle("Form created mannually");
}
在iniUI()函数中实现界面组件的创建,如果有布局也在这里创建。下方的iniSignalSlots()函数连接信号和槽:
void QWDlgManual::iniUI()
{
// //创建按钮
btn = new QPushButton("关闭",this);
btn->setFixedSize(100, 100);
}
void QWDlgManual::iniSignalSlots()
{
connect(btn,SIGNAL(clicked()),this,SLOT(close()));
}