2021.5.25:QT——界面布局管理详解
在上一节中,通过一个简单的应用程序,分析了QT创建的GUI应用程序中各个文件的作用,剖析了可视化设计的UI文件是如何被转换为C++的类定义,并自动创建界面的,这些是使用QT Creator可视化设计用户界面,并使各个部分融合起来运行的基本原理。
本节以一个稍微复杂的例子来讲解设计GUI的常见功能,包括界面设计时布局的管理,以及程序中如何访问界面组件。
实例程序功能
创建一个Widget Application的项目samp2_2,在创建窗体时选择基类QDialog,生成的类命名为QDialog,并选择生成窗体。

如此新建的项目samp2_2有一个界面文件dialog.ui,一个头文件dialog.h和dialog.cpp。此外,还有项目文件samp2_2.pro和主程序文件main.cpp。
dialog.ui界面设计如下图所示。程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置:

在界面设计时,对需要访问的组件修改其ObjectName,如各个按钮,需要读取输入的编辑框,需要显示结果的标签等,以便在程序里区分。对于不需要程序访问的组件则无需修改其ObjectName,如用于界面上组件分组的GroupBox、Frame、布局等,让UI设计器自动命名即可。
对图1中几个主要组件的命名、属性设置见下表。
| 对象名 | 类名称 | 属性设置 | 备注 |
| txtEdit | QPlainTextEdit |
Text="Hello,World It is my demo." Font.PointSize=20 |
可编辑文本框内容 |
| chkBoxUnder | QCheckBox | Text="Underline" | 下换线 |
| chkBoxItalic | QCheckBox | Text="Italic" | 斜体 |
| chkBoxBold | QCheckBox | Text="Bold" | 粗体 |
| rBtnBlack | QRadioButton | Text="Black" | 黑色 |
| rBtnRed | QRadioButton | Text="Red" | 红色 |
| rBtnBlue | QRadioButton | Text="Blue" | 蓝色 |
| btnOK | QPushButton | Text="确定" | 返回确定,并关闭窗口 |
| btnCancel | QPushButton | Text="取消" | 返回取消 ,并关闭窗口 |
| btnClose | QPushButton | Text="退出" | 退出程序 |
| QWDialog | QWDialog | windowTitle="Dialog by Designer" | 界面窗口类,它的ObjectName不需要修改 |
对于界面组件的属性设置,需要注意以下几点:
- ObjectName是窗体上创建的组件的实例名称。每个组件都有一个唯一的ObjectName,在程序中访问界面组件时都是通过其ObjectName进行访问的,自动生成的槽函数名称中也有ObjectName。所以,组建的ObjectName需要在设计程序之前设置好,而且设置好之后就不要再改动了。如果程序设计完成后再改动ObjectName,涉及到的代码均需要修改,这一点很不方便!
- Window的ObjName就是它的类名,我们不需要修改窗体Window的ObjName。
界面组件布局
QT的界面设计使用了布局(Layout)功能。所谓布局,就是界面上组件的排列方式,使用布局可以使组件有规则地分布,并且随着窗体大小的变化自动地调整大小和相对位置。布局管理是GUI设计必备技巧,下面逐步讲解如何实现之前图中所示的界面设计
界面组件的层次关系
为了将界面上各个组件的分布设计得更加美观,经常使用一些容器类,例如QGroupBox、QTabWidget、QFrame等。
例如,将3个CheckBox放在一个GroupBox组件中,那么这个GroupBox组件就是这3个CheckBox的容器,移动这个GroupBox就会同时移动其中的3个CheckBox。

上图所示是本例设计的前期阶段。在窗体上有2个GroupBox,第一个GroupBox中有3个CheckButton,第二个GroupBox中有3个RadioButton。右侧的对象编辑器(Obj Inspector)中显示了各组件间的层次关系。
布局管理
QT为界面设计提供了丰富的布局管理功能,在UI设计器中,有两个组件/容器相关的组件面板——Layouts、Spacers,在窗体上方的工具栏中有布局管理的按钮:

