2021.5.25:QT——.ui文件
QT项目中,后缀为“.ui”文件时可视化设计的窗口的定义文件,如mainwindow.ui。双击项目文件目录树中的文件mainwindow.ui,会打开如下图所示的集成在Qt Creator中的Qt Designer对窗口进行可视化设计:
之后我们将这个集成在Qt Creator中的Qt Designer称为“UI 设计器”,以便与独立运行的Qt Designer区别开来。
- Signals、Slot编辑器和Action编辑器是位于窗体下方的两个编辑器;Signals、Slots编辑器用于可视化地进行信号与槽的关联,Action编辑器用于可视化设计Action
- 布局和界面设计工具栏:窗口上方的一个工具栏;按钮用于实现布局和界面设计
- 对象浏览器(Object Inspector):窗口右上方的编辑器;用树状视图显示窗口上各组件之间的布局包含关系,视图有两列,显示每个组件的对象名称和类名称
- 属性编辑器(Property Editor):窗口右下方的编辑器;界面设计时最常用的编辑器。属性编辑器显示某个选中的组件或者窗体的各种属性及其取值,可以在属性编辑器中修改这些属性的值。
下图显示的是选中窗体上放置的标签组件后属性编辑器的内容。最上方显示的文字“label:QLabel”表示这个组件是一个QLabel类的组件,对象名为label
属性编辑器的内容分为两列,分别为属性名和属性值。属性又分为多个组,实际上表示类的继承关系,上图中,可以看出QLabel的继承关系是QObject→Qmainwindow→QFrame→QLabel
属性名的修改在QObject下进行直接修改,界面上每个组件都需要一个唯一的对象名称,以便引用。
其他属性值的设置只需要在属性编辑器中操作即可,比如设置label的text属性为“Hello,World”,只需要修改QLabel下的text属性值就行了。
需要注意的是,标准C++语言中并没有property关键字,property是QT对标准C++的扩展,使得在QT Designer中就可以可视化设置类的数据。
在图1显示的ui中,放置一个Label与PushButton组件,它们的主要属性见下表:
ObjectName |
类名称 | 属性设置 | 备注 |
LabDemo | QLabel |
Text="Hello,World" Font.PointSize=20 Font.bold=true |
设置标签显示文字和字体 |
btnClose | QPushButton | Text="Close" | 设置按钮的文字 |
编辑完属性后,再为btnClose按钮添加一个功能,就是单击此按钮时,关闭窗口,退出程序。使用Signals和Slots编辑器(信号与槽编辑器)完成这个功能:
在信号与槽编辑器的工具栏上点击 +,在出现的条目中,设置Sender为btnClose,Signal选择clicked(),Receiver选择主窗体centralmainwindow,Slot选择close()。这样设置,表示当按钮btnClose被单击时,就执行主窗口的close函数,实现关闭窗口的功能。
然后对项目编译运行,可能出现如下图所示的窗口,单击“Close”就能关闭程序。
标签内的文字内容和字体被修改了,窗口标题也显示为所设置的标题,而我们并没有编写一行程序语句,QT是如何实现这些功能的呢?
为了搞清楚窗体类的定义,以及界面功能的实现原理,这里将项目进行编译。编译后在项目目录下会自动生成一个文件ui_mainwindow.h,这样对于一个窗体,就有4个文件了,各文件的功能说明见下表:
文件名 | 功能 |
mainwindow.h | 窗体类的头文件,定义了类mainwindow |
mainwindow.cpp | mainwindow类的功能实现源程序文件 |
mainwindow.ui | 窗体界面文件,由UI设计器自动生成,存储了窗体上各个组件的属性设置和布局 |
ui_mainwindow.h | 编译后,根据窗体上的组件及其属性、信号和槽的关联等自动生成的一个类的定义文件,类的名称是Ui_mainwindow |
下面分别分析各个文件的内容及其功能,以及它们是如何联系在一起工作,实现界面的创建与显示的。
mainwindow.h
mainwindow.h文件是窗体类的头文件。在创建项目时,选择窗体基类是QMainWindow,在mainwindow.h中定义了一个继承自QMainWindow的类MainWindow。
下面是widget.h文件的内容:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui {class MainWindow;} QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget * parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; }; #endif
mainwindow.h中有几个重要的部分:
namespace
namespace Ui{ class MainWindow; }
这里声明了一个名称为Ui的命名空间,包括一个class MainWindow。但是这个class并不是本文件中定义的class MainWindow,而是ui_mainwindow.h中定义的class,用于描述界面组件的。这个声明相当于一个外部类型声明。
MainWindow
本文件的主体部分是一个继承于QMainWindow的类MainWindow的定义,也就是本实例的窗体类。
在MainWindow类中使用了宏Q_OBJECT,这是使用QT的信号槽机制(Signals与Slots)的类必须加入的一个宏。
在public部分定义了Widget类的构造函数与析构函数。
在private部分又定义了一个指针
Ui :: MainWindow *ui;
这个指针是用前面声明的namespace Ui中的MainWindow类定义的,所以指针ui是指向可视化设计的界面,之后会看到将要访问界面上的组件,都要通过这个指针ui。
可视化设计时,在cpp文件中想要访问界面和界面上组件,需要用ui指针。
mainwindow.cpp
mainwindow.cpp是类MainWindow的实现代码,下面是这个文件的内容:
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; }
注意到,在这个文件的包含文件部分自动加入了如下一行的内容:
#include "ui_mainwindow.h"
这个就是QT编译生成的与UI文件mainwindow.ui对应的类定义文件。
目前只有构造函数与析构函数。其中构造函数头为:
MainWindow :: MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow)
其意义是:执行父类QMainWindow的构造函数,创建一个Ui::MainWindow的对象ui。这个ui就是Widget的private定义的指针变量ui。
构造函数中只有一句话:
ui->setupUi(this);
它是执行了Ui::MainWindow类的setupUi()函数,这个函数实现窗口生成、属性设置、信号与槽的关联。
析构函数只是简单地删除用new创建的指针ui。
所以,在ui_mainwindow.h文件中有一个namespace名称为Ui,里边有一个class MainWindow是用于描述可视化设计的窗体,且与mainwindow.h里定义的类同名。在MainWindow类里(cpp文件中)访问Ui::MainWindow类的成员变量或函数需要通过MainWindow类中的ui指针,如同构造函数里执行ui→setupUi(this)函数那样。
mainwindow.ui
ui文件是窗体界面定义文件,是一个XML文件,定义了窗口上的所有组件的属性设置、布局及其信号与槽函数的关联等。用UI设计器可视化设计的界面都由QT自动解析,并以XML文件的形式保存下来。在设计界面时,只需要在UI设计器中进行可视化设计即可,而不用管ui文件是如何生成的。
ui_mainwindow.h
该文件是在对mainwindow.ui文件编译后生成的一个文件,该文件会出现在编译后的目录下,或者与mainwindow.ui同目录(与项目的Shadow Build编译设置有关)。
文件ui_mainwindow.h并不会出现在QT Creator的项目文件目录树(左上角)中,当然,我们可以手工将该文件添加到项目中。方法是在目录树上边,右击项目名称节点,在调出的快捷菜单中选择“Add Existing Files ...”,找到并添加ui_mainwindow.h文件即可。
注意,ui_mainwindow.h是对mainwindow.ui文件编译后自动生成的,widget.ui又是通过UI设计器可视化设计自动生成的。所以,对ui_mainwindow.h进行手工修改没什么意义,所有涉及界面的修改都应该直接在UI设计器中进行。
查看这个文件的内容,发现它主要做了以下的一些工作:
- 定义了一个class Ui_MainWindow,用于封装可视化设计的界面;
- 自动生成了界面各个组件的类成员变量定义。在public部分为界面上每个组件定义了一个指针变量,变量的名称就是设置的objectName。比如,在窗体上放置了一个QLabel和一个QPushButton并命名后,自动生成的定义是:
QLabel * LabDemo; QPushButton * btnClose;
- 定义了setupUi()函数,这个函数用于创建各个界面组件,并设置其位置、大小、文字内容、字体等属性,设置了信号与槽的关联。setupUi()函数体的第一部分是根据可视化设计的界面内容,用C++代码创建界面上各组件,并设置其属性。接着,setupUi()调用了函数retranslateUi(MainWindow),用来设置界面各组件的文字内容属性,如标签的文字、按键的文字、窗体的标题等。将界面上的文字设置的内容独立出来作为一个函数retranslateUi(),在设计多语言界面时会用到这个函数。setupUi()函数的第三部分是设置信号与槽的关联,本文件中有以下两行:
QObject :: connect(btnClose,SIGNAL(clicked()),MainWindow,SLOT(close())); QMetaObject :: connectSlotsByName(MainWindow);
第一行是调用connect()函数,将在UI设计器中设置的信号和槽的关联转换为语句。这里是将btnClose按键的clicked()信号与窗体MainWindow的close()槽函数关联起来,这里就是我们之前在信号槽编辑器中添加的信号槽的关联的程序语句实现。这样,当点击btnClose按钮,就会执行MainWindow的close()槽函数,而这个槽函数的功能是关闭窗口。
第二行是设置槽函数的关联方式,用于将UI设计器自动生成的组件信号的槽函数与组件信号相关联。
所以,在MainWindow的构造函数中调用ui->setupUi(this),就实现了窗体上组件的创建、属性设置、信号与槽的关联。
- 定义namespace Ui,并定义一个从Ui_mainwindow继承的类mainwindow
namespace Ui{ class MainWindow : public Ui_MainWindow{}; }
ui_mainwindow.h中实现界面功能的类是Ui_MainWindow。再定义一个类MainWindow从Ui_MainWindow继承而来,并定义在namespace Ui中,这样Ui::MainWindow与mainwindow.h中的类MainWindow同名,所以要用namespace区分开来。所以,界面的Ui::MainWindow类与文件mainwindow.h中定义的MainWindow类实际上是两个类,但是QT的处理让用户感觉不到Ui::MainWindow类的存在,只需要知道在MainWindow类中用ui指针可以访问可视化设计的界面组件就可以了。