使用Qt6 和现代 c++ 进行跨平台开发 6:使用Qt Widgets进行GUI设计
Qt Widgets是一个模块,它提供了一组用于构建经典UI的用户界面(UI)元素。在这一章中,将向您介绍Qt Widgets模块,并学习基本的Widgets。我们将看看什么是窗口小部件,以及可用于创建图形用户界面(GUI)的各种窗口小部件。除此之外,还将向您介绍Qt Designer的布局,您还将学习如何创建自己的自定义控件。我们将仔细看看Qt能为我们提供什么,让我们轻松设计出漂亮的图形用户界面。在这一章的开始,将向您介绍Qt提供的部件类型及其功能。之后,我们将完成一系列步骤,并使用Qt设计我们的第一个表单应用程序。然后你将学习样式表、Qt样式表(QSS文件)和主题化。
本章将涵盖以下主要主题:
介绍Qt部件
使用Qt Designer创建用户界面
管理布局
创建自定义小部件
创建Qt样式表和定制主题
探索定制风格使用小工具、窗口和对话框
到本章结束时,你将了解GUI元素及其对应的C++类的基础知识,如何不用一行代码就能创建自己的UI,以及如何使用样式表自定义UI的外观。
技术要求
本章的技术要求包括Qt 6.0.0 MinGW 64位、Qt Creator 4.14.0、Windows 10/Ubuntu 20.04/MAC OS 10.14。
注意
本章中使用的屏幕截图来自Windows环境。您将看到基于您电脑平台的类似屏幕。
Qt部件介绍
小部件是GUI的基本元素。它也称为用户界面控件。它接受来自底层平台的不同用户事件,如鼠标和键盘事件(以及其他事件)。我们使用不同的小部件创建ui。曾经有一段时间,所有的GUI控件都是从头开始编写的。Qt widgets通过开发带有现成GUI控件的桌面GUI来减少开发时间,Qt广泛使用了继承的概念。所有小部件都继承自QObject。QWidget是一个基本的小部件,是所有UI小部件的基类。它包含描述小部件所需的大部分属性,以及几何、颜色、鼠标、键盘行为、工具提示等属性。让我们看看下图中的QWidget继承层次结构:
大多数Qt小部件的名称都是不言自明的,因为它们以q开头,所以很容易识别。下面列出了其中一些名称:
- QPushButton用于命令应用程序执行某个动作。
- QCheckBox允许用户进行二值选择。
- QRadioBut ton允许用户从一组互斥选项中仅选择一个。
- QFrame显示一个框架。
- QLabel用于显示文本或图像。
- QLineEdit允许用户输入和编辑单行纯文本。
- QTabWidget用于显示多个选项卡式小部件以及与每个选项卡相关的页面。
使用Qt Widgets的一个好处就是它的继承系统。任何从QObject继承的对象都有父子关系。这种关系为开发人员带来了许多便利,例如:
- 当一个小部件被销毁时,由于父子层次结构,它的所有子部件也被销毁。这避免了内存泄漏。
- 使用findChild()和findChildren()可以找到给定QWidget类的子类。
- Qwidget中的子部件会自动出现在父部件中。
典型的C++程序在主程序返回时终止,但是在GUI应用程序中我们不能这样做,否则应用程序将无法使用。因此,我们需要GUI一直存在,直到用户关闭窗口。为了实现这一点,程序应该一直循环执行,直到GUI应用程序接收到用户输入事件。
#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();
记得在helloworld.pro中添加下面一行代码来启用Qt小部件模块:
QT += widgets
在修改.pro文件后,需要运行qmake。如果您使用命令行,请执行以下命令:
qmake
make
现在,点击Run按钮以构建和运行应用程序。你很快就会看到带有Hello World!字样的UI界面。如下图所示:
你也可以在windows中使用命令行控制台打开应用,如下:
helloworld.exe
或者执行一下命令,在linux发行版中运行这个应用程序:
./helloworld
在命令行模式下,如果应用程序找不到库,您可能会看到几个错误对话框。您可以将Qt库和插件文件复制到这个二进制文件夹来解决问题。为了避免这些问题,我们将坚持使用Qt Creator来构建和运行示例程序。
在本节中,我们学习了如何使用Qt Widgets模块创建一个简单的GUI。在下一节中,我们将探索可用的小部件并使用Qt Designer创建UI。
使用Qt Designer创建Ul
在开始学习如何设计自己的UI之前,先熟悉一下Qt Designer的界面。下面的截图显示了Qt Designer的不同部分。在设计我们的UI时,我们将逐步了解他们:
Qt Widgets模块附带了现成的小部件。所有这些小部件都可以在小部件框(Widget Box)部分找到。Qt提供了通过拖放方法创建UI的选项。通将它们从部件框区域拖放到表单编辑器区域(Form Editor)来研究这些部件。您可以通过拖动一个项目,然后在目标区域上按下并释放鼠标或触控板来完成此操作。
下面的屏幕截图显示了小部件框部分中可用的不同类型的小部件。我们在表单编辑器区域添加了几个现成的小部件,比如标签(Label)、按钮(Push Button)、单选按钮(Radio button)、复选框(Check Box)、组合框(Combo Box)、进度条(Progress Bar)和行编辑(Line Edit)。这些小部件是非常常用的小部件。您可以在属性编辑器(Property Editor)中浏览特定于小部件的属性:
您可以通过选择Form菜单下的View C++ Code选项看到下面的界面。这些是生成UI代码。您可以在创建动态UI时重用这些代码:
在本节中,我们熟悉了Qt Designer UI。你也可以在Qt Creator中打开.ui文件以显示相同界面。在下一节中,您将带你了解不同类型的界面布局以及如何使用它们。
管理布局
Qt提供了一组方便的布局管理类,可以在布局小部件中自动排列子小部件,以确保UI布局合理。QLayout类是所有布局部件的基类。您也可以通过重新实现setGeometry()、sizeHint()、addItem()、itemAt()、takeAt()和minimumSize()函数来创建自己的布局管理器。请注意,一旦布局管理器被删除,布局管理也将停止。
以下是主要布局类的简要描述:
- QVBoxLayout垂直排列小部件。
- QHBoxLayout水平排列小部件。
- QGridLayout将小部件排列在表格中。
- QFormLayout管理输入部件及其相关标签。
- QStackedLayout提供了堆叠布局,每次只能看到一个小部件
QLayout通过从Q0bject和QLayoutItem继承来使用多继承。QLayout的子类是QBoxLayout、QGridLayout、QFormLayout和QStackedLayout。QVBoxLayout和QHBoxLayout是从QBoxLayout继承而来的,增加了方向信息。
让我们使用Qt Designer模块来设计一些按钮布局。
QVBoxLayout
在QVBoxLayout类中,小部件是垂直排列的,它们在布局中从上到下排列。此时,您可以执行以下操作:
1.将四个按钮拖到表单编辑器上。
2.重命名按钮,并通过按下键盘上的Ctrl键来选择按钮。
3.在表单工具栏中,单击垂直布局按钮。您可以将鼠标悬停在工具栏上的垂直布局按钮上来找到它。
在下面的屏幕截图中,您可以看到按钮以自上而下的方式垂直排列:
你也可以通过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");
QVBoxLayout *verticalLayout = new QVBoxLayout(widget);
verticalLayout->addWidget(pushBtn1);
verticalLayout->addWidget(pushBtn2);
verticalLayout->addWidget(pushBtn3);
verticalLayout->addWidget(pushBtn4);
widget->show ();
这个程序演示了如何使用一个垂直布局对象。请注意,Qwi dget实例widget将成为应用程序的主窗口。在这里,布局被直接设置为顶层布局。使用addWidget()方法第一个按钮放在布局的顶部,而最后一个按钮位于布局的底部。addWidget()方法将一个小部件添加到布局的末尾,并带有拉伸因子和对齐方式。
如果您没有在构造函数中设置父窗口,那么您将不得不在后面使用QWidget::setLayout()来设置布局,并重新设置小部件实例的父窗口。 接下来,我们将看看QHBoxLayout类。
QHBoxLayout
在QHBoxLayout类中,小部件水平排列,并且从左到右对齐。
我们现在可以执行以下操作:
1.将四个按钮拖到表单编辑器上。
2.重命名按钮,并通过按下键盘上的Ctrl键来选择按钮。
3.在表单工具栏中,单击水平布局按钮。您可以通过将鼠标悬停在水平布局工具栏按钮上来找到它。
在这张屏幕截图中,您可以看到按钮以从左到右的方式水平排列:
你也可以使用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");
QHBoxLayout *horizontalLayout = new QHBoxLayout(widget);
horizontalLayout->addWidget(pushBtn1);
horizontalLayout->addWidget(pushBtn2);
horizontalLayout->addWidget(pushBtn3);
horizontalLayout->addWidget(pushBtn4);
widget->show ();
前面的示例演示了如何使用水平布局对象。与垂直布局示例类似,QWidget实例将成为应用程序的主窗口。在这种情况下,布局被直接设置为顶级布局。默认情况下,使用addWidget()方法添加的第一个按钮位于布局的最左侧,而最后一个按钮位于布局的最右侧。当小部件被添加到布局中时,可以通过使用setDirection()方法来改变方向。
在下一节中,我们将研究QGridLayout类。
QGridLayout
在QGridLayout类中,小部件通过指定行数和列数排列在表格中。它类似于具有行和列的网格状结构,小部件作为项目插入。在这里,我们应该做到以下几点:
1.将四个按钮拖到表单编辑器上。
2.重命名按钮,并通过按下键盘上的Ctrl键来选择按钮。
3.在表单工具栏中,单击网格布局按钮。您可以通过将鼠标悬停在工具栏上的“按网格布局”按钮上来找到它。
在下面的屏幕截图中,您可以看到按钮排列在一个网格中:
你也可以使用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();
前面的代码片段解释了如何使用grid layout对象。布局概念与前面几节中的相同。您可以从Qt文档中探索QFormLayout和QStackedLayout布局。让我们继续下一节,讨论如何创建您的定制小部件并将其导出到Qt Designer模块。
创建自定义小部件
Qt提供了现成的GUI元素。在Qt建模语言(QML)出现之后,Qt小部件并没有得到积极的开发,所以您可能需要一个更具体的小部件,并且可能希望让其他人也能使用它。自定义小部件可以是一个或多个Qt小部件的组合,也可以从头开始编写。我们将从QLabel创建一个简单的标签小部件,作为我们的第一个定制小部件。自定义小部件集合可以有多个自定义小部件。
按照以下步骤构建您的第一个Qt定制小部件库:
1.要在Qt中创建新的Qt定制小部件项目,单击菜单栏上的File菜单选项或按Ctrl + N。或者,您也可以单击位于欢迎屏幕上的New Project按钮。选择另一个项目模板,然后选择Qt Custom Designer小部件,如下面的屏幕截图所示:
2 在下一步中,将要求您选择项目名称和项目位置。您可以通过单击浏览导航到所需的项目位置...按钮。让我们将示例项目命名为MyWidgets。然后,单击“下一步”按钮进入下一个屏幕。以下屏幕截图说明了这一步骤:
在下一步中,您可以从一组工具包中选择一个工具包来构建和运行您的项目。要构建和运行项目,必须至少有一个工具包是活动的和可选择的。选择默认的桌面Qt 6.0.0 MinGW 64位套件。单击“下一步”按钮进入下一个屏幕。以下屏幕截图说明了这一步骤:
在这一步中,您可以定义您的自定义小部件类名和继承细节。让我们用类名MyLabel创建自己的定制标签。单击“下一步”按钮进入下一个屏幕。以下屏幕截图说明了这一步骤:
在下一步中,您可以添加更多自定义小部件来创建小部件集合。让我们用类名MyFrame创建自己的自定义框架。您可以在“描述”选项卡中添加更多信息,也可以在以后进行修改。单击表示小部件是容器的复选框,将框架用作容器。单击“下一步”按钮进入下一个屏幕。以下屏幕截图说明了这一步骤:
在这一步中,您可以指定集合类名和插件信息来自动生成项目框架。让我们将集合类命名为MyWidgetCollection。单击“下一步”按钮进入下一个屏幕。以下屏幕截图说明了这一步骤:
下一步是将您的定制小部件项目添加到已安装的版本控制系统中。您可以跳过此项目的版本控制。单击Finish按钮,用生成的文件创建项目。以下屏幕截图说明了这一步骤:
展开项目浏览器视图并打开mylabel.h文件。我们将修改内容以扩展功能。在自定义小部件类名前添加一个QDESIGNER_WIDGET_EXPORT宏,以确保在动态链接库(DLL)或共享库中正确导出该类。您的自定义小部件可以在没有这个宏的情况下工作,但是添加这个宏是一个很好的做法。插入宏后,必须将#include <QtDesigner >添加到头文件中。以下屏幕截图说明了这一步骤:
重要说明
在某些平台上,构建系统可能会删除Qt Designer模块创建新部件所需的符号,使它们不可用。使用QDESIGNER_WIDGET_EXPORT宏可以确保符号保留在这些平台上。这在创建跨平台库时很重要。在其他平台上没有副作用。
9.现在,打开mylabelplugin.h文件。您会发现plugin类继承自一个名为QDesignerCustomWidgetInterface的新类。这个类允许Qt Designer访问和创建定制的小部件。请注意,您必须按如下方式更新头文件,以避免出现不推荐使用的警告:
# include <QtUiPlugin/QDesignerCustomWidgetInterface>
你会发现mylabelplugin.h中自动创建了几个函数,不要删除这些函数。您可以在Qt设计器模块中出现的name()、group()和icon()函数中指定值。注意,如果没有在icon()中指定图标路径,那么Qt Designer将使用默认的Qt图标。下面的代码片段演示了group()函数:
QString MyFramePlugin::group() const
return QLatin1String("My Containers");
您可以在以下代码片段中看到,isContainer() 在 MyLabel 中返回 false,在 MyFrame 中返回 true,因为 MyLabel 并非设计用于容纳其他小部件。 Qt Designer 调用 createWidget() 来获取 MyLabel 或 MyFrame 的实例:
bool MyFramePlugin::isContainer() const
return true;
要创建具有已定义几何或任何其他属性的小部件,您可以在 domXML() 方法中指定这些。 该函数返回一个可扩展标记语言 (XML) 片段,小部件工厂函数使用该片段来创建具有已定义属性的自定义小部件。 让我们将 MyLabel 的宽度指定为 100 像素 (px),将高度指定为 16 px,如下所示:
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构建定制小部件集合库所需的所有信息。您可以在下面的代码片段中看到,该项目是一个库类型,并被配置为用作插件:
CONFIG += plugin debug_and_release
CONFIG += c++17
TARGET = $$qtLibraryTarget(
mywidgetcollectionplugin)
TEMPLATE = lib
HEADERS = mylabelplugin.h myframeplugin.h
mywidgetcollection.h
SOURCES = mylabelplugin.cpp myframeplugin.cpp \
mywidgetcollection.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)
include(myframe.pri)
14.我们已经完成了自定义小部件的创建过程。让我们运行qmake并在发布模式下构建库。右键单击项目并单击Build选项,如下面的屏幕截图所示。该项目将在几秒钟内构建完成,并将在内部发布文件夹中提供。在Windows平台上,可以手动复制mywidgetcollectionplugin.dll将插件库创建到d:\Qt\6.0.0\mingw81_64\plugins\designer路径。对于不同的操作系统,此路径和扩展名会有所不同:
15.我们已经创建了我们的自定义插件。现在,关闭插件项目并点击d:\Qt\6.0.0\mingw81_64\bin\designer.exe文件。您可以在Custom Widgets部分下看到MyFrame,如下图所示。单击“创建”按钮或使用小部件模板。您还可以通过进行特定于平台的修改,将自己的表单注册为模板。让我们使用Qt Designer提供的小部件模板:
16.您可以在底部左侧的小部件框部分看到我们的定制小部件。将MyLabel小部件拖到表单上。您可以在属性编辑器下找到创建的属性,如multiLine和fontCase以及QLabel属性,如下面的屏幕截图所示:
您还可以在以下 Qt 文档链接中找到包含示例的详细说明:
Creating Custom Widgets for Qt Designer
恭喜你!您已经成功创建了具有新属性的自定义小部件。 您可以通过组合多个小部件来创建复杂的自定义小部件。在下一节中,您将学习如何定制小部件的外观。
创建Qt样式表和定制主题
在上一节中,我们创建了自定义小部件,但是小部件仍然只具有本机外观。Qt提供了几种定制UI外观的方法。Qt样式表是改变小部件的外观和感觉的最简单的方法之一,不需要做太多复杂的编码。Qt样式表语法与超文本标记语言/级联样式表(HTML/CSS)语法相同。样式表由一系列样式规则组成。样式规则由选择器和声明组成。选择器指定将受样式规则影响的小部件,声明指定小部件的属性。样式规则的声明部分是一个包含在内的键值对属性列表{}并用分号分隔。
让我们来看看样式表上简单的QPushButtn语法,如下所示:
QPushButton {
color:green;
background-color:rgb(193,255,216);
}
您还可以通过使用样式表编辑器在Qt Designer中应用样式表来更改小部件的外观,如下所示:
1.打开Qt Designer模块并创建一个新表单。将按钮拖放到表单上。
2.然后,右键单击按钮或表单中的任意位置,以显示上下文菜单。
3. 下一步,点击 Change styleSheet...选项,如下屏幕截图:
4. 我们已经使用样式表创建了前面的外观,你也可以在属性编辑器中QWidget的修改样式表属性:
QPushButton {
background-color: rgb(193, 255, 216);
border-width: 2px;
border-radius: 6;
border-color: lime;
border-style: solid;
padding: 2px;
min-height: 2.5ex;
min-width: 10ex;
QPushButton:hover {
background-color: rgb(170, 255, 127);
QPushButton:pressed {
background-color: rgb(170, 255, 127);
font: bold;
在前例中,只要Push Button获得了外观样式,其他的小部件扔以原生样式显示。你也在样式表中通过设置按钮的名称(object name)创建不同的样式,如下:
QPushButton#pushButtonID
使用QSS文件
您可以将所有样式表代码组合在一个.qss文件。这有助于确保在所有屏幕上保持应用程序的外观一致。QSS文件类似于.css文件,包含GUI元素的外观定义,如颜色、背景色、字体和鼠标交互行为。可以用任何文本编辑器创建和编辑它们。然后把.qss文件添加到资源文件(.qrc)。.ui文件不是必须的,GUI控件可以通过代码动态创建。您可以将样式表应用于小部件或整个应用程序,如下面的代码片段所示。对于自定义小部件或表单,我们是这样做的:
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
setStyleSheet("QWidget { background-color: green }");
这里是如何把前例样式应用到整个应用中:
#include "mywidget.h"
#include <QApplication>
#include <QFile>
int main(int argc, char *argv[])
QApplication app(argc, argv);
QFile file(":/qss/default.qss");
file.open(QFile::ReadOnly);
QString styleSheet = QLatin1String(file.readAll());
app.setStyleSheet(styleSheet);
Widget mywidget;
mywidget.show();
return app.exec();
前面的程序演示了如何为整个Qt GUI应用程序使用样式表文件。您需要添加.qss文件到资源中。打开.qss文件,并将QSS规则作为参数传递给QApplication对象上的setStyleSheet()方法。您将看到所有窗体都应用了样式表。
在本节中,您了解了如何使用样式表定制应用程序的外观,但是还有其他方法可以改变应用程序的外观。这些方法取决于您的项目需求。在下一节中,您将了解自定义样式。
探索自定义样式
Qt提供了几个QStyle子类,模拟Qt支持的不同平台的样式。Qt GUI模块提供了这些样式。您可以构建自己的自定义样式,并将它们作为插件导出。Qt使用QStyle来呈现Qt小部件,以确保它们的外观和感觉与原生小部件一样。
在Unix分发版本中,你可以运行下面的命令获取Window样式UI。
./helloworld -style windows
您可以使用 QWidget::setStyle() 方法在单个小部件上设置样式。
创建自定义样式
您可以通过创建自定义样式来自定义GUI的外观和感觉。创建自定义样式有两种不同的方法。在静态方法中,您可以创建QStyle类的子类并重新实现虚函数来提供所需的行为,或者从头开始重写QStyle类。QCommonStyle一般用作基类,而不是QStyle。在动态方法中,您可以子类化QProxyStyle,并在运行时修改系统样式的行为。您还可以通过使用QStyle函数(如drawPrimitive()、drawItemText()和drawControl())来开发具有样式意识的自定义小部件。
本节是一个高级Qt主题。你需要深入了解Qt来创建你自己的风格插件。如果你是初学者,可以跳过这一节。您可以通过以下链接在Qt文档中了解QStyle类和自定义样式:
https:// doc.qt.io/qt-6/qstyle.h tml
使用自定义样式
有几种方法可以在Qt应用程序中应用自定义样式。最简单的方法是在创建QAapplicationion对象之前调用QAapplication::setstyle()静态函数,如下所示:
#include "customstyle.h"
int main(int argc, char *argv[])
QApplication::setStyle(new CustomStyle);
QApplication app(argc, argv);
Widget helloworld;