Layouts和Spacers两个组件面板中的布局组件的功能见下表:
| 布局组件 | 功能 |
| Vertical Layout | 垂直方向布局——组件自动在垂直方向上分布 |
| Horizontal Layout | 水平方向布局——组件自动在水平方向上分布 |
| Grid Layout | 网格状布局——布局大小改变时,每个网格的大小都会改变 |
| Form Layout | 窗体布局——与网格状类似,但是最右侧一列的网格会改变大小 |
| Horizontal Spacer | 一个用于水平分隔的空格 |
| Vertical Spacer | 一个用于垂直分隔的空格 |
设计窗体上的布局工具栏为
,功能是布局设计,工具栏上各按钮的功能见下表:
| 按钮及快捷键 | 功能 |
| Edit Widget(F3) | 界面进入普通编辑状态,就是正常的设计状态 |
| Edit Signals/Slots(F4) | 进入信号与槽的可视化设计状态 |
| Edit Buddies | 进入伙伴关系编辑状态,可以设置一个Label与一个组件绑定 |
| Edit Tad Order | 进入Tab顺序编辑状态,Tab顺序是指在键盘上按Tab键时,焦点在各组件之间来回切换 |
| LayOut Horizontally(CTRL+H) | 将窗体上所选组件水平布局 |
| LayOut Vertically(CTRL+L) | 将窗体上所选组件垂直布局 |
| LayOut Horizontally in Splitter | 将窗体上所选组件用一个分隔条进行水平分割布局 |
| LayOut Vertically in Splitter | 将窗体上所选组件用一个分隔条进行垂直分隔布局 |
| LayOut in a Form Layout | 将所选组件按窗体布局 |
| LayOut in a Grid | 将所选组件按网格布局 |
| Break Layout | 解除所选组件的布局 |
| Adjust Size(CTRL+J) | 自动调整所选组件大小 |
使用上述工具栏布局按钮时,只需要在窗体中选中要设计布局的组件,然后点击某个布局按钮就可以了。如果选择了某个容器组件,就相当于同时选择了其内部的所有组件。
例如,在上图中,选中GroupBox1,再点击某个布局按钮,就可以同时对groupbox1中的3个CheckBox组件进行布局。
在上图中,布局安排为:
- groupbox1中的3个CheckBox水平布局;
- groupbox2中的3个RadioBox水平布局;
- 最下方的三个按钮水平布局;
- 在窗体上放置一个PlainTextEdit组件;
改变groupbox1、groupbox2或按钮的水平布局的大小,其内部组件都会自动改变大小;但是改变窗体大小时,界面上的各个组件却不会自动改变大小。
随后还需要为窗体指定一个总的布局:
- 选中窗体(不需要选中任何组件),单击工具栏上的“LayOut Vertically”按钮,使4个组件垂直分布;
这样,当窗体大小改变时,各个组件都会自动改变大小。
在UI设计器中可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度、高度来实现某些需要的布局效果。
伙伴关系与Tab顺序
在UI工具栏上单击“Edit Bubbies”按钮可以进入伙伴关系编辑状态,在设计一个窗体时,进入伙伴关系编辑状态之后的界面如下图所示:

伙伴关系(Bubble)是指界面上的一个Label与一个组件相关联,如上图的伙伴关系编辑状态,单击一个Label,按住鼠标左键,然后拖向一个组件,就建立了Label和组件之间的伙伴关系。
伙伴关系的作用:程序运行时,在窗体上用快捷键将输入焦点切换到某个组件上。
例如,在上图的界面上,设定“姓名”标签的Text属性为“姓名(&N)”,其中符号“&”用来指定快捷字符,界面上并不显示“&”,这里的作用是,指定快捷键为N。程序运行过程中,当用户按下ALT+N,输入焦点就会快速切换到“姓名”关联的输入框中。
在UI设计器工具栏上单击“Edit Tab Order”按钮进入Tab顺序编辑状态,如下图所示。

Tab顺序是指:程序运行时,按下Tab键焦点的移动顺序。
一个好的用户界面,在按Tab键时,焦点应该以合理的顺序在界面上移动,而不是随意的移动。
进入Tab顺序编辑状态后,在界面上回显示具有Tab顺序的组件编号,依次按希望的顺序单击编号,就可以重排Tab顺序了。
没有输入焦点的组件是没有Tab顺序的,比如Label组件。
功能实现
下面开始设计程序的功能,对于改程序,希望它的功能如下:
- 单击UnderLine、Italic、Bold 3个CheckBox之后,根据其状态,设置PlainTextEdit中的文字的字体样式;
- Black、Red、Blue 3个RadioButton是互斥选择的,单击某个RadioButton时,设置文字的颜色;
- 单击“确认”、“取消”、“退出”按钮时,关闭窗口,退出程序。
字体样式设计
设计模式下,选中与下划线UnderLine相关的CheckBox,即chkBoxUnder,单击右键调出快捷菜单,在快捷菜单中单击菜单项“Go to slot...”(转到槽),出现下图所示对话框:

