✿3-The Basics-使用Qt Widgets进行GUI设计

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了✿3-The Basics-使用Qt Widgets进行GUI设计相关的知识,希望对你有一定的参考价值。

目录


Qt Widgets是一个模块,它提供了一组用于构建经典UI的用户界面(UI)元素。在本章中,你将了解 Qt Widgets模块,并了解基本小部件。我们将了解什么是小部件,以及可用于创建图形用户界面(GUI)的各种小部件。除此之外,还将向你介绍 Qt Designer的布局,并学习如何创建自己的自定义控件。我们将仔细研究Qt在轻松设计外观光滑的GUI时能为我们提供什么。在本章的开头,你将了解Qt提供的小部件类型及其功能。之后,我们将完成一系列步骤,并使用Qt设计我们的第一个表单应用程序。然后,你将学习样式表、Qt样式表( Qt Style Sheets)(QSS文件)和主题化。

本章将介绍以下主要主题:

  • 介绍Qt小部件
  • 使用Qt Designer创建UI
  • 管理布局
  • 创建自定义小部件
  • 创建Qt样式表和自定义主题
  • 探索自定义样式
  • 使用小部件、窗口和对话框

本章结束时,你将了解GUI元素及其相应的C++类的基础知识,如何在不编写一行代码的情况下创建自己的UI,以及如何使用样式表自定义UI的外观。

介绍Qt widgets

widget(小部件)是GUI的基本元素,它也被称为UI控件。它从底层平台接受不同的用户事件,例如鼠标和键盘事件(以及其他事件)。我们使用不同的小部件创建UI。曾经有一段时间,所有GUI控件都是从头开始编写的。Qt小部件通过开发带有现成GUI控件的桌面GUI来减少时间,Qt广泛使用继承的概念。所有小部件都继承自QObjectQWidget是一个基本的小部件,是所有UI小部件的基类;它包含描述小部件所需的大多数属性,以及几何体、颜色、鼠标、键盘行为、工具提示等属性。让我们看看下图中的QWidget继承层次结构:

  • QPushButton 用于命令应用程序执行特定操作。
  • QCheckBox 允许用户进行二进制选择。
  • QRadioButton 允许用户从一组相互排斥的选项中仅做出一个选择。
  • QFrame 显示一个框架。
  • QLabel 用于显示文本或图像。
  • QLineEdit 允许用户输入和编辑单行纯文本。
  • QTabWidget 用于在选项卡式窗口小部件的堆栈中显示与每个选项卡相关的页面。

使用Qt小部件的优点之一是它的继承系统。从QObject继承的任何对象都具有父子关系。这种关系为开发人员提供了许多便利,例如:

  • 当一个小部件被销毁时,由于父子层次结构,它的所有子部件也会被销毁。这样可以避免内存泄漏。
  • 通过使用findChild()和findChildren()可以找到给定QWidget类的子类。
  • Qwidget中的子部件会自动出现在父部件中。

典型的C++程序在主main()函数返回时终止,但在GUI应用程序中,我们不能这样做,或者应用程序将无法使用。因此,我们需要GUI一直存在,直到用户关闭窗口。为了实现这一点,程序应该在循环中运行,直到发生这种情况。GUI应用程序等待用户输入事件。

让我们使用简单的GUI程序,使用QLabel显示一段文本:

#include <QApplication> 
#include <QLabel> 
int main(int argc, char *argv[])  
	QApplication app(argc, argv); 
	QLabel myLabel; 
	myLabel.setText("Hello World!"); 
	myLabel.show(); 
	return app.exec(); 

记住在.pro文件中添加以下行,用于启用Qt Widgets模块:

QT += widgets

使用Qt Designer创建UI


Qt Widgets模块附带了现成的小部件。Qt提供了一个通过拖放方法创建UI的选项。让我们通过简单地将这些小部件从小部件框区域拖动到表单编辑器区域来探索它们。在项目到达表单编辑器区域之前,不要释放鼠标或触控板。下面的屏幕截图显示了小部件框部分中可用的不同类型的小部件。我们在表单编辑器区域中添加了一些现成的小部件,如标签、按钮、单选按钮、复选框、组合框、进度条和行编辑。这些小部件是非常常用的小部件。你可以在 [属性编辑器] 中浏览特定于小部件的属性。

你可以预览UI:Tools->Form Editor->Preview

还可以查看UI对应的C++代码:Tools->C+±>Inspect C++ Code Model…

管理布局

