本篇介绍了利用Qt Creator创建UI设计所使用到的文件及整体的运行机制,例外以实例的方式详细介绍了可视化UI设计(QDialog)、代码化UI设计(QDialog)及混合式UI设计(QMainwindow),混合式的UI设计是笔者推崇的,后期实际工程中需要结合具体情况选择使用的方法。
GUI应用程序设计基础
1.UI文件设计与运行机制
本节主要讲了一个Widget Application项目中包含的文件及详细的内容,分析了 Qt 创建的 GUI 应用程序中各个文件的作用,剖析了可视化设计的 UI 文件是如何被转换为 C++的类定义,并自动创建界面的。这些是使用 QtCreator 可视化设计用户界面,并使各个部分融合起来运行的基本原理。
主要包含了以下文件内容:
-
项目组织文件 samp2_1.pro,存储项目设置的文件
-
主程序入口文件 main.cpp,实现 main()函数的程序文件。
-
窗体界面文件 widget.ui,一个XML 格式存储的窗体上的元件及其布局的文件。定义了窗口上的所有组件的属性设置布局,及其信号与槽函数的关联等。用 UI 设计器可视化设计的界面都由 Qt自动解析,并以XML文件的形式保存下来。在设计界面时,只需在 UI 设计器里进行可视化设计即可,而不用管 widget.ui
-
widget.h 是所设计的窗体类的头文件,widget.cpp 是 widget.h 里定义类的实现文件。在 C++里,任何窗体或界面组件都是用类封装的,一个类一般有一个头文件 (.h 文件) 和一个源程序文件 (.cpp 文件)。
关于各个文件中代码含义等细节均可在需要时通过《Qt5.9 c++开发指南》进行查看,比较重要一定查看,有助于后期理解。
2.可视化UI设计
以下以一个实例介绍如何通过可视化的方法进行UI设计。
2.1 实例程序功能
程序界面如下
实现功能:
程序的主要功能是对中间一个文本框的文字字体样式和颜色进行设置。
《Qt5.9 c++开发指南》中对每个组件的命名和属性设置进行说明,用于后面的程序编写。按照说明,初始界面如下图
2.2 界面组件布局
Qt 的界面设计使用了布局(Layout) 功能。所谓布局,就是界面上组件的排列方式,使用布局可以使组件有规则地分布,并且随着窗体大小变化自动地调整大小和相对位置。以下将会对实现过程逐步进行讲解。
2.2.1 界面组件的层次关系
为了将界面上的各个组件的分布设计得更加美观,经常使用一些容器类,如 QgoupBox
、QtabWidget
、QFrame
等。例如,将3 个 CheckBox 组件放置在一个 GroupBox 组件里,该GroupBox组件就是这3个CheckBox 的容器,移动这个GroupBox 就会同时移动其中的3个 CheckBox。
在窗体上放置了2个GroupBox组件,在groupBoxl里放置3个CheckBox组件,在 groupBox2 里放置3个RadioButton 组件,效果如下图所示:
2.2.2 布局管理
Qt 为界面设计提供了丰富的布局管理功能,在 UI 设计器中,组件面板里有 Layouts 和 Spacers两个组件面板,在窗体上方的工具栏里有布局管理的按钮。
(1)使用组件面板里的布局组件设计布局时,先拖放一个布局组件到窗体上,如在设计3个按钮的布局时,先放一个 Horizontal Layout
到窗体上,布局组件会以红色边框显示。再往布局组件里拖放 3个 Push Button 和 2个 Horizontal Spacer,就可以得到下图 中3 个按钮的水平布局效果。
(2)在设计窗体的上方有一个工具栏,用于调整设计器进入不同的状态,以及进行布局设计,工具栏上各按钮的功能见下图。
使用工具栏上的布局控制按钮时,只需在窗体上选中需要设计布局的组件,然后点击某个布局按钮即可。在窗体上选择组件时同时按住 Ctrl 键,可以实现组件多选,选择某个容器类组件,相当于选择了其内部的所有组件。
例如,在上面的界面中,选中 groupBox,然后单击“Lay OutHorizontally”工具栏按钮,就可以对 groupBoxl内的3个 CheckBox 水平布局。
在界面上,使 groupBox1里的 3个 CheckBox 水平布局,groupBox2 里的 3个RadioButton 水平布局,下方3 个按钮水平布局。在窗体上又放置了一个 PlainTextEdit 组件。现在改变 groupBoxl、groupBox2 或按钮的水平布局的大小,其内部组件都会自动改变大小。但是当改变窗体大小时,界面上的各组件却并不会自动改变大小。
(3)随后还需为窗体指定一个总的布局。选中窗体(即不要选择任何组件),单击工具栏上的“LayoutVertically”按钮,使4 个组件垂直分布。这样布局后,当窗体大小改变时,各个组件都会自动改变大小。
最后的布局效果如下图所示:
在 UI设计器里可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度和高度来实现某些需要的布局效果。
2.2.3 伙伴关系与 Tab 顺序
伙伴关系
是为了在程序运行时,在窗体上用快捷键快速将输入焦点切换到某个组件上;Tab 顺序
是指在程序运行时,按下键盘上的 Tab 键时输入焦点的移动顺序。详情请查看《Qt5.9 c++开发指南》对应内容。
2.3 信号与槽简要介绍
2.3.1 信号槽的基础概念
信号(Signal)就是在特定情况下被发射的事件,例如 PushButton 最常见的信号就是鼠标单击时发射的 clicked()信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的CurrentIndexChanged()信号。GUI程序设计的主要内容就是对界面上各组件的信号的响应,只需要知道什么情况下发射哪些信号,合理地去响应和处理这些信号就可以了。
槽 (Slot)就是对信号响应的函数。槽就是一个函数,与一般的 C++函数是一样的,可以定义在类的任何部分 (public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
2.3.2 信号槽的基本格式
信号与槽关联是用QObject::connect()函数实现的,其基本格式是:
QObject::connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
connect()是 OObiect 类的一个静态函数,而OObiect 是所有 Ot类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:
connect(sender,SIGNAL(signal()), receiver, SLOT(slot()));
-
sender 是发射信号的对象的名称,signal()是信号名称。信号可以看做是特殊的函数需要带括号,有参数时还需要指明参数。receiver 是接收信号的对象名称,slot()是槽函数的名称需要带括号,有参数时还需要指明参数。
-
SIGNAL 和 SLOT是 Qt 的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。例如,上篇的ui widget.h 文件中,在 setupUi()函数中有如下的语句:
QObject;:connect(btnClose,SIGNAL(clicked()),Widget,SLOT(close()));
其作用就是将 btnClose 按钮的 clicked()信号与窗体(Widget)的槽函数 close()相关联,这样当单击 btnClose 按钮(就是界面上的“Close”按钮)时,就会执行 Widget 的 close(槽函数)。
2.3.3 信号槽的使用的注意事项
-
一个信号可以连接多个槽;
-
多个信号可以连接同一个槽;
-
一个信号可以连接另外一个信号;
-
严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误。;
-
在使用信号与槽的类中,必须在类的定义中加入宏Q OBJECT;
-
当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码
2.4 可视化生成槽函数原型和框架
2.4.1 功能需求
- 单击 UnderLine、Italic、Bold 3 个 CheckBox 时,根据其状态,设置 PlainTextEdit 里的文字的字体样式
- Black、Red、Blue 3 个 RadioButton 是互斥选择的,单击某个 RadioButton 时,设置文字的颜色
- 单击“确定”“取消”或“退出”按钮时,关闭窗口,退出程序
2.4.2 字体样式设置
2.4.2.1 功能实现
选中 checkBoxUnder 组件,单击右键调出其快捷菜单。在快捷菜单中单击菜单项“Go to slot”,有下面的界面出现:
信号 clicked(bool)会将 CheckBox 组件当前的选择状态作为一个参数传递,在响应代码里可以直接利用这个传递的参数。而如果用信号clicked0,则需要在代码里读取 CheckBox 组件的选中状态。为了简化代码,选择 clicked(bool)信号。点击OK后,会在Dialog.h中添加槽函数声明
private slots:
void on_checkBoxUnder_clicked(bool checked);
在.cpp中自动添加void Dialog::on_checkBoxUnder_clicked(bool checked)
框架,在其中添加对应的代码即可。
以同样的方法为 Italic 和 Bold 两个CheckBox 设计槽函数,编译后运行,可以看到已经实现想要的功能
2.4.2.2 可视化UI设计下的信号槽连接的原理
上面的结果表明信号与槽实现了关联,但是在构造函数中只有一下一句:
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
}
查看编译生成的 ui_qwdialog.h 文件。构造函数里调用的 setupUi()是在 ui_qwdialog.h 文件里实现的, setupUi()中也是没有信号槽关联实现的操作,但是有以下语句:
QMetaObject::connectSlotsByName(Dialog);
秘密就在于这句,QMetaObject::connectSlotsByName(Dialog);
将会搜索Dialog界面上所有组件,将信号与槽函数匹配的信号和槽关联起来,它假设槽函数的名称是:
void on_<object name>_<signal name>(<signal parameters>);
-
例如
通过UI设计器的操作,为checkBoxUnder自动生成的槽函数是:
on_checkBoxUnder_clicked(bool checked)
它就正好是 checkBoxUnder 的信号 clicked(bool)的槽函数。那么,connectSlotsByName0)就会将此信号和槽函数关联起来,如同执行了下面的这样一条语句:
connect(checkBoxUnder,SIGNAL(clicked(bool)),this,SLOT(on_checkBoxUnder_clicked(bool)));
这就是用 UI设计器可视化设计某个组件的信号响应槽函数,而不用手工去将其关联起来的原因,都是在界面类的构造函数里调用 setupUi()自动完成了关联。
2.4.3 字体颜色设置
(1)Black、Red、Blue 3 个 RadioButton 是互斥选择的,单击某个 RadioButton 时,设置文字的颜色,虽然可以采用可视化设计的方式设计其clicked()信号的槽函数,但这样就要生成三个槽函数。此处使用3个RadioButton 的 clicked()信号关联到这一个槽函数方式简化设计。
.h中添加:
private slots:
void setTextFontColor();
(2)可以通过鼠标停留在槽函数上,“Alt+Enter“的方式在.cpp中添加对应的实现及代码。
(3)由于这个槽函数是自定义的,所以不会自动与 RadioButton 的 clicked()事件关联,此时编译后运行程序不会实现改变字体颜色的功能。需要在构造函数中手工进行关联,代码如下:
connect(ui->rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//信号与槽的关联
connect(ui->rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//信号与槽的关联
connect(ui->rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//信号与槽的关联
编译运行后结果如下图:
2.4.4 三个按钮的功能设计
2.4.4.1 功能实现
界面上还有“确定”“取消”“退出”3 个按钮,这是在对话框中常见的按钮。“确定”表示确认选择并关闭对话框,“取消”表示取消选择并关闭对话框,“退出”则直接关闭对话框。
Dialog 是从 QDialog 继承而来的,QDialog 提供了 accept0、reject0)、close0等槽函数来表示这三种状态,只需将按钮的 clicked0信号与相应槽函数关联即可。
下面采用可视化的方式,将按钮的 clicked0)信号与这些槽函数关联起来。在 UI 设计器里,单击上方工具栏里的“Edit Signals/Slots”按钮,窗体进入信号与槽函数编辑状态。将鼠标移动到“确定”按钮上方,再按下鼠标左键,移动到窗体的空白区域释放左键,这时出现如下图 所示的关联设置对话框,并进行“确定”按钮的关联。
同样的方法可以将 btnCancel 的 clicked()信号与 QWDialog 的 reject()槽函数关联,将 btnClose的 clicked()信号与 QWDialog 的 close()槽函数关联。
注意:右侧列表框中没有 close()槽函数,需要勾选下方的“Show signals and slots inherited from QWidget”才会出现 close()函数。
效果如下图所示:
2.4.4.2 原理介绍
设置完 3 个按钮的信号与槽关联之后,在窗体下方的 Sigmals 和 Slots 编辑器里也显示了这3个关联。实际上,可以直接在 Signals 和 Slots 编辑器进行关联设置。现在编译并运行程序,单击这3个按钮都会关闭程序。
那么,这 3个按钮的信号与槽函数的关联是在哪里实现的呢?答案在 setupUi0函数里,在setupUi()函数里自动增加了以下3行代码:
QObject::connect(btnOK, SIGNAL(clicked()), Dialog, SLOT(accept()));
QObject::connect(btnClose, SIGNAL(clicked()), Dialog, SLOT(reject()));
QObject::connect(btnCancel, SIGNAL(clicked()), Dialog, SLOT(close()));
以上就采用可视化UI设计实现了程序,涉及的源码如下。
2.5 源码
2.5.1 Dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private slots:
void on_checkBoxUnder_clicked(bool checked);
void on_checkBoxItalic_clicked(bool checked);
void on_checkBoxBold_clicked(bool checked);
void setTextFontColor();
private:
Ui::Dialog *ui;
};
#endif // DIALOG_H
2.5.2 Dialog.cpp
#include "Dialog.h"
#include "ui_Dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
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()));//信号与槽的关联
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_checkBoxUnder_clicked(bool checked)
{
//设置下划线
QFont font=ui->txtEdit->font();
font.setUnderline(checked);
ui->txtEdit->setFont(font);
}
void Dialog::on_checkBoxItalic_clicked(bool checked)
{
//设置斜体
QFont font=ui->txtEdit->font();
font.setItalic(checked);
ui->txtEdit->setFont(font);
}
void Dialog::on_checkBoxBold_clicked(bool checked)
{
//设置粗体
QFont font=ui->txtEdit->font();
font.setBold(checked);
ui->txtEdit->setFont(font);
}
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 if (ui->rBtnBlack->isChecked())
plet.setColor(QPalette::Text,Qt::black);
else
plet.setColor(QPalette::Text,Qt::black);
ui->txtEdit->setPalette(plet);
}
3.代码化UI设计
由于界面设计的底层其实都是由 C++语言实现的,底层实现的功能比可视化设计更加强大和灵活。某些界面效果是可视化设计无法完成的,或者某些人习惯了用纯代码的方式来设计界面,就可以采用纯代码的方式设计界面,如 Qt 自带的实例基本都是用纯代码方式实现用户界面的。
3.1 实现效果
框架主要由构造函数中的以下程序实现:
iniUI(); //界面创建与布局
iniSignalSlots(); //信号与槽的关联
setWindowTitle("Form created manually");//设置窗体标题
3.2 源码
3.2.1 qwdlgmanual.h
代码中的汉字存在乱码问题,后期再想办法解决
#ifndef QWDLGMANUAL_H
#define QWDLGMANUAL_H
#include <QDialog>
#include <QCheckBox>
#include <QRadioButton>
#include <QPlainTextEdit>
#include <QPushButton>
class QWDlgManual : public QDialog
{
Q_OBJECT
private:
QCheckBox *chkBoxUnder;
QCheckBox *chkBoxItalic;
QCheckBox *chkBoxBold;
QRadioButton *rBtnBlack;
QRadioButton *rBtnRed;
QRadioButton *rBtnBlue;
QPlainTextEdit *txtEdit;
QPushButton *btnOK;
QPushButton *btnCancel;
QPushButton *btnClose;
void iniUI();//UI 鍒涘缓涓庡垵濮嬪寲
void iniSignalSlots();//鍒濆鍖栦俊鍙蜂笌妲界殑閾炬帴
private slots:
void on_chkBoxUnder(bool checked); //Underline 鐨刢licked(bool)淇″彿鐨勬Ы鍑芥暟
void on_chkBoxItalic(bool checked);//Italic 鐨刢licked(bool)淇″彿鐨勬Ы鍑芥暟
void on_chkBoxBold(bool checked); //Bold 鐨刢licked(bool)淇″彿鐨勬Ы鍑芥暟
void setTextFontColor(); //璁剧疆瀛椾綋棰滆壊
public:
QWDlgManual(QWidget *parent = 0);
~QWDlgManual();
};
#endif // QWDLGMANUAL_H
3.2.2 qwdlgmanual.cpp
#include "qwdlgmanual.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
void QWDlgManual::iniUI()
{
//创建 Underline, Italic, Bold三个CheckBox,并水平布局
chkBoxUnder=new QCheckBox(tr("Underline"));
chkBoxItalic=new QCheckBox(tr("Italic"));
chkBoxBold=new QCheckBox(tr("Bold"));
QHBoxLayout *HLay1=new QHBoxLayout;
HLay1->addWidget(chkBoxUnder);
HLay1->addWidget(chkBoxItalic);
HLay1->addWidget(chkBoxBold);
//创建 Black, Red, Blue三个RadioButton,并水平布局
rBtnBlack=new QRadioButton(tr("Black"));
rBtnBlack->setChecked(true);//缺省被选中
rBtnRed=new QRadioButton(tr("Red"));
rBtnBlue=new QRadioButton(tr("Blue"));
QHBoxLayout *HLay2=new QHBoxLayout;
HLay2->addWidget(rBtnBlack);
HLay2->addWidget(rBtnRed);
HLay2->addWidget(rBtnBlue);
//创建 确定, 取消, 退出 三个 QPushButton, 并水平布局
btnOK=new QPushButton(tr("OK"));
btnCancel=new QPushButton(tr("CanCel"));
btnClose=new QPushButton(tr("Close"));
QHBoxLayout *HLay3=new QHBoxLayout;
HLay3->addStretch();
HLay3->addWidget(btnOK);
HLay3->addWidget(btnCancel);
HLay3->addStretch();
HLay3->addWidget(btnClose);
//创建 文本框,并设置初始字体
txtEdit=new QPlainTextEdit;
txtEdit->setPlainText("Hello world\n\nIt is my demo");
QFont font=txtEdit->font(); //获取字体
font.setPointSize(20);//修改字体大小为20
txtEdit->setFont(font);//设置字体
//创建 垂直布局,并设置为主布局
QVBoxLayout *VLay=new QVBoxLayout;
VLay->addLayout(HLay1); //添加字体类型组
VLay->addLayout(HLay2);//添加字体颜色组
VLay->addWidget(txtEdit);//添加PlainTextEdit
VLay->addLayout(HLay3);//添加按键组
setLayout(VLay); //设置为窗体的主布局
}
void QWDlgManual::iniSignalSlots()
{
//三个颜色 QRadioButton的clicked()事件与setTextFontColor()槽函数关联
connect(rBtnBlue,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//
connect(rBtnRed,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//
connect(rBtnBlack,SIGNAL(clicked()),this,SLOT(setTextFontColor()));//
//三个字体设置的 QCheckBox 的clicked(bool)事件与 相应的槽函数关联
connect(chkBoxUnder,SIGNAL(clicked(bool)),this,SLOT(on_chkBoxUnder(bool)));//
connect(chkBoxItalic,SIGNAL(clicked(bool)),this,SLOT(on_chkBoxItalic(bool)));//
connect(chkBoxBold,SIGNAL(clicked(bool)),this,SLOT(on_chkBoxBold(bool)));//
//三个按键与窗体的槽函数关联
connect(btnOK,SIGNAL(clicked()),this,SLOT(accept()));//
connect(btnCancel,SIGNAL(clicked()),this,SLOT(reject()));//
connect(btnClose,SIGNAL(clicked()),this,SLOT(close()));//
}
void QWDlgManual::on_chkBoxUnder(bool checked)
{
QFont font=txtEdit->font();
font.setUnderline(checked);
txtEdit->setFont(font);
}
void QWDlgManual::on_chkBoxItalic(bool checked)
{
QFont font=txtEdit->font();
font.setItalic(checked);
txtEdit->setFont(font);
}
void QWDlgManual::on_chkBoxBold(bool checked)
{
QFont font=txtEdit->font();
font.setBold(checked);
txtEdit->setFont(font);
}
void QWDlgManual::setTextFontColor()
{
QPalette plet=txtEdit->palette();
if (rBtnBlue->isChecked())
plet.setColor(QPalette::Text,Qt::blue);
else if (rBtnRed->isChecked())
plet.setColor(QPalette::Text,Qt::red);
else if (rBtnBlack->isChecked())
plet.setColor(QPalette::Text,Qt::black);
else
plet.setColor(QPalette::Text,Qt::black);
txtEdit->setPalette(plet);
}
QWDlgManual::QWDlgManual(QWidget *parent)
: QDialog(parent)
{
iniUI(); //界面创建与布局
iniSignalSlots(); //信号与槽的关联
setWindowTitle("Form created manually");//设置窗体标题
}
QWDlgManual::~QWDlgManual()
{
}
4.混合方式UI设计
4.1 设计目的
采用纯代码方式进行 UI 设计虽然无所不能,但是设计效率太低,过程非常繁琐,而可视化UI设计简单高效。所以,能用可视化设计的就尽可能用可视化设计解决,无法解决的再用纯代码方式,将两种方法结合,才是高效设计 UI的方法。
本节用一个实例讲解如何用混合方式创建 UI,即部分界面设计用 UI 设计器可视化实现,部分无法在 UI 设计器里实现的界面设计用代码实现。同时用这个实例讲解如何使用资源文件
、如何使用Actions
,如何设计主窗口里的菜单、工具栏和状态栏
,这些是一般应用程序主窗口都有的功能。
本项目的窗口类是从QMainWindow 继承而来的,具有主菜单、工具栏和状态栏。这个例子实现了主菜单、主工具栏和状态栏,中间工作区里是一个 TextEdit 组件,用于编辑文本。下图是在 UI设计器里设计完成的窗口。
我们希望在工具栏上添加一个 SpinBox 组件,用于设置字体大小,还想在工具栏上添加一个FontComboBox组件,用于选择字体名称。但是在 UI设计器里,将这些组件拖放到工具栏上时,会显示不能添加到工具栏上。同样,在窗体下方的状态栏上也不能直接添加 Label 和ProgressBar 组件。这就是 UI可视化设计的局限,无法实现某些界面效果。
通过编写代码解决了上面的问题,并且在状态栏添加了显示功能,实现期望的界面效果,下图就是最终界面效果
4.2 创建项目并添加资源文件
4.2.1 创建项目
创建一个 Widget Application 项目,在向导的创建窗口类时,选择基类QMainWindow,新建类的名称设置为QWMainWind,并选择生成窗体。
4.2.2 添加资源文件
项目中需要使用到图标,Qt中是将图标存储在资源文件中,添加资源文件的过程如下面这些图所示:
添加前缀,前缀类似于资源的分组
添加文件
选中对应地址中的所有文件,编译后就可以得到下图框架
此时源代码文件夹内如下图
4.3 设计Action
QAction 是一个非常有用的类,在界面设计时创建 Action,并编写其 trigger()信号的槽函数。使用设计的 Action 可以创建菜单项、工具栏按钮
,还可以设置为 QToolButton 按钮的关联 Action
。点击这些由 Action 创建的菜单项、按钮就是执行 Action 的槽函数
按照如下步骤,在.ui下的Action编辑器中创建Action列表
最终设计好之后的Action列表如下图所示
创建Action列表的详细过程请参考《Qt5.9 c++开发指南》
4.4 设计菜单和工具栏
建立 Action 之后,就可以在主窗体上设计菜单和工具栏了。本项目的窗体类 QWMainWind是从QMainWindow 继承的,具有菜单栏、工具栏和状态栏。
双击项目文件目录树里的 qwmainwind.ui,在 UI 设计器里打开此窗口。在窗口最上方显示“Type Here”的地方是菜单栏,菜单栏下方是工具栏,窗口最下方是状态栏。
4.4.1 创建菜单栏
在菜单栏显示“Type Here”的地方双击,出现一个编辑框,在编辑框里输入所要设计菜单的分组名称,如“文件”,然后回车,这样就创建了一个“文件”菜单分组,同样可以创建“编辑”“格式”分组。
创建主菜单的分组后,从Action 编辑器的列表里将一个 Action 拖放到菜单某个分组下,就可以创建一个菜单项,如同在界面上放置一个组件一样。如果需要在菜单里增加一个分隔条,双击“Add Seperator”就可以创建一个分隔条,然后拖动到需要的位置即可。如果需要删除某个菜单项或分隔条,单击右键,选择“Remove”菜单项。菜单设计的结果如下图
4.4.2 创建工具栏
设计工具栏的方式也很相似。将一个 Action 拖放到窗口的工具栏上,就会新建一个工具栏按钮。同样可以在工具栏上添加分隔条,可以移除工具栏按钮。主窗口上初始的只有一个工具栏,如果需要设计多个工具栏,在主窗口上单击右键,单击“Add Tool Bar”即可新建一个工具栏。
工具栏上按钮的显示方式有多种,只需设置工具栏的 toolButtonStvle 属性,具体详见《Qt5.9 c++开发指南》,得到的界面如下图所示
4.4.3 放置QTextEdit
在窗体上放置一个QTextEdit 组件,其 objectname 设置为 txtEdit。如此就完成了菜单栏、工具栏的可视化设计。如下图
4.4.4 可视化界面的底层实现
可视化设计的界面的底层实现是由ui qwmainwind.h 文件实现的。打开ui gwmainwind.h文件,可以看到ui_qwmainwind.h中定义了类 Ui_QWMainWind。在 public 部分定义了界面所有 Action 和各种组件的指针变量,在 setupUi()函数里依次创建所有的 Action 和界面组件,然后将 Action 添加到主菜单和工具栏上。
可以想象如果都采用代码的方式去编写,会花费很长的时间。
4.5 代码创建其他界面组件
在工具栏上增加一个 SpinBox 用于设置字体大小,增加一个 FontComboBox 来选择字体。当从组件面板里拖放一个 SpiBox 到工具栏上时,却发现工具栏“拒收”,同样,在状态栏上放一个Label 和一个 ProgreeBar 也是被“拒收”的。这是 U设计器的局限性,某些界面效果无法用可视化设计方式实现。
4.5.1 qwmainwind.h
#ifndef QWMAINWIND_H
#define QWMAINWIND_H
#include <QMainWindow>
#include <QLabel>
#include <QProgressBar>
#include <QSpinBox>
#include <QFontComboBox>
namespace Ui {
class qwmainwind;
}
class qwmainwind : public QMainWindow
{
Q_OBJECT
public:
explicit qwmainwind(QWidget *parent = 0);
~qwmainwind();
private:
Ui::qwmainwind *ui;
private:
QLabel *fLabCurFile;//状态栏里显示当前文件的Label
QProgressBar *progressBar1;//状态栏上的进度条
QSpinBox *spinFontSize;// 字体大写
QFontComboBox *comboFont;//字体名称
void iniUI(); //程序设计的UI初始化
};
#endif // QWMAINWIND_H
4.5.2 qwmainwind.cpp
#include "qwmainwind.h"
#include "ui_qwmainwind.h"
qwmainwind::qwmainwind(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::qwmainwind)
{
ui->setupUi(this);
iniUI();
}
qwmainwind::~qwmainwind()
{
delete ui;
}
void qwmainwind::iniUI()
{
//状态栏
fLabCurFile=new QLabel; //用于显示当前文件名的标签
fLabCurFile->setMinimumWidth(150);
fLabCurFile->setText(QString::fromLocal8Bit("当前文件:"));
ui->statusBar->addWidget(fLabCurFile);//添加到状态栏
progressBar1=new QProgressBar;//状态栏上的进度条
progressBar1->setMaximumWidth(200);//设置组件最大宽度
progressBar1->setMinimum(5);
progressBar1->setMaximum(50);
progressBar1->setValue(ui->txtEdit->font().pointSize());//初始值
ui->statusBar->addWidget(progressBar1); //添加到状态栏
//工具栏
spinFontSize = new QSpinBox;// 工具栏上的文字大小 SpinBox
spinFontSize->setMinimum(5);
spinFontSize->setMaximum(50);
spinFontSize->setValue(ui->txtEdit->font().pointSize());//初始值
spinFontSize->setMinimumWidth(50);//设置组件最小宽度
ui->mainToolBar->addWidget(new QLabel(QString::fromLocal8Bit("字体大小 "))); //不引用的Label无需定义变量
ui->mainToolBar->addWidget(spinFontSize); //SpinBox添加到工具栏
ui->mainToolBar->addSeparator(); //工具栏上增加分隔条
ui->mainToolBar->addWidget(new QLabel(QString::fromLocal8Bit(" 字体 ")));
comboFont = new QFontComboBox;//字体名称ComboBox
comboFont->setMinimumWidth(150); //设置组件最小宽度
ui->mainToolBar->addWidget(comboFont);//添加到工具栏
setCentralWidget(ui->txtEdit); //将txtEdit设置为中心组件,自动填充整个工作区
}
4.5.3 编译运行结果及注意
编译运行后的界面如下图所示
注意:
iniUI()函数一定要在 ui->setupUi(this)之后再调用,两行语句的先后顺序不能调换。因为 ui->setupUi(this)实现了可视化设计的界面的创建,iniUI()是在可视化创建的界面基础之上添加其他组件,所以必须在其后面调用。
4.6 Action的功能实现
Action 是一种不可见的界面元素,主要用于菜单项、工具栏按钮的设计。Action 的主要信号是 trigger(),为一个Action 的 trigger()信号编写槽函数之后,菜单和工具栏上由此 Action 创建的菜单项和工具栏按钮就都关联此槽函数。
4.6.1 编辑功能Action的实现
用于编辑的 Action 有剪切、复制、粘贴和清除的功能,以便对 txtEdit 组件进行相应的操作。而QTextEdit 类就有相应的槽函数,无需再编写实现代码,只需将 Action 的 trigger()信号与 txtEdit 的相应槽函数进行关联即可。在 Signals 和 Slots 编辑器里设置信号与槽的关联,设计好后的几个 Action 的关联如下图 所示。
4.6.2 其他 Action 的功能实现
Action 的主要信号是 trigger()
和 trigger(bool)
,在单击菜单项或工具栏按钮时发射。
用于设置粗体、斜体和下划线的 3 个 Action 具有 Checkable 属性,选择 trigger(bool)信号设计槽函数更合适,该信号会将 Action 的复选状态作为一个参数传递,在响应代码里可以直接使用。其他Action 可选择 trigger()信号生成槽函数。在 Action 的“Go to slot”对话框,选择信号为Action创建槽函数。
自动生成槽函数,进行代码补充如下:
void qwmainwind::on_actFontBold_triggered(bool checked)
{
QTextCharFormat fmt; //格式
fmt=ui->txtEdit->currentCharFormat();//获取当前选择文字的格式
if (checked) // 相当于调用ui->actFontBold->isChecked();读取Action的check状态
fmt.setFontWeight(QFont::Bold);
else
fmt.setFontWeight(QFont::Normal);
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
剩下的代码自行进行补充,后面会给出最终代码。
4.6.3 Action 的 enabled 和 checked 属性的更新
为了使界面和程序功能显得更加智能一点,应该根据当前的状态自动更新相关 Action 的checked 和 enabled 属性,这是软件设计中常见的功能。
在本程序中,“剪切”“复制”“粘贴”的 enabled 属性应该随文本框内文字选择的状态变化而变化,“粗体”“斜体”“下划线”的 checked 属性应该随着当前文字的字体状态而自动更新。这可以针对 QTextEdit 的一些信号编写槽函数来实现。
在主窗体上选择文本编辑框txtEdit,在快捷菜单里调出“Go to slot”对话框。对话框里列出了QTextEdit 的所有信号,有两个可以利用的信号。
-
copyAvailable(bool) 信号在有内容可以被复制时发射,并且传递了一个布尔参数,可以利用此信号来改变actCut,actCopy 的enabled 属性。
-
selectionChanged()信号在选择的文字发生变化时发射,利用此信号,可以读取当前文字的格式,从而更新粗体、斜体和下划线3 种字体设置Action 的 checked 属性。
为txtEdit 组件的这两个信号生成槽函数定义和函数体框架,操作图如下:
编写代码如下:
void qwmainwind::on_txtEdit_copyAvailable(bool b)
{
//有文字可copy时更新cut,copy的Enable状态
ui->actCut->setEnabled(b); //能否 cut
ui->actCopy->setEnabled(b); //能否copy
ui->actPaste->setEnabled(ui->txtEdit->canPaste()); //能否paste
}
void qwmainwind::on_txtEdit_selectionChanged()
{
//当前选择的文字发生变化,更新粗体、斜体、下划线3个action的checked状态
QTextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat(); //获取文字的格式
ui->actFontItalic->setChecked(fmt.fontItalic()); //是否斜体
ui->actFontBold->setChecked(fmt.font().bold()); //是否粗体
ui->actFontUnder->setChecked(fmt.fontUnderline()); //是否有下划线
}
4.7 代码创建的组件的信号与槽
在界面上还有两个用代码创建的组件,用于设置字体大小的 spinFontSize 和用于设置字体的comboFont,这两个组件的信号与槽的功能实现显然不能通过前面所述的可视化的方法来实现。对于代码创建的组件需要利用代码编写槽函数,并将信号与槽关联起来。
为此需要在QWMainWind类中定义两个槽函数,以及用于进行信号与槽关联的函数 iniSignalSlots0,该函数在构造函数里调用。
最终代码见下。
4.8 最终的功能实现代码
4.8.1 qwmainwind.h
#ifndef QWMAINWIND_H
#define QWMAINWIND_H
#include <QMainWindow>
#include <QLabel>
#include <QProgressBar>
#include <QSpinBox>
#include <QFontComboBox>
namespace Ui {
class qwmainwind;
}
class qwmainwind : public QMainWindow
{
Q_OBJECT
public:
explicit qwmainwind(QWidget *parent = 0);
~qwmainwind();
private slots:
void on_actFontBold_triggered(bool checked);
void on_txtEdit_copyAvailable(bool b);
void on_txtEdit_selectionChanged();
void on_actFontItalic_triggered(bool checked);
void on_actFontUnder_triggered(bool checked);
void on_actOpen_triggered();
void on_actNew_triggered();
void on_actFont_triggered();
// 自定义槽函数
void on_spinBoxFontSize_valueChanged(int aFontSize);//改变字体大小的SpinBox的响应
void on_comboFont_currentIndexChanged(const QString &arg1);//FontCombobox的响应,选择字体名称
private:
Ui::qwmainwind *ui;
private:
QString fCurFileName;//当前文件名
QLabel *fLabCurFile;//状态栏里显示当前文件的Label
QProgressBar *progressBar1;//状态栏上的进度条
QSpinBox *spinFontSize;// 字体大写
QFontComboBox *comboFont;//字体名称
void iniUI(); //程序设计的UI初始化
void updateCurFile(QString aFile);//更新当前文件名,并更新状态栏提示
void iniSignalSlots(); //手工关联信号与槽
void createNew();
};
#endif // QWMAINWIND_H
4.8.2 qwmainwind.cpp
#include "qwmainwind.h"
#include "ui_qwmainwind.h"
#include <QFile>
#include <QFileDialog>
#include <QTextStream>
#include <QFontDialog>
#include <QTextCharFormat>
qwmainwind::qwmainwind(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::qwmainwind)
{
ui->setupUi(this);
iniUI();
iniSignalSlots();//信号与槽关联
}
qwmainwind::~qwmainwind()
{
delete ui;
}
void qwmainwind::iniUI()
{
//状态栏
fLabCurFile=new QLabel; //用于显示当前文件名的标签
fLabCurFile->setMinimumWidth(150);
fLabCurFile->setText(QString::fromLocal8Bit("当前文件:"));
ui->statusBar->addWidget(fLabCurFile);//添加到状态栏
progressBar1=new QProgressBar;//状态栏上的进度条
progressBar1->setMaximumWidth(200);//设置组件最大宽度
progressBar1->setMinimum(5);
progressBar1->setMaximum(50);
progressBar1->setValue(ui->txtEdit->font().pointSize());//初始值
ui->statusBar->addWidget(progressBar1); //添加到状态栏
//工具栏
spinFontSize = new QSpinBox;// 工具栏上的文字大小 SpinBox
spinFontSize->setMinimum(5);
spinFontSize->setMaximum(50);
spinFontSize->setValue(ui->txtEdit->font().pointSize());//初始值
spinFontSize->setMinimumWidth(50);//设置组件最小宽度
ui->mainToolBar->addWidget(new QLabel(QString::fromLocal8Bit("字体大小 "))); //不引用的Label无需定义变量
ui->mainToolBar->addWidget(spinFontSize); //SpinBox添加到工具栏
ui->mainToolBar->addSeparator(); //工具栏上增加分隔条
ui->mainToolBar->addWidget(new QLabel(QString::fromLocal8Bit(" 字体 ")));
comboFont = new QFontComboBox;//字体名称ComboBox
comboFont->setMinimumWidth(150); //设置组件最小宽度
ui->mainToolBar->addWidget(comboFont);//添加到工具栏
setCentralWidget(ui->txtEdit); //将txtEdit设置为中心组件,自动填充整个工作区
}
void qwmainwind::updateCurFile(QString aFile)
{
//更新当前文件名,并更新状态栏提示
fCurFileName=aFile;
fLabCurFile->setText(QString::fromLocal8Bit("当前文件:")+fCurFileName);
}
void qwmainwind::iniSignalSlots()
{
//信号与槽的关联,当函数带有参数时,必须写明参数的类型
connect(spinFontSize,SIGNAL(valueChanged(int)),
this,SLOT(on_spinBoxFontSize_valueChanged(int)));
connect(comboFont,SIGNAL(currentIndexChanged(const QString &)),
this,SLOT(on_comboFont_currentIndexChanged(const QString &)));
}
void qwmainwind::on_actFontBold_triggered(bool checked)
{
QTextCharFormat fmt; //格式
fmt=ui->txtEdit->currentCharFormat();//获取当前选择文字的格式
if (checked) // 相当于调用ui->actFontBold->isChecked();读取Action的check状态
fmt.setFontWeight(QFont::Bold);
else
fmt.setFontWeight(QFont::Normal);
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
void qwmainwind::on_txtEdit_copyAvailable(bool b)
{
//有文字可copy时更新cut,copy的Enable状态
ui->actCut->setEnabled(b); //能否 cut
ui->actCopy->setEnabled(b); //能否copy
ui->actPaste->setEnabled(ui->txtEdit->canPaste()); //能否paste
}
void qwmainwind::on_txtEdit_selectionChanged()
{
//当前选择的文字发生变化,更新粗体、斜体、下划线3个action的checked状态
QTextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat(); //获取文字的格式
ui->actFontItalic->setChecked(fmt.fontItalic()); //是否斜体
ui->actFontBold->setChecked(fmt.font().bold()); //是否粗体
ui->actFontUnder->setChecked(fmt.fontUnderline()); //是否有下划线
}
void qwmainwind::on_actFontItalic_triggered(bool checked)
{
QTextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat();
fmt.setFontItalic(checked);
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
void qwmainwind::on_actFontUnder_triggered(bool checked)
{
QTextCharFormat fmt;
fmt=ui->txtEdit->currentCharFormat();
fmt.setFontUnderline(checked);
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
void qwmainwind::on_actOpen_triggered()
{
QString curPath,aFileName;
curPath=QCoreApplication::applicationDirPath(); //获取应用程序的路径
//调用打开文件对话框打开一个文件
aFileName=QFileDialog::getOpenFileName(this,tr("打开一个文件"),curPath,
"C++程序文件(*.cpp);;H头文件(*.h);;文本文件(*.txt);;所有文件(*.*)");
if (!aFileName.isEmpty())
{
QFile aFile(aFileName); //以文件方式读出
if (aFile.open(QIODevice::ReadWrite | QIODevice::Text))
{
QTextStream aStream(&aFile); //用文本流读取文件
while (!aStream.atEnd())
ui->txtEdit->append(aStream.readLine()); //读取一个文本行
updateCurFile(aFileName); //更新状态栏显示
}
aFile.close();
}
}
void qwmainwind::on_actNew_triggered()
{
//新建文件
ui->txtEdit->clear();
updateCurFile("");
}
void qwmainwind::on_actFont_triggered()
{
bool ok;
QFont font = QFontDialog::getFont(&ok, this);
if (ok)
ui->txtEdit->setFont(font);
}
void qwmainwind::on_spinBoxFontSize_valueChanged(int aFontSize)
{
//改变字体大小的SpinBox的响应
QTextCharFormat fmt;
fmt.setFontPointSize(aFontSize); //设置字体大小
ui->txtEdit->mergeCurrentCharFormat(fmt);
progressBar1->setValue(aFontSize);
}
void qwmainwind::on_comboFont_currentIndexChanged(const QString &arg1)
{
//FontCombobox的响应,选择字体名称
QTextCharFormat fmt;
fmt.setFontFamily(arg1);//设置字体名称
ui->txtEdit->mergeCurrentCharFormat(fmt);
}
4.8.3 qwmainwind.ui
4.9 为应用程序设置图标
用Qt Creator 创建的项目编译后的可执行文件具有默认的图标,如果需要为应用设置一个自己的图标,其操作很简单,只需两步。
将一个图标文件(必须是“ico”后缀的图标文件) 复制到项目源程序目录下
在项目配置文件即.pro文件中里用 RC_ICONS 设置图标文件名,添加下面一行代码。
RC_ICONS = APPIcon.ico
其中,“AppIcon.ico”就是复制到项目源程序目录下的图标文件名称。这样设置后,编译后生成的可执行文件,以及主窗口的图标就换成设置的图标了。
4.10 运行结果
5. 源码下载
上述提到的实例的源码,有需要的可以从02-0-Qt 5.9 C++开发指南-GUI应用程序设计基础源码进行下载