✥2-GUI应用程序设计基础

本文详细介绍了QtWidgetApplication的项目结构,包括.pro文件、main.cpp、widget.ui、widget.h和widget.cpp的作用。讲解了QtDesigner如何创建组件、设置属性、布局管理以及信号与槽的关联。还探讨了对象名称的重要性,信号与槽的使用规则,以及如何通过代码化和混合方式设计UI。此外,还展示了如何在Qt中处理组件的事件,如按钮点击、文本变化等,以及如何自定义槽函数来响应这些事件。

Application->Qt Widget Application

  • 项目组织文件MyWidgets.pro,存储项目设置的文件。
  • 主程序入口文件main.cpp,实现main()函数的程序文件。
  • 窗体界面文件widget.ui,一个XML格式存储的窗体上的元件及其布局的文件。
  • widget.h是所设计的窗体类的头文件,widget.cpp是widget.h里定义类的实现文件。在C++里,任何窗体或界面组件都是用类封装的,一个类一般有一个头文件和一个源程序文件。

Hello.pro

QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

“QT += core gui” 表示项目中加入core gui模块。core gui是Qt用于GUI设计的类库模块,如果创建的控制台应用程序,就不需要添加core gui。
如果项目中使用到涉及数据库操作的类,就需要用到sql模块,在.pro文件中增加如下一行:

QT += sql

“greaterThan(QT_MAJOR_VERSION, 4): QT += widgets” 表示当Qt主版本大于4时,才加入widgets模块。

双击widget.ui,打开Qt Designer:

  • 组件面板:分为多个组,如Layouts、Buttons、Display Widgets等,界面设计的常见组件都可以在组件面板里找到。
  • Signals和Slots编辑器:可视化地进行信号与插槽的关联
  • Action编辑器:可视化地设计Action
  • 对象浏览器:用树状视图显示窗体上各组件之间的布局包含关系
  • 属性编辑器:显示某个选中的组件或窗体的各种属性及其取值

选中拖到待设计的窗体的Label组件:

可以看出QLabel的继承关系是QObject->QWidget->QFrame->QLabel

objectName表示组件的对象名称,界面上的每个组件都需要一个唯一的对象名称,以便被引用。

如果一个类继承自QLabel,并增加了几个自定义属性,那么在属性编辑器里也会多出几个增加的属性供修改。
标准C++语言里并没有property关键字,property是Qt对标准C++的扩展,使得在Qt Designer里就可以可视化设置类的数据。

为拖入的PushButton(objectName设置为“btnClose”)增加一个功能,就是单击此按钮,关闭窗口(objectName设置为“MyWidget”)。
步骤:Add,Sender选择closeBtn,Signal选择clicked(),Receiver选择窗体MyWidget,Slot选择close()。

main.cpp

#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[]) {
    QApplication a(argc, argv);// 定义并创建应用程序
    Widget w;// 定义并创建窗口
    w.show();// 显示窗口
    return a.exec();// 应用程序运行
}

QApplication是Qt的标准应用程序类。本例中QApplication类的实例a,就是应用程序对象。
Widget类继承自QWidget,它是本实例设计的窗口的类名,定义此窗口后再用w.show()显示此窗口。
a.exec()启动应用程序的执行,开始应用程序的消息循环和事件处理。

编译后在项目目录下会自动生成一个文件“ui_widget.h”。

  • widget.h:定义窗体类的头文件,定义了类Widget
  • ui_widget.h:编译后,根据窗体上的组件及其属性、信号与插槽的关联等自动生成的一个类的定义文件,类名称是Ui_MyWidget

widget.h

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
    class MyWidget;
}
QT_END_NAMESPACE

class Widget: public QWidget {
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::MyWidget *ui;
};
#endif

(1) namespace声明
这是声明了一个名称为Ui的命名空间,包含一个类MyWidget;这个类是ui_widget.h文件里定义的类,用于描述界面组件的。这个声明相当于一个外部类型声明。

(2) Widget类的定义
widget.h文件的主体部分是一个继承于QWidget的类Widget的定义,也是本实例的窗体类。
在Widget类中使用宏Q_OBJECT,这是使用Qt的信号与槽(signal and slot)机制的类都必须加入的一个宏。
在public部分定义了Widget类的构造函数和析构函数;在private部分又定义了一个指针Ui::MyWidget *ui;

widget.cpp

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::MyWidget) {
    ui->setupUi(this);
}