Qt提供了一组方便的布局管理类,可以在另一个小部件中自动排列子小部件,以确保UI保持可用。QLayout类是所有布局管理器的基类。还可以通过重新实现setGeometry()、sizeHint()、addItem()、itemAt()、takeAt()和minimumSize()函数来创建自己的布局管理器。请注意,一旦删除布局管理器,布局管理也将停止。以下列表简要介绍了主要布局类:

  • QVBoxLayout 垂直排列小部件。
  • QHBoxLayout 水平排列小部件。
  • QGridLayout 在网格中布局小部件。
  • QFormLayout 管理各种形式的输入小部件及其相关标签。
  • QStackedLayout 提供了一组小部件,一次只能看到一个小部件。

QLayout通过从QObject和QLayoutItem继承来使用多个继承。QLayout的子类是QBoxLayout、QGridLayout、QFormLayout和QStackedLayout。QVBoxLayout和QHBoxLayout继承自QBoxLayout,并添加了方向信息。让我们使用Qt Designer模块来布置几个QPushButtons。

QVBoxLayout:

对应的完整C++代码:

#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow 
public:
    QWidget *centralWidget;
    QVBoxLayout *verticalLayout;
    QPushButton *pushButton;
    QPushButton *pushButton_2;
    QPushButton *pushButton_3;
    QPushButton *pushButton_4;
    QMenuBar *menuBar;
    QToolBar *mainToolBar;
    QStatusBar *statusBar;

    void setupUi(QMainWindow *MainWindow) 
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QStringLiteral("MainWindow"));
        MainWindow->resize(378, 282);
        centralWidget = new QWidget(MainWindow);
        centralWidget->setObjectName(QStringLiteral("centralWidget"));
        verticalLayout = new QVBoxLayout(centralWidget);
        verticalLayout->setSpacing(6);
        verticalLayout->setContentsMargins(11, 11, 11, 11);
        verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
        pushButton = new QPushButton(centralWidget);
        pushButton->setObjectName(QStringLiteral("pushButton"));

        verticalLayout->addWidget(pushButton);

        pushButton_2 = new QPushButton(centralWidget);
        pushButton_2->setObjectName(QStringLiteral("pushButton_2"));
        pushButton_2->setMinimumSize(QSize(366, 15));

        verticalLayout->addWidget(pushButton_2);

        pushButton_3 = new QPushButton(centralWidget);
        pushButton_3->setObjectName(QStringLiteral("pushButton_3"));

        verticalLayout->addWidget(pushButton_3);

        pushButton_4 = new QPushButton(centralWidget);
        pushButton_4->setObjectName(QStringLiteral("pushButton_4"));

        verticalLayout->addWidget(pushButton_4);

        MainWindow->setCentralWidget(centralWidget);
        menuBar = new QMenuBar(MainWindow);
        menuBar->setObjectName(QStringLiteral("menuBar"));
        menuBar->setGeometry(QRect(0, 0, 378, 17));
        MainWindow->setMenuBar(menuBar);
        mainToolBar = new QToolBar(MainWindow);
        mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
        MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
        statusBar = new QStatusBar(MainWindow);
        statusBar->setObjectName(QStringLiteral("statusBar"));
        MainWindow->setStatusBar(statusBar);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
     // setupUi

    void retranslateUi(QMainWindow *MainWindow) 
        MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", Q_NULLPTR));
        pushButton->setText(QApplication::translate("MainWindow", "PushButton1", Q_NULLPTR));
        pushButton_2->setText(QApplication::translate("MainWindow", "PushButton2", Q_NULLPTR));
        pushButton_3->setText(QApplication::translate("MainWindow", "PushButton3", Q_NULLPTR));
        pushButton_4->setText(QApplication::translate("MainWindow", "PushButton4", Q_NULLPTR));
     // retranslateUi
;

namespace Ui 
    class MainWindow: public Ui_MainWindow ;
 // namespace Ui

QT_END_NAMESPACE

这个程序演示了如何使用垂直布局对象。请注意,QWidget实例widget将成为应用程序的主窗口。在这里,布局被直接设置为顶层布局。添加到addWidget()方法的第一个按钮占据版面的顶部,而最后一个按钮占据版面的底部。
如果没有在QWidget构造函数中设置父窗口,那么以后必须使用QWidget::setLayout()安装布局并重新分配到小部件实例。

QGridLayout:
你还可以通过C++代码动态添加网格布局,如下面的代码片段所示:

QWidget *widget = new QWidget; 
QPushButton *pushBtn1 = new QPushButton( "Push Button 1"); 
QPushButton *pushBtn2 = new QPushButton( "Push Button 2"); 
QPushButton *pushBtn3 = new QPushButton( "Push Button 3"); 
QPushButton *pushBtn4 = new QPushButton( "Push Button 4"); 
QGridLayout *gridLayout = new QGridLayout(widget); 
gridLayout->addWidget(pushBtn1); 
gridLayout->addWidget(pushBtn2); 
gridLayout->addWidget(pushBtn3); 
gridLayout->addWidget(pushBtn4); 
widget->show();