该对话框列出了QCheckBox类的所有信号,有两个是clicked()与clicked(bool);
clicked(bool)会将当前选择状态作为一个参数传递,在相应代码中可以直接利用这个传递的参数;
clicked()则需要则需要在代码中自己写读取状态的代码。
换言之,clicked()只是点击了这个组件的响应,clicked(bool)是点击组件并改变选中状态的响应。为了简化代码,选择clicked(bool)信号。
选择clicked(bool)信号之后,在dialog.h的QWDialog的类定义中,会在private slots部分自动增加一个槽函数声明,函数名是根据信号源和信号名自动命名的:
void on_chkBoxUnder_clicked(bool checked);
同时,在dialog.cpp中自动添加了这个函数的框架,在该函数中添加如下的代码,实现文本框字体下划线的控制。
void Dialog :: on_chkBoxUnder_clicked(bool checked) { QFont font = ui->txtEdit->font(); font.setUnderline(checked); ui->txtEdit->setFont(font); }
以同样的方法为Italic和Bold两个CheckBox设计槽函数,编译后运行,发现已经实现了修改字体的下划线、斜体、粗体属性的功能,说明信号与槽函数已经关联了。
但是,查看Dialog的构造函数,发现构造函数仍然是只有简单的一个语句:
Dialog::Dialog(QWidget * parent):QDialog(parent),ui(new Ui::Diglog) { ui->setupUi(this); }
这里没有发现用connect()函数进行几个CheckBox的信号与槽函数关联的操作。所以这些功能是如何实现的呢?
编译生成的ui_dialog.h文件中(正如我们在.ui文件那一篇文章中所说的一样),可以看到setupUi()函数的实现,其中并没有connect()语句进行了几个CheckBox的信号与槽关联的操作,只是在setupUi()中发现了如下语句:
QMetaObject :: connectSlotsByName(Dialog);
秘密就在此。connectSlotsByName(Dialog)这个函数的作用是搜索Dialog界面上的所有组件,将信号与匹配的槽函数关联起来。
这就是UI设计器可视化设计某个组件的信号响应槽函数,而不用手工去将其关联起来的原因,都是在界面类的构造函数(cpp文件里)里调用setupUi()自动完成了关联。
字体颜色设置
设置字体颜色的3个RadioButton是互斥性选择的,即一次只有一个RadioButton被选中,虽然也可以采用可视化设计的方式设计其clicked()信号的槽函数,但是这样要生成3个槽函数。这里可以简化设计,即设计一个槽函数,将3个RadioButton的clicked()信号关联到这一个槽函数。
为此,在Dialog类的private slots部分增加一个槽函数定义如下:
void setTextFontColor();
注意:将光标移动到这个函数的函数名上,右击右键,在弹出的快捷菜单中选择Refactor→Add Definition in dialog.cpp,就可以在dialog.cpp文件中自动为函数setTextFontColor()生成一个函数框架。
在dialog.cpp文件中,为setTextFontColor()编写代码如下:
void Dialog::setTextFontColor() { QPalette plet = ui->txtEdit->palette(); if(ui->rBtnBlue->isChecked()) plet.setColor(QPalette::Text,Qt::blue); else if(ui->rBtnRed->isChecked()) plet.setColor(QPalette::Text,Qt::red); else plet.setColor(QPalette::Text,Qt::black); ui->txtEdit->setPalette(plet); }
由于这个槽函数是自定义的,所以并不会自动与RadioButton的clicked()事件关联,此时编译后运行程序并不会改变字体颜色。需要在Dialog的构造函数中手工进行关联,代码如下:
Dialog::Dialog(QWidget *parent) : QDialog(parent) , ui(new Ui::Dialog) { ui->setupUi(this); connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor())); connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor())); }
在构造函数中将3个RadioButton的clicked()信号与同一个槽函数setTextFontColor()相关联。再编译运行,就可以更改文字的颜色了。
三个按钮的功能设计
界面上还有“确定”、“取消”、“退出”3个按钮,这是在对话框中常见的按钮。“确定”表示确认选择并关闭对话框,“取消”表示取消选择并关闭对话框,“关闭”则直接关闭对话框。
Dialog是从QDialog继承而来的,QDialog提供了accept()、reject()、close()等槽函数来表示这三种状态,只需要将按钮的clicked()信号与相应的槽函数相关联即可。
下面采用可视化的方式,将按钮的clicked信号与这些槽函数关联起来。在UI设计器中,单击上方工具栏中的“Edit Signals/Slots”按钮,窗体进入信号与槽编辑状态,如下图所示:

在上图中,左侧的列表中显示了btnOK的信号,选择clicked,右边列表框中显示了Dialog的槽函数,选择accept,点击“OK”完成关联。
同样的方式可以将btnCancel的clicked信号与Dialog的reject槽函数关联,将btnClose的clicked信号与Dialog的close槽函数关联。(需要注意的是,只有勾选了“显示从QWidget继承的信号和槽”之后,才会有close槽函数)
设置完了3个按钮的信号与槽的关联后,在窗体下方的Signals与Slots编辑器中也显示了这3个关联。实际上,可以直接在Signals与Slots编辑器进行关联设置。
现在编译并运行,单击这3个按钮都会关闭程序。
但是,这3个按钮的信号与槽函数的关联是在哪里实现呢——setupUi()中,在这个函数中自动增加了以下3行代码:
QObject::connect(btnOK,SIGNAL(clicked()),QWDialog,SLOT(accept()));
QObject::connect(btnCancel, SIGNAL(clicked()), QWDialog, SLOT(reject()));
QObject::connect(btnClose, SIGNAL(clicked()), QWDialog, SLOT(close()));
这个实例程序的功能全部完成了,采用UI设计器设计了窗体界面,采用可视化和程序化的方式设计了槽函数,设计信号和槽函数之间的关联。
从以上的设计过程可以看出,QT Creator和UI设计器给设计应用程序提供了强大的可视化设计功能

浙公网安备 33010602011771号