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

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了✥2-GUI应用程序设计基础相关的知识,希望对你有一定的参考价值。

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"以上是关于✥2-GUI应用程序设计基础的主要内容,如果未能解决你的问题,请参考以下文章

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

PyQt5学习笔记2-GUI编程基础-2

如何在 SwiftUI 中制作斜体圆形样式系统文本?

(转)面向对象——UML类图设计

21种JavaScript设计模式最新记录(含图和示例)

Word 2016中公式不能自动斜体的解决方法