创建自定义部件

自定义小部件可以是一个或多个放置在一起的Qt小部件的组合,也可以从头开始编写。我们将从QLabel创建一个简单的标签小部件,作为我们的第一个自定义小部件。一个自定义小部件集合可以有多个自定义小部件。
New->Other Project->Qt Custom Designer Widget,步骤如下:



工程目录:

展开Project Explorer视图并打开mylabel.h文件。我们将修改内容以扩展功能。在自定义小部件类名之前添加QDESIGNER_WIDGET_EXPORT宏,以确保在动态链接库(dynamic-link library:DLL)或共享库中正确导出该类。你的自定义小部件可能在没有此宏的情况下工作,但添加此宏是一个很好的做法。插入宏后,必须将#include<QtDesigner>添加到头文件中。mylabel.h修改如下:

#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
#include <QtDesigner>
class QDESIGNER_WIDGET_EXPORT MyLabel: public QLabel 
    Q_OBJECT
    Q_PROPERTY(bool multiLine READ isMultiLine WRITE setMultiLine)
    Q_PROPERTY(Qt::GlobalColor color READ getColor WRITE setColor)
private:
    bool m_isMultiLine = false;
    Qt::GlobalColor m_color = Qt::GlobalColor::black;
public:
    MyLabel(QWidget *parent = 0);
    void setMultiLine(bool isMultiLine);
    bool isMultiLine() const  return m_isMultiLine; 
    void setColor(Qt::GlobalColor color);
    Qt::GlobalColor getColor()  return m_color; 
;
#endif

mylabel.cpp代码如下:

#include "mylabel.h"
MyLabel::MyLabel(QWidget *parent): QLabel(parent) 
void MyLabel::setMultiLine(bool isMultiLine)  m_isMultiLine = isMultiLine; 
void MyLabel::setColor(Qt::GlobalColor color)  m_color = color; 

注:
在某些平台上,构建系统可能会删除Qt Designer模块创建新小部件所需的符号,使其无法使用。使用QDESIGNER_WIDGET_EXPORT宏可确保符号保留在这些平台上。这在创建跨平台库时非常重要。在其他平台上没有副作用。

mylabelplugin.h

#ifndef MYLABELPLUGIN_H
#define MYLABELPLUGIN_H
#include <QDesignerCustomWidgetInterface>

class MyLabelPlugin : public QObject, public QDesignerCustomWidgetInterface 
    Q_OBJECT
    Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
    MyLabelPlugin(QObject *parent = 0);
    
    bool isContainer() const;
    bool isInitialized() const;
    QIcon icon() const;
    QString domXml() const;
    QString group() const;
    QString includeFile() const;
    QString name() const;
    QString toolTip() const;
    QString whatsThis() const;
    QWidget *createWidget(QWidget *parent);
    void initialize(QDesignerFormEditorInterface *core);
private:
    bool m_initialized;
;
#endif

可以看到MyLabelPlugin类继承自类QDesignerCustomWidgetInterface,这个类允许Qt Designer去访问和创建自定义小部件。
请注意,你得更改头文件如下,以避免depressed warnings,如下:

修改为如下,则不报depressed warnings:

#include <QtUiPlugin/QDesignerCustomWidgetInterface>

mylabelplugin.cpp

#include "mylabel.h"
#include "mylabelplugin.h"
#include <QtPlugin>
MyLabelPlugin::MyLabelPlugin(QObject *parent): QObject(parent) 
    m_initialized = false;

void MyLabelPlugin::initialize(QDesignerFormEditorInterface * /* core */) 
    if (m_initialized)
        return;
    // Add extension registrations, etc. here
    m_initialized = true;

bool MyLabelPlugin::isInitialized() const  return m_initialized; 
QWidget *MyLabelPlugin::createWidget(QWidget *parent)  return new MyLabel(parent); 
QString MyLabelPlugin::name() const  return QLatin1String("MyLabel"); 
QString MyLabelPlugin::group() const  return QLatin1String(""); 
QIcon MyLabelPlugin::icon() const  return QIcon(); 
QString MyLabelPlugin::toolTip() const  return QLatin1String(""); 
QString MyLabelPlugin::whatsThis() const  return QLatin1String(""); 
bool MyLabelPlugin::isContainer() const  return false; 
QString MyLabelPlugin::domXml() const 
    return QLatin1String("<widget class=\\"MyLabel\\" name=\\"myLabel\\">\\n</widget>\\n");

