2.2 可视化UI设计
在上一节,通过一个极简单的应用程序,分析了Qt创建的GUI应用程序的各个文件的作用,剖析了可视化设计的UI文件是如何被转换为C++的类定义,并自动创建界面的。这些是使用Qt Creator可视化设计用户界面,并使各个部分融合起来运行的基本原理。
本节再以一个稍微复杂的例子来讲解设计GUI的常见功能,包括界面设计时布局的管理,程序里如何访问界面组件,以及Qt关键的信号与槽的概念。
2.2.1 实例程序功能
【本节实例程序samp2_2完整项目源码下载地址】https://download.csdn.net/download/hongandyi/10413746
创建一个Widget Application项目samp2_2,在创建窗体时选择基类QDialog,生成类的命名为QWDialog,并选择生成窗体。
如此新建的项目samp2_2有一个界面文件qwdialog.ui,一个头文件qwdialog.h和源程序文件qwdialog.cpp。此外,还有项目文件samp2_2.pro和主程序文件main.cpp。
qwdialog.ui界面文件设计时界面如图2-6所示。程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置。
在界面设计时,对需要访问的组件修改其objectName,例如各个按钮、需要读取输入的编辑框、需要显示结果的标签等,以便在程序里区分。对于不需要程序访问的组件则无需修改其objectName,如用于界面上组件分组的GroupBox、Frame、布局等,让UI设计器自动命名即可。
图2-6实例程序samp2_2设计时界面
对图2-6中几个主要组件的命名、属性设置见表2-3。
表2-3 qwdialog.ui中各个组件的相关设置
对象名 |
类名称 |
属性设置 |
备注 |
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” |
界面窗口的类名称是QWDialog,objectName不要修改 |
对于界面组件的属性设置,需要注意以下几点:
(1)objectName是窗体上创建的组件的实例名称,界面上的每个组件需要有一个唯一的objectName,程序里访问界面组件时都是通过其objectName进行访问,自动生成的槽函数名称里也有objectName。所以,组件的objectName需要在设计程序之前设置好,设置好之后一般不要再改动。若设计程序之后再改动objectName,涉及的代码需要相应的改动。
(2)窗体的objectName就是窗体的类名称,在UI设计器里不要修改窗体的objectName,窗体的实例名称需要在使用窗体的代码里去定义。
2.2.2 界面组件布局
Qt的界面设计使用了布局(Layout)功能。所谓布局,就是界面上的组件的排列方式,使用布局可以使组件有规则的分布,并且随着窗体大小变化自动的调整大小和相对位置。布局管理是GUI设计的必备技巧,下面逐步讲解如何实现图2-6的界面设计。
1. 界面组件的层次关系
为了将界面上的各个组件的分布设计的更加美观,经常使用一些容器类,如QGoupBox,QTabWidget,QFrame等。比如,将三个CheckBox组件放置在一个GroupBox组件里,这个GroupBox组件就是这三个CheckBox的容器,移动这个GroupBox就会同时移动其中的三个CheckBox。
图2-7是设计图2-6的界面的前期阶段。在窗体上放置了两个GroupBox组件,在groupBox1里放置三个CheckBox组件,在groupBox2里放置三个RadioButton组件。图2-7右侧Object Inspector里显示了界面上各组件之间的层次关系。
图2-7界面组件的放置及层次关系
2. 布局管理
Qt为界面设计提供了丰富的布局管理功能,在UI设计器中,组件面板里有Layouts和Spacers两个组件面板,在窗体上方的工具栏里有布局管理的按钮(如图2-8)。
图2-8用于布局可视化设计的组件面板和工具栏
Layouts和Spacers两个组件面板里的布局组件的功能见表2-4。
表2-4 组件面板上用于布局的组件
布局组件 |
功能 |
Vertical Layout |
垂直方向布局,组件自动在垂直方向上分布 |
Horizontal Layout |
水平方向布局,组件自动在水平方向上分布 |
Grid Layout |
网格状布局,网状布局大小改变时,每个网格的大小都改变。 |
Form Layout |
窗体布局,与网格状布局类似,但是只有最右侧的一列网格会改变大小 |
Horizontal Spacer |
一个用于水平分隔的空格 |
Vertical Spacer |
一个用于垂直分隔的空格 |
使用组件面板里的布局组件设计布局时,先拖放一个布局组件到窗体上,例如在设计图2-8的三个按钮的布局时,先放一个Horizontal Layout到窗体上,布局组件会以红色边框显示。再往布局组件里拖放三个Push Button和两个Horizontal Spacer,就可以得到图2-8中三个按钮的水平布局效果。
在设计窗体的上方有一个工具栏,用于使设计器进入不同的状态,以及进行布局设计,工具栏上各按钮的功能见表2-5。
表2-5 UI设计器工具栏各按钮的功能
按钮及快捷键 |
功能 |
Edit Widget (F3) |
界面设计进入编辑状态,就是正常的设计状态 |
Edit Signals/Slots(F4) |
进入信号与槽的可视化设计状态 |
Edit Buddies |
进入伙伴关系编辑状态,可以设置一个Label与一个组件成为伙伴关系 |
Edit Tab Order |
进入Tab顺序编辑状态,Tab顺序是在键盘上按Tab键时,输入焦点在界面各组件之间跳动的顺序 |
Lay Out Horizontally (Ctrl+H) |
将窗体上所选组件水平布局 |
Lay Out Vertically (Ctrl+L) |
将窗体上所选组件垂直布局 |
Lay Out Horizontally in Splitter |
将窗体上所选组件用一个分割条进行水平分割布局 |
Lay Out Vertically in Splitter |
将窗体上所选组件用一个分割条进行垂直分割布局 |
Lay Out in a Form Layout |
将窗体上所选组件按窗体布局 |
Lay Out in a Grid |
将窗体上所选组件网格布局 |
Break Layout |
解除窗体上所选组件的布局,也就是打散现有的布局 |
Adjust Size(Ctrl+J) |
自动调整所选组件的大小 |
使用工具栏上的布局控制按钮时,只需在窗体上选中需要设计布局的组件,然后点击某个布局按钮即可。在窗体上选择组件时同时按住Ctrl键,可以实现组件多选,选择某个容器类组件,相当于选择了其内部的所有组件。例如,在图2-7的界面中,选中groupBox1,然后单击“Lay Out Horizontally”工具栏按钮,就可以对groupBox1内的三个CheckBox水平布局。
在图2-8的界面上,使groupBox1里的三个CheckBox水平布局,groupBox2里的三个RadioButton水平布局,下方三个按钮水平布局。在窗体上又放置了一个PlainTextEdit组件。现在,改变groupBox1, groupBox2或按钮的水平布局的大小,其内部组件都会自动改变大小。但是当改变窗体大小时,界面上的各组件却并不会自动改变大小。
还需为窗体指定一个总的布局。选中窗体(即不要选择任何组件),单击工具栏上的“Lay Out Vertically”按钮,使4个组件垂直分布。这样布局后,当窗体大小改变时,各个组件都会自动改变大小。
在UI设计器里可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度和高度来实现某些需要的布局效果。
3. 伙伴关系与Tab顺序
在UI设计工具栏上单击“EditBuddies”按钮可以进入伙伴关系编辑状态,例如设计一个窗体时,进入伙伴编辑状态之后如图2-9所示。
伙伴关系(Buddy)是指的界面上一个Label和一个组件相关联,在图2-9的伙伴关系编辑状态,单击一个Label,按住鼠标左键,然后拖向一个组件,就建立了Label和组件之间的伙伴关系。
图2-9编辑伙伴关系
伙伴关系是为了在程序运行时,在窗体上用快捷键快速将输入焦点切换到某个组件上。例如,在图2-9的界面上,设定“姓名”标签的Text属性为“姓名(&N)”,其中符号“&”用来指定快捷字符,界面上并不显示“&”,这里指定快捷字母为N。那么程序运行时,用户按下Alt+N,输入焦点就会快速切换到“姓名”关联的输入框内。
在UI设计器工具栏上单击“EditTab Order”按钮进入Tab顺序编辑状态(如图2-10)。Tab顺序是指在程序运行时,按下键盘上的Tab键时输入焦点的移动顺序。一个好的用户界面,在按Tab键时,焦点应该以合理的顺序在界面上移动,而不是随意地移动。
图2-10 Tab顺序编辑状态
进入Tab顺序编辑状态后,在界面上会显示具有Tab顺序的组件的Tab顺序编号,依次按希望的顺序单击组件,就可以重排Tab顺序了。没有输入焦点的组件是没有Tab顺序的,如Label组件。
2.2.3 信号与槽
信号与槽(Signal & Slot)是Qt编程的基础,也是Qt的一大创新。因为有了信号与槽的编程机制,在Qt中处理界面各个组件的交互操作时变得比较直观和简单。
信号(Signal)就是在特定情况下被发射的事件,例如PushButton最常见的信号就是鼠标单击时发射的clicked()信号,一个ComboBox最常见的信号是选择的列表项变化时发射的CurrentIndexChanged()信号。GUI程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。
槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public,private或protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
信号与槽关联是用QObject::connect()函数实现的,其基本格式是:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect()是QObject类的一个静态函数,而QObject是所有Qt类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中,sender是发射信号的对象的名称,signal()是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver是接收信号的对象名称,slot()是槽函数的名称,需要带括号,有参数时还需要指明参数。
SIGNAL和SLOT是Qt的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。例如,在samp2_1的ui_widget.h文件中,在setupUi( )函数中有如下的语句:
QObject::connect(btnClose, SIGNAL(clicked()), Widget, SLOT(close()));
其作用就是将btnClose按钮的clicked()信号与窗体(Widget)的槽函数close()相关联,这样,当单击btnClose按钮(就是界面上的“Close”按钮)时,就会执行Widget的close()槽函数。
关于信号与槽的使用,有以下一些规则需要注意:
(1)一个信号可以连接多个槽,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int));
这是当一个对象spinNum的数值发生变化时,所在窗体有两个槽进行响应,一个addFun()用于计算,一个updateStatus()用于更新状态。
当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行。
当信号和槽函数带有参数时,在connect()函数里,要写明参数的类型,但可以不用写参数名称。
(2)多个信号可以连接同一个槽,例如在本项目的设计中,让三个选择颜色的RadioButton的clicked()信号关联到相同的一个自定义槽函数setTextFontColor()。
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
这样,当任何一个RadioButton被单击时,都会执行setTextFontColor()函数。
(3)一个信号可以连接另外一个信号,例如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SIGNAL (refreshInfo(int));
这样,当一个信号发射时,也会发射另外一个信号,实现某些特殊的功能。
(4)严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。
(5)在使用信号与槽的类中,必须在类的定义中加入宏Q_OBJECT。
(6)当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才执行发射信号处后面的代码。
信号与槽机制是Qt GUI编程的基础,使用信号与槽机制可以比较容易地将信号与响应代码关联起来。
2.2.4 可视化生成槽函数原型和框架
下面开始设计程序功能。对于该程序,希望它的功能是:
● 单击UnderLine, Italic, Bold三个CheckBox时,根据其状态,设置PlainTextEdit里的文字的字体样式。
● Black,Red,Blue三个RadioButton是互斥选择的,单击某个RadioButton时,设置文字的颜色。
● 单击“确定”、“取消”或“退出”按钮时,关闭窗口,退出程序。
1. 字体样式设置
窗体在设计模式下,选中chkBoxUnder组件,单击右键调出其快捷菜单。在快捷菜单中单击菜单项“Go to slot…”,出现如图2-11的对话框。
图2-11 QCheckBox的Go to slot对话框
该对话框列出了QCheckBox类的所有信号,第一个是clicked(),第二个是带一个布尔类型参数的clicked(bool)。
信号clicked(bool)会将CheckBox组件当前的选择状态作为一个参数传递,在响应代码里可以直接利用这个传递的参数。而如果用信号clicked(),则需要在代码里读取CheckBox组件的选中状态。为了简化代码,选择clicked(bool)信号。
选择clicked(bool),然后单击“OK”按钮,在QWDialog的类定义中,会在private slots部分自动增加一个槽函数声明,函数名是根据发射对象及其信号名称自动命名的。
void on_chkBoxUnder_clicked(bool checked);
同时,在qwdialog.cpp文件中自动添加了函数on_chkBoxUnder_clicked(bool)的框架,在此函数中添加如下的代码,实现文本框字体下划线的控制。
void QWDialog::on_chkBoxUnder_clicked(bool checked)
{
QFont font=ui->txtEdit->font();
font.setUnderline(checked);
ui->txtEdit->setFont(font);
}
以同样的方法为Italic和Bold两个CheckBox设计槽函数,编译然后运行,发现已经实现了修改字体的下划线、斜体、粗体属性的功能,说明信号与槽函数已经关联了。
但是,查看QWDialog的构造函数,构造函数只有简单的一条语句。
QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog)
{
ui->setupUi(this);
}
这里没有发现用connect()函数进行几个CheckBox的信号与槽函数关联的操作。这些功能是如何实现的呢?
查看编译生成的ui_qwdialog.h文件。构造函数里调用的setupUi()是在ui_qwdialog.h文件里实现的。查看setupUi()函数的内容,也没有发现用connect()函数进行几个CheckBox的信号与槽关联的操作,只是在setupUI()里发现了如下的一条语句:
QMetaObject::connectSlotsByName(QWDialog);
秘密就在于这条语句。connectSlotsByName(QWDialog)函数将搜索QWDialog界面上的所有组件,将信号与槽函数匹配的信号和槽关联起来,它假设槽函数的名称是:
void on_
例如,通过UI设计器的操作,为chkBoxUnder自动生成的槽函数是:
void on_chkBoxUnder_clicked(bool checked);
它就正好是chkBoxUnder的信号clicked(bool)的槽函数。那么,connectSlotsByName()就会将此信号和槽函数关联起来,如同执行了下面的这样一条语句:
connect(chkBoxUnder, SIGNAL(clicked (bool)),
this, SLOT (on_chkBoxUnder_clicked (bool));
这就是用UI设计器可视化设计某个组件的信号响应槽函数,而不用手工去将其关联起来的原因,都是在界面类的构造函数里调用setupUi()自动完成了关联。
2. 字体颜色设置
设置字体的三个RadioButton是互斥性选择的,即一次只有一个RadioButton被选中,虽然也可以采用可视化设计的方式设计其clicked()信号的槽函数,但是这样就需要生成三个槽函数。可以简化设计,即设计一个槽函数,将三个RadioButton的clicked()信号关联到这一个槽函数。
为此,在QWDialog类的private slots部分增加一个槽函数定义如下:
void setTextFontColor();
【提示】将鼠标光标移动到这个函数的函数名上面,单击右键,在弹出的快捷菜单中选择“Refactor”→“Add Definition in qwdialog.cpp”,就可以在qwdialog.cpp文件中自动为函数setTextFontColor()生成一个函数框架。
在qwdialog.cpp文件中,为setTextFontColor()编写实现代码如下:void QWDialog::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 if (ui->rBtnBlack->isChecked())
plet.setColor(QPalette::Text,Qt::black);
else
plet.setColor(QPalette::Text,Qt::black);
ui->txtEdit->setPalette(plet);
}
由于这个槽函数是自定义的,所以,不会自动与RadioButton的clicked()事件关联,此时编译后运行程序不会实现改变字体颜色的功能。需要在QWDialog的构造函数中手工进行关联,代码如下:
QWDialog::QWDialog(QWidget *parent) : QDialog(parent), ui(new Ui::QWDialog)
{
ui->setupUi(this);
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));
}
在构造函数中将三个RadioButton的clicked() 信号与同一个槽函数setTextFontColor()相关联。再编译后运行,就可以更改文字的颜色了。
3. 三个按钮的功能设计
界面上还有“确定”,“取消”,“退出”三个按钮,这是在对话框中常见的按钮。“确定”表示确认选择并关闭对话框,“取消”表示取消选择并关闭对话框,“退出”直接关闭对话框。
QWDialog是从QDialog继承而来的,QDialog提供了accept(), reject(), close()等槽函数来表示这三种状态,只需将按钮的clicked()信号与相应槽函数关联即可。
下面采用可视化的方式,将按钮的clicked()信号与这些槽函数关联起来。在UI设计器里,单击上方工具栏里的“Edit Signals/Slots”按钮,窗体进入信号与槽函数编辑状态,如图2-12所示。将鼠标移动到“确定”按钮上方,再按下鼠标左键,移动到窗体的空白区域释放左键,这时出现如图2-13所示的关联设置对话框。
图2-12 窗体进入Signals/Slots编辑状态
图2-13 信号与槽关联编辑对话框
在图2-13中,左边的列表框里显示了btnOK的信号,选择clicked(),右边的列表框里显示了QWDialog的槽函数,选择accept(),单击“OK”按钮。
同样的方法可以将btnCancel的clicked()信号与QWDialog的reject()槽函数关联,将btnClose的clicked()信号与QWDialog的close()槽函数关联。
【注意】在图2-13的右边列表框中没有close()槽函数,需要勾选下方的“Show signals and slotsinherited from QWidget”才会出现close()函数。
设置完三个按钮的信号与槽关联之后,在窗体下方的Signals &Slots 编辑器里也显示了这三个关联。实际上,可以直接在Signals & Slots 编辑器进行关联设置。现在编译并运行程序,单击这三个按钮都会关闭程序。
那么,这三个按钮的信号与槽函数的关联是在哪里实现的呢?答案在setupUi()函数里,在setupUi()函数里自动增加了以下三行代码:
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设计器为设计应用程序提供了强大的可视化设计功能。
【本节实例程序samp2_2完整项目源码下载地址】https://download.csdn.net/download/hongandyi/10413746
<总目录> <第2章> <上一节> <下一节>