Widget::~Widget() {
    delete ui;
}

注意到,这个文件的包含文件部分自动加入了#include “ui_widget.h”,这就是Qt编译生成的与UI文件widget.ui对应的类定义文件。
其中,构造函数的意义是:执行父类QWidget的构造函数,创建一个Ui::MyWidget类的对象ui。
构造函数里只有一行语句,它是执行Ui::MyWidget类的setupUi()函数,这个函数实现窗口的生成与各种属性的设置、信号与槽的关联。
析构函数只是简单地删除用new创建的指针ui。

ui_widget.h

/********************************************************************************
** Form generated from reading UI file 'widget.ui'
**
** Created by: Qt User Interface Compiler version 6.2.4
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_WIDGET_H
#define UI_WIDGET_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MyWidget {
public:
    QLabel *label;
    QPushButton *btnClose;

    void setupUi(QWidget *qWidget) {
        if (qWidget->objectName().isEmpty())
            qWidget->setObjectName(QString::fromUtf8("MyWidget"));
        qWidget->resize(353, 158);
        
        label = new QLabel(qWidget);
        label->setObjectName(QString::fromUtf8("label"));
        label->setGeometry(QRect(130, 20, 101, 16));
        
        QFont font;
        font.setPointSize(12);
        font.setBold(true);
        label->setFont(font);
        
        btnClose = new QPushButton(qWidget);
        btnClose->setObjectName(QString::fromUtf8("btnClose"));
        btnClose->setGeometry(QRect(260, 130, 80, 18));

        retranslateUi(qWidget);
        
        QObject::connect(btnClose, &QPushButton::clicked, qWidget, qOverload<>(&QWidget::close));
        QMetaObject::connectSlotsByName(qWidget);
    }

    void retranslateUi(QWidget *qWidget) {
        qWidget->setWindowTitle(QCoreApplication::translate("MyWidget", "Widget", nullptr));
        label->setText(QCoreApplication::translate("MyWidget", "Hello World", nullptr));
        btnClose->setText(QCoreApplication::translate("MyWidget", "Close", nullptr));
    }
};

namespace Ui {
    class MyWidget: public Ui_MyWidget {};
}
QT_END_NAMESPACE

#endif

(1) 定义了一个类Ui_Widget,用于封装可视化设计的界面。
(2) 自动生成了界面各个组件的类成员变量定义。在public部分为界面上每个组件定义了一个指针变量,变量的名称就是设置的objectName。
(3) 定义了setupUi()函数,这个函数用于创建各个界面组件,并设置其位置、大小、文字内容、字体等属性,设置信号与槽的关联。
setupUi()调用了函数retranslateUi(qWidget),用来设置界面组件的文字内容属性,如标签的文字、按键的文字、窗体的标题等。将界面上的文字设置的内容独立出来作为一个函数,在设计多语言界面时会用到这个函数。
(4) QObject::connect(btnClose, &QPushButton::clicked, qWidget, qOverload<>(&QWidget::close));调用connect()函数,将在UI设计器里设置的信号与槽的关联转换为语句。这里将btnClose按键的clicked()信号与窗体qWidget的close()槽函数关联起来。这样,当单击btnClose按钮时,就会执行qWidget的close()槽函数,而close()槽函数的功能是关闭窗口。
QMetaObject::connectSlotsByName(qWidget);这句是设置槽函数的关联方式,用于将UI设计器自动生成的组件信号的槽函数与组件信号相关联。
(5) 定义namespace UI,并定义一个从Ui_MyWidget继承的类MyWidget。

Application->Qt Widget Application


通过UI Designer来完成下表给出的组件及属性:


类层次关系:
请添加图片描述
需要注意以下几点:
(1) objectName是窗体上创建的组件的【实例名称】,界面上的每个组件需要有一个唯一的objectName,程序里访问界面组件时是通过其objectName进行访问,自动生成的槽函数名称里也有objectName。所以,组件的objectName需要在设计程序之前设置好,设置好之后一般不要再改动。若设计程序之后再改动objectName,涉及的代码需要相应的改动。
(2) 窗体的objectName就是窗体的类名称,在UI设计器里不要修改窗体的objectName,窗体的实例名称需要在使用窗体的代码里去定义。

组件面板上用于而已的组件:

UI Designer工具栏各按钮的功能:

当组件都拖动到窗体上后,改变窗体大小,界面上的各组件却并不会自动改变大小。随后还需为窗体指定一个总布局;选中窗体(即不要选择任何组件),单击工具栏上的“Lay Out Vertically”按钮,使4组组件垂直分布;这样布局后,当窗体大小改变时,各组件都会自动改变大小。

在UI设计器里可视化设计布局时,要善于利用水平和垂直空格组件,善于设置组件的最大、最小宽度和高度来实现某些需要的布局效果。

伙伴关系(Buddy)是指界面上一个Label和一个组件相关联。在伙伴关系编辑状态下,单击一个Label,按住鼠标左键,然后拖向一个组件,就建立了Label和组件之间的伙伴关系。
伙伴关系是为了在程序运行时,在窗体上用快捷键快速将输入焦点切换到某个组件上。如,设定“姓名”标签的Text属性为“姓名(&N)”,其中符号“&”用来指定快捷字符,界面上并不显示“&”,这里指定快捷键字母N。那么在程序运行时,用户按下Alt+N,输入焦点就会快速切换到“姓名”关联的输入框内。

信号与槽
信号(Signal)就是在特定情况下被发射的事件;槽(Slot)就是对信号响应的函数。
槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
信号与槽关联是用QObject::connect()函数实现的,其基本格式是:

QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));

connect()是QObject类的一个静态函数,而QObject是所有Qt类的基类,所以在实际调用时可以省略掉前面的限定符“QObject::”。
sender - 发射信号的对象名称;signal() - 信号名称。
receiver - 接收信号的对象名称;slot() - 槽函数名称。
SIGNAL和SLOT是Qt的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串。

在示例中的ui_widget.h文件中,在setupUi()函数中有如下语句:

QObject::connect(btnClose, SIGNAL(clicked()), qWidget, SLOT(close()));

其作用就是将btnClose按钮的clicked()信号与窗体(Widget)的槽函数close()相关联,这样,当单击btnClose按钮时,就会执行qWidget的close()槽函数。

关于信号与槽的使用,有以下一些规则需要注意:
(1) 一个信号可以连接多个槽

connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(addFun(int));
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(aupdateStatus(int));

当一个对象spinNum的数值发生变化时,所在窗体的两个槽进行响应,一个addFun()用于计算,一个updateStatus()用于更新状态。
(2) 多个信号可以连接同一个槽

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) 当一个信号被发射时,与其关联的槽函数通常被立即执行。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。

单击chkBoxUnder组件,右键调出快捷菜单,单击“Go to slot…”,选择clicked(bool),自动生成on_chkBoxUnder_clicked(bool)的框架,添加三行代码,实现文本框字体下划线控制:

void QWDialog::on_chkBoxUnder_clicked(bool checked) {
    QFont font = ui->txtEdit->font();
    font.setUnderline(checked);
    ui->txtEdit->setFont(font);
}

同理,完成字体的斜体、加粗的控制。

查看编译生成的ui_qwdialog.h文件,发现只是在setupUi()函数里有一条信号与槽关联的语句:

QMetaObject::connectSlotsByName(QWDialog);

以上语句将搜索QWDialog界面上的所有组件,将信号与槽函数匹配的信号和槽关联起来,它假设槽函数的名称是:

void on_<object name>_<signal name>(<signal parameters>);

对于3个RadioButton,也可以采用可视化设计的方式设计其clicked()信号的槽函数,但这样就需要3个槽函数。我们可以简化,即只设计一个槽函数。
在QWDialog类的private slots部分增加一个槽函数定义如下:

void setTextFontColor();

将鼠标光标移动到这个函数的函数名上面,单击右键调出快捷菜单,单击“Refactor”->“Add Definition in qwdialog.cpp”,就会在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::white);
	ui->txtEdit->setPalette(plet);
}

在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()));
}

单击上方工具栏的“Edit Signals/Slots”,再按下鼠标左键,移动到窗体空白区域释放。
“确定”按钮(btnOK)对应:clicked(),QWDialog的槽函数accept()。
“取消”按钮(btnCancel)对应:clicked(),QWDialog的槽函数reject()。
“退出”按钮(btnClose)对应:clicked(),QWDialog的槽函数close()。

close()槽函数没有出现在弹出的列表框中,需要勾选“Show signals and slots inherited from QWidget”才会出现close()函数。

在编译生成的ui_qwdialog.h文件中多了3行代码:

QObject::connect(btnOK, SIGNAL(clicked()), QWDialog, SLOT(accept()));
QObject::connect(btnCancel, SIGNAL(clicked())::clicked, QWDialog, SLOT(reject()));
QObject::connect(btnClose, SIGNAL(clicked())::clicked, QWDialog, SLOT(close()));

现在,单击这3个按钮都会关闭程序。

代码化UI设计
Qt自带的实例基本都是用纯代码方式实现用户界面的。
创建一个Widget Application项目,不勾选“Generate form”复选框。创建后的项目文件目录树下没有.ui文件。

QWDlgManual类定义
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的槽函数
	void on_chkBoxItalic(bool checked);// Italic的槽函数
	void on_chkBoxBold(bool checked);// Bold的槽函数
	void setTextFontColor();// 设置字体颜色
public:
	QWDlgManual(QWidget *parent = 0);
	~QWDlgManual();
};

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("确定"));
    btnCancel = new QPushButton(tr("取消"));
    btnClose = new QPushButton(tr("退出"));

    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() {}


混合方式UI设计
要完成的效果如下:


Application->Qt Widget Application,本项目的窗口类从QMainWindow继承而来。

File->New File or Project…->Qt Resource File
在项目根目录下新建images文件夹,并拷贝要用到的图片资源到images目录里。

QAction是一个非常有用的类,在界面设计时创建Action,并编写其trigger()信号的槽函数。使用设计的Action可以创建菜单项、工具栏按钮,还可以设置为QToolButton按钮的关联Action。点击这些由Action创建的菜单项、按钮就是执行Action的槽函数。
设计好之后的Action列表如下图:

单击“Add Tool Bar”即可新建一个工具栏。
工具栏通过设置toolButtonStyle属性可以有多种显示方式,缺省是Qt::ToolButtonIconOnly,即只显示按钮的图标。还可以设置为:

  • Qt::ToolButtonTextBesideIcon——文字显示在按钮旁边
  • Qt::ToolButtonTextOnly——只显示文字
  • Qt::ToolButtonTextUnderIcon——文字显示在按钮下方

在窗体上放置一个QTextEdit组件,其objectName设置为txtEdit。

在编译生成的ui_qwmainwindow.h中,是这样设置菜单栏和工具栏的(只以“新建”按钮展示):

QIcon icon;
icon.addFile(QString::fromUtf8(":/images/images/new2.bmp"), QSize(), QIcon::Normal, QIcon::Off);
actNew->setIcon(icon);
actNew->setObjectName(QString::fromUtf8("actNew"));
actNew = new QAction(QWMainWindow);

menubar = new QMenuBar(QWMainWindow);
menubar->setObjectName(QString::fromUtf8("menubar"));
menubar->setGeometry(QRect(0, 0, 351, 17));

menu = new QMenu(menubar);
menu->setObjectName(QString::fromUtf8("menu"));

menubar->addAction(menu->menuAction());
menu->addAction(actNew);

toolBar = new QToolBar(QWMainWindow);
toolBar->setObjectName(QString::fromUtf8("toolBar"));

toolBar->addAction(actNew);

void retranslateUi(QMainWindow *QWMainWindow) {
	actNew->setShortcut(QCoreApplication::translate("QWMainWindow", "Ctrl+N", nullptr));
	actNew->setText(QCoreApplication::translate("QWMainWindow", "\346\226\260\345\273\272", nullptr));
	actNew->setToolTip(QCoreApplication::translate("QWMainWindow", "\347\262\230\350\264\264", nullptr));

	menu->setTitle(QCoreApplication::translate("QWMainWindow", "\346\226\207\344\273\266", nullptr));
}

需要编写一些代码来实现无法可视化设计的界面功能。
qwmainwindow.h

#ifndef QWMAINWIND_H
#define QWMAINWIND_H
#include <QMainWindow>
#include <QLabel>
#include <QProgressBar>
#include <QSpinBox>
#include <QFontComboBox>

namespace Ui {
    class QWMainWindow;
}

class QWMainWindow: public QMainWindow {
    Q_OBJECT
private:
    QString fCurFileName;// 当前文件名
    QLabel *fLabCurFile;// 状态栏里显示当前文件的Label
    QProgressBar *progressBar1;// 状态栏上的进度条
    QSpinBox *spinFontSize;// 字体大小
    QFontComboBox *comboFont;// 字体名称

    void updateCurFile(QString aFile);// 更新当前文件名,并更新状态栏提示
    void iniUI();// 程序设计的UI初始化
    void iniSignalSlots();// 手工关联信号与槽
    void createNew();
public:
    explicit QWMainWindow(QWidget *parent = 0);
    ~QWMainWindow();
private slots:
    // UI界面设计生成的槽函数
    void on_actFontBold_triggered(bool checked);// 粗体Action
    void on_actFontItalic_triggered(bool checked);// 斜体Action
    void on_actFontUnder_triggered(bool checked);// 下划线Action

    void on_actNew_triggered();// 新建文件
    void on_actOpen_triggered();// 打开文件
    void on_actFont_triggered();// 字体选择对话框

    void on_txtEdit_selectionChanged();// 当前选择的文字发生变化,更新粗体、斜体、下划线3个action的checked状态
    void on_txtEdit_copyAvailable(bool b);// 有文字可copy时更新cut,copy的Enable状态

    void on_actToolbarLab_triggered(bool checked);// 设置工具栏按钮样式

    // 自定义槽函数
    void on_spinBoxFontSize_valueChanged(int aFontSize);// 改变字体大小的SpinBox的响应
    void on_comboFont_currentIndexChanged(const QString &arg1);// FontCombobox的响应,选择字体名称
private:
    Ui::QWMainWindow *ui;
};
#endif

qwmainwindow.cpp

#include "qwmainwindow.h"
#include "ui_qwmainwindow.h"
#include <QFile>
#include <QFileDialog>
#include <QTextStream>
#include <QFontDialog>
#include <QCoreApplication>
#include <QLabel>
#include <QTextCharFormat>

void QWMainWindow::updateCurFile(QString aFile) {// 更新当前文件名,并更新状态栏提示
    fCurFileName = aFile;
    fLabCurFile->setText("当前文件:" + fCurFileName);
}

void QWMainWindow::iniUI() {
    // 状态栏
    fLabCurFile = new QLabel;// 用于显示当前文件名的标签
    fLabCurFile->setMinimumWidth(150);
    fLabCurFile->setText("当前文件:");
    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("字体大小 "));// 不引用的Label无需定义变量
    ui->mainToolBar->addWidget(spinFontSize);// SpinBox添加到工具栏

    ui->mainToolBar->addSeparator();// 工具栏上增加分隔条
    ui->mainToolBar->addWidget(new QLabel(" 字体 "));
    comboFont = new QFontComboBox;// 字体名称ComboBox
    comboFont->setMinimumWidth(150);// 设置组件最小宽度
    ui->mainToolBar->addWidget(comboFont);// 添加到工具栏

    setCentralWidget(ui->txtEdit);// 将txtEdit设置为【中心组件】,自动填充整个工作区
}

void QWMainWindow::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&)));
}

QWMainWindow::QWMainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::QWMainWindow) {
    ui->setupUi(this);

    iniUI();// 手工初始化UI
    iniSignalSlots();// 信号与槽关联
}

QWMainWindow::~QWMainWindow() {
    delete ui;
}

void QWMainWindow::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 QWMainWindow::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 QWMainWindow::on_actFont_triggered() {
    bool ok;
    QFont font = QFontDialog::getFont(&ok, this);
    if (ok)
    	ui->txtEdit->setFont(font);
}

void QWMainWindow::on_actNew_triggered() {// 新建文件
    ui->txtEdit->clear();
    updateCurFile("");
}

void QWMainWindow::on_spinBoxFontSize_valueChanged(int aFontSize) {// 改变字体大小的SpinBox的响应
    QTextCharFormat fmt;
    fmt.setFontPointSize(aFontSize);// 设置字体大小
    ui->txtEdit->mergeCurrentCharFormat(fmt);
    progressBar1->setValue(aFontSize);
}

void QWMainWindow::on_comboFont_currentIndexChanged(const QString &arg1) {// FontCombobox的响应,选择字体名称
    QTextCharFormat fmt;
    fmt.setFontFamily(arg1);// 设置字体名称
    ui->txtEdit->mergeCurrentCharFormat(fmt);
}

void QWMainWindow::on_actToolbarLab_triggered(bool checked) {
    if (checked)// 显示文字和图标
        ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    else// 只显示图标
        ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
}

void QWMainWindow::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 QWMainWindow::on_actFontItalic_triggered(bool checked) {
    QTextCharFormat fmt;
    fmt = ui->txtEdit->currentCharFormat();
    fmt.setFontItalic(checked);
    ui->txtEdit->mergeCurrentCharFormat(fmt);
}

void QWMainWindow::on_actFontUnder_triggered(bool checked) {
    QTextCharFormat fmt;
    fmt = ui->txtEdit->currentCharFormat();
    fmt.setFontUnderline(checked);
    ui->txtEdit->mergeCurrentCharFormat(fmt);
}

void QWMainWindow::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());// 是否有下划线
}

Action是一种不可见的界面元素,主要用于菜单项、工具栏按钮设计。Action的主要信号是trigger(),为一个Action的trigger()信号编写槽函数之后,菜单和工具栏上由此Action创建的菜单项和工具栏按钮就都关联此槽函数。

用于设置粗体、斜体和下划线的3个Action具有Checkable属性,选择trigger(bool)信号设计槽函数更合适,该信号会将Action的复选状态作为一个参数传递,在响应代码里可以直接使用。
其他Action可选择trigger()信号生成槽函数。在Action的“Go to slot”对话框,选择信号为Action创建槽函数。

下面是用于设置字体粗体的actFontBold槽函数代码,使用了trigger(bool)信号:

void QWMainWindow::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);
}

在本程序中,“剪切”“复制”“粘贴”的enabled属性应该随文本框文字选择的状态变化而变化。在主窗体上选择文本编辑框txtEdit,在快捷菜单里调用“Go to slot”对话框。对话框里列出了QTextEdit的所有信号,有两个可以利用的信号。

  • copyAvailable(bool)信号在有内容可以被复制时发射,并且传递一个布尔参数。
  • selectionChanged()信号在选择的文字发生变化时发射。

为txtEdit组件的这两个信号生成槽函数定义和函数体框架,如下:

// 有文字可copy时更新cut,copy的Enable状态
void QWMainWindow::on_txtEdit_copyAvailable(bool b) {
    ui->actCut->setEnabled(b);// 能否cut
    ui->actCopy->setEnabled(b);// 能否copy
    ui->actPaste->setEnabled(ui->txtEdit->canPaste()); // 能否paste
}
// 当前选择的文字发生变化,更新粗体、斜体、下划线3个action的checked状态
void QWMainWindow::on_txtEdit_selectionChanged() {
    QTextCharFormat fmt;
    fmt = ui->txtEdit->currentCharFormat();// 获取文字的格式
    ui->actFontItalic->setChecked(fmt.fontItalic());// 是否斜体
    ui->actFontBold->setChecked(fmt.font().bold());// 是否粗体
    ui->actFontUnder->setChecked(fmt.fontUnderline());// 是否有下划线
}

在界面上还有两个用代码创建的组件,用于设置字体大小的spinFontSize和用于设置字体的comboFont,这两个组件的信号与槽的功能实现显然不能通过可视化的方法来实现。
用于设置字体大小的SpinBox部分最合适的信号是valueChanged(int),用于字体设置的FontComboBox组件最合适使用currentIndexChanged(QString)信号。

qwmainwindow.h
// 自定义槽函数
void on_spinBoxFontSize_valueChanged(int aFontSize);// 改变字体大小的SpinBox的响应
void on_comboFont_currentIndexChanged(const QString &arg1);// FontCombobox的响应,选择字体名称
——————————————————————————————————————————————————————————
qwmainwindow.cpp
void QWMainWindow::iniSignalSlots() {// 信号与槽的关联,当函数带有参数时,必须写明参数的类型
    connect(spinFontSize, SIGNAL(valueChanged(int)), this, SLOT(on_spinBoxFontSize_valueChanged(int)));
    connect(comboFont, &QFontComboBox::currentFontChanged, ui->txtEdit, &QTextEdit::setCurrentFont);
}

void QWMainWindow::on_spinBoxFontSize_valueChanged(int aFontSize) {// 改变字体大小的SpinBox的响应
    QTextCharFormat fmt;
    fmt.setFontPointSize(aFontSize);// 设置字体大小
    ui->txtEdit->mergeCurrentCharFormat(fmt);
    progressBar1->setValue(aFontSize);
}

用Qt Creator创建的项目编译后的可执行文件具有默认图标,如果要为应用设置一个自定义图标,只需两步:

  • 将一个图标文件(必须是“.ico”后缀的图标文件)复制到项目源程序目录下
  • 将项目配置文件里用RC_ICONS设置图标文件名
RC_ICONS = AppIcon.icon// App.Icon就是复制到源程序目录下的图标文件名称
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值