QString MyLabelPlugin::includeFile() const  return QLatin1String("mylabel.h"); 

#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(mylabelplugin, MyLabelPlugin)
#endif

请不要删除生成的函数。你可以给name()、group()、icon()指定值。注意,如果你不给icon()指定值,Qt Designer会使用默认的Qt icon。

你可以看到,isContainer()返回false在MyLabelMyFrame返回true。因为MyLabel不是设计用来作为其他小部件的容器的,而MyFrame在创建时就勾选了“The widget is a container”。
Qt Designer调用createWidget()去生成一个MyLabelMyFrame的实例。

我们指定myframeplugin.cpp中的group()值,如下:

QString MyFramePlugin::group() const  return QLatin1String("My Container"); 

要创建具有自定义几何体或任何其他属性的小部件,则在domXML()方法中指定这些属性。该函数返回可扩展标记语言(XML)片段,小部件工厂(the widget factory)使用该片段创建具有定义属性的自定义小部件。
让我们指定MyLable宽=100px,高=16px,如下:

QString MyLabelPlugin::domXml() const  
	return "<ui language=\\"c++\\" displayname=\\"MyLabel\\">\\n" 
		   		"<widget class=\\"MyLabel\\" name=\\"myLabel\\">\\n" 
					"<property name=\\"geometry\\">\\n" 
						"<rect>\\n" 
							"<x>0</x>\\n" 
							"<y>0</y>\\n" 
							"<width>100</width>\\n" 
							"<height>16</height>\\n" 
						"</rect>\\n" 
					"</property>\\n" 
					"<property name=\\"text\\">\\n" 
						"<string>MyLabel</string>\\n" 
					"</property>\\n" 
				"</widget>\\n" 
			"</ui>\\n";

现在我们看看MyWidgets.pro文件,如下。
它包含qmake所需的所有信息去构建自定义小部件集合库。

#MyWidgets.pro
CONFIG      += plugin debug_and_release
TARGET      = $$qtLibraryTarget(mylabelplugin)
TEMPLATE    = lib
HEADERS     = mylabelplugin.h
SOURCES     = mylabelplugin.cpp
RESOURCES   = icons.qrc
LIBS        += -L.
greaterThan(QT_MAJOR_VERSION, 4) 
    QT += designer
 else 
    CONFIG += designer

target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS    += target
include(mylabel.pri)

可以看到,这个工程是一个库类型,并且被配置用作一个插件。

现在,让我们运行qmake并在Release模式下构建这个库。

Build后,在项目所在目录生成如下文件夹:

在Windows平台上,可以手动将新生成的文件夹里的release目录下的mywidgetcollectionplugin.dll复制到D:\\Qt\\6.0.0\\mingw81_64\\plugins\\designer路径下。这个路径及文件扩展名依不同操作系统而异。如果不做这样的拷贝操作,就无法在designer.exe窗口看到我们自定义的插件。

此时,我们已经创建了我们自定义的插件。
现在,在D:\\Qt\\6.0.0\\mingw81_64\\bin目录下找到designer.exe,双击打开。
可以看到我们自定义的部件:

单击“创建”按钮,将MyLabel拖到MyFrame里面。

你还可以在属性编辑器中看到创建的MyLabel的属性:自定义QLabel下的multiLine、color属性。

你现在已经成功创建了你的自定义部件,并拥有新的属性。你也可以通过组合不同的部件创建更复杂的部件。

你还可以在以下Qt文档链接中找到带有示例的详细说明:
https://doc.qt.io/qt-6/designer-creating-custom-widgets.html

创建Qt样式表和自定义主题

Qt提供了几种定制UI外观的方法。Qt样式表(Qt Style Sheet)是在不进行复杂编码的情况下改变小部件外观的最简单方法之一。Qt样式表语法与超文本标记语言(html)/级联样式表(CSS)语法相同。样式表由一系列样式规则组成。样式规则由选择器和声明组成。选择器指定将受样式规则影响的小部件,声明指定小部件的属性。样式规则的声明部分是一个属性列表,作为键值对,包含在中,用分号分隔。

让我们来看一个简单的QPushButton样式表语法:

QPushButtoncolor:green; background-color:rgb以上是关于✿3-The Basics-使用Qt Widgets进行GUI设计的主要内容,如果未能解决你的问题,请参考以下文章

✿3-The Basics-使用Qt Widgets进行GUI设计

✿3-The Basics-GUI Design Using Qt Widgets

✿3-The Basics-GUI Design Using Qt Widgets

Flutter 学习 Basics Widget

✿4-The Basics-Qt Quick and QML

✿4-The Basics-Qt Quick and QML