Qt for Beginners/zh

From Qt Wiki
< Qt for Beginners
Revision as of 14:30, 8 November 2025 by YangShen (talk | contribs) (Translate the tutorial from English to Simplified Chinese)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

Warning : 请确保开始前您已有一些 C++ 基础知识。

Remark : 本教程系列主要针对 Qt4。即便教程中的大部分同样适用于 Qt5,Qt5 的情况将在另一部分单独讨论。

C++ 提醒

一个方法或函数的签名也可以被称为它的原型。它完整地描述了一个方法或函数。它包含返回类型、方法名或者函数名(包含类名)以及参数和参数类型。

Type MyObject::myFunction(
 Type1 param1,
 Type2 *param2,
 const Type3 &param3
);

刚刚接触 Qt?不确定从哪里开始?那么这页 wiki 就是为您准备!这是一个循序渐进的教程,介绍了 Qt 的所有特性和功能。想要学习更多?请查阅 C++ GUI Classes for Qt 5C++ GUI Classes for Qt 6

Qt 简介

Qt (读作 cute 而非分 QT 两个字母) 是一个跨平台的框架,通常被用作图形化工具包,虽然它在创建 CLI 应用方面也很有帮助。它可以运行在三个主流的桌面操作系统上,也可以在手机操作系统上,包括 Symbian、Nokia Belle、Meego Harmattan、MeeGo 或者 BB10 以及嵌入式设备上。Android (Necessitas) 和 iOS 的移植也在开发中。

Qt 拥有令人印象深刻的模块集合,其中包括

  • QtCore 基础库,提供了容器、线程管理、事件管理以及其他
  • QtGuiQtWidgets 桌面图形工具包,提供了非常多用于设计应用程序的图形化组件。
  • QtNetwork 提供了处理网络通信的类集
  • QtWebkit webkit 引擎,能够在 Qt 应用程序中展示网页或者 web 应用。
  • QtSQL 功能齐全的 SQL 关系数据库管理抽象层,可以通过自定义驱动程序进行扩展,ODBC、SQLite、MySQL 以及 PostgreSQL 的支持开箱即用
  • QtXML 支持简单 XML 解析(SAX)以及 DOM
  • QtXmlPatterns 支持 XSLT、XPath、XQuery 和模式验证

安装 Qt SDK

开始创建 Qt 应用前,您需要获取 Qt 库以及一个 IDE (如果您有需要)。这些库可以从源码构建,或者更好的方式是从下载页面下载 SDK。

这个 SDK 包含了很多功能,比如为 Symbian 和 Nokia N9 的交叉编译器。您可以在 custom install 里选择不安装某些功能。但请务必包含这些包

  • QMake Documentation
  • Qt Documentation
  • Qt 4.8.1 (Desktop) 假设 Qt 4.8.1 是最新版本
  • Qt Creator

这些包同样比较有用

  • Qt Examples
  • Qt Linguist

如果您想为 Symbian、Maemo、Meego 开发应用,您也可以选择其他需要的包,或者旧版本的 Qt。

NB:在 Linux 系统上最好使用您的发行版所提供的 Qt 包。Qt Creator 在几乎所有的发行版里均可用,并且安装它的同时也会安装所有的依赖,比如库、编译器和开发所使用的头文件。

Note: 参见另一篇指南 Getting Started with Qt Widgets

我们现在即将创建首个窗体程序。和以往一样,一个 hello world

Qt Creator 功能

在编写我们的第一个 GUI 应用程序之前,让我们先来了解一下 Qt Creator。

Qt Creator 是另一款 C++ 集成开发环境 (IDE),但它非常适合编写 Qt 应用程序。它提供文档浏览器和“设计器”,使窗口创建更加便捷,所有功能都集成在一个设计精良的用户界面中。它也是目前速度最快的 IDE 之一。

我们的首个窗体应用

让我们从创建第一个项目开始。这将是一个空项目,所以我们需要执行以下操作: File > New file or project > Other Projects > Empty Qt Project

Beginners-01-NewProject.jpg

按照向导操作,选择项目文件夹和名称,并选择要使用的 Qt 版本后,您应该会看到此页面。

Beginners-02-HelloWorldPro.jpg

这是项目文件(扩展名 .pro)。Qt 使用命令行工具解析这些项目文件,以生成 Makefile 文件,编译器使用这些文件来构建应用程序。这个工具叫作 qmake。但是,我们不必太在意 qmake,因为 Qt Creator 可以帮我们完成这项工作。

项目文件中,有一些最基本的代码是必须始终编写的:

TEMPLATE = app
TARGET = name_of_the_app

QT = core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
  • TEMPLATE 描述要构建的类型。它可以是应用程序、库,或者仅仅是子目录。
  • TARGET 是应用程序或库的名称。
  • QT 用于指示此项目中使用的库(Qt 模块)。由于我们的第一个应用程序是一个小型 GUI,因此我们需要 QtCore 和 QtGui。

现在让我们添加应用程序的入口点。通过 File > New file or project > C++ > C++ Source file 可以完成这个步骤。

Beginners-03-NewFile.jpg

再次按照向导操作,将文件命名为“main”,就完成了。 你会注意到,在项目文件中,Qt Creator 自动添加了一行新代码:

TEMPLATE = app
TARGET = name_of_the_app

QT = core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

SOURCES +=  main.cpp

可以通过以下方式手动添加头文件和源文件

HEADERS += first_file.h second_file.h
SOURCES += first_file.cpp second_file.cpp

或者

HEADERS += first_file.h  second_file.h
SOURCES += first_file.cpp  second_file.cpp

如果您使用 Qt Creator 的向导,则会自动完成此操作。

Qt 应用程序的最小源代码是

#include <QApplication>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);
 return app.exec();
}

QApplication 是一个非常重要的类。它负责处理输入参数,但还负责处理很多其他事情,其中最值得注意的是事件循环(event loop)。事件循环是 GUI 应用程序中等待用户输入的循环。

当调用 app.exec() 时,事件循环启动。

让我们编译这个应用程序。点击左下角的绿色箭头,Qt Creator 将编译并运行它。结果如何呢?应用程序似乎已经启动,但没有响应。这其实是正常的。事件循环正在运行并等待事件,例如 GUI 上的鼠标点击,但我们没有提供任何要处理的事件,所以它会无限期地运行下去。

我们来添加一些要显示的内容。

#include <QApplication>
#include <QPushButton>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 QPushButton button ("Hello world !");
 button.show();

 return app.exec();
}

编译完成后您就看到了我们的第一个窗口!

Beginners-04-FirstWindow.jpg

Qt 程序是如何编译的

Qt Creator 负责为我们调用构建系统,但了解 Qt 程序是如何编译的可能很有趣。

对于小型程序,手动编译所有文件、创建目标文件并链接它们很容易。但对于大型项目,编写命令行很容易变得繁琐。如果您熟悉 Linux,您可能知道所有程序都是使用 Makefile 编译的,Makefile 描述了所有要执行的命令行。但对于某些项目来说,即使编写 Makefile 也可能变得很繁琐。

qmake 是 Qt 自带的构建系统,它负责生成 Makefile 文件(虽然还有其他构建系统可以使用,但这里我们以 qmake 为例)。它语法简单,可以生成用于编译 Qt 程序的 Makefile 文件。但这并非它的唯一用途。Qt 使用元对象来扩展 C++ 的功能,而 qmake 负责生成包含元对象提取阶段的 Makefile 文件。您将在另一章节中看到这部分内容。

因此,Qt 应用程序的编译分为3个步骤

  1. 一个 .pro 文件用来描述所要构建的项目
  2. 一个 Makefile 通过 qmake 生成
  3. 程序通过 make (或者在 Windows 系统上通过 nmake 或者 jom)构建

一个漂亮的按钮

本章概述了控件(widgets)模块。内容包括控件属性、控件中使用的继承方案以及父子关系系统。

现在我们有了按钮,我们可能需要对其进行一些自定义。

Beginner-Pretty-Button.jpg

Qt 对象有很多属性,可以使用 getter 和 setter 方法进行修改。在 Qt 中,如果一个属性名为 foo,则其关联的 getter 和 setter 方法将具有以下签名:

T foo() const;
void setFoo(const T);

实际上,Qt 将属性、getter 和 setter 方法的系统扩展为一个名为 property(属性)的概念。属性是任何类型的值,可以访问、修改或保持常量,并且可以通知更改。属性系统非常有用,尤其是在第三部分(QML)中。目前,我们将使用 attribute(属性)或 property(属性)来表示相同的功能。

QPushButton 具有许多属性:

  • text
  • font
  • tooltip
  • icon
  • ...

因此,我们可以利用这些参数来自定义按钮。

我们先来修改文本并添加工具提示。

#include <QApplication>
#include <QPushButton>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 QPushButton button;
 button.setText("My text");
 button.setToolTip("A tooltip");
 button.show();

 return app.exec();
}

结果如下:

Beginner-Test-Widget-Tooltip.jpg

我们还可以更改字体。在 Qt 中,字体用 QFont 类表示。文档提供了大量信息。我们这里特别关注的是 QFont 的一个构造函数。

QFont(const QString & family, int pointSize = 1, int weight = -1, bool italic = false)

为了改变字体,我们需要实例化一个 QFont 类,然后通过 setFont 方法把它传递给 QPushButton。以下代码片段会将字体更改为 Courier。

QFont font ("Courier");
button.setFont(font);

您可以尝试 QFont 构造函数的其他参数来重现本章第一张图片中所示的按钮。

设置图标也并不难。图标用 QIcon 类表示。只要图标在文件系统中具有绝对路径(或相对路径),就可以创建图标。建议在这个例子中提供绝对路径。但出于部署方面的考虑,您可以使用相对路径,或者更好的选择是使用资源系统。

QIcon icon ("/path/to/my/icon/icon.png");
button.setIcon(icon);

在 Linux 和其他一些操作系统中,可以通过图标主题轻松设置图标。可以使用静态方法来实现:

QIcon Qicon::fromTheme ( const QString &name, const QIcon &fallback = QIcon());

例如,在本章开头的截图中,笑脸图标来自 Oxygen KDE 图标主题,由以下用户设置:

button.setIcon(QIcon::fromTheme("face-smile"));

Qt 类层次结构

Qt 广泛使用继承,尤其是在 Widgets 模块中。下图展示了一些继承关系:

Beginner-Class-Hierarchy.jpg

QObject 是 Qt 中最基本的类。Qt 中的大多数类都继承自该类。QObject 提供了一些非常强大的功能,例如:

  • object name 你可以给对象设置一个字符串形式的名称,然后按名称搜索对象。
  • parenting system (如下节所述)
  • signals and slots (下一章将对此进行详细描述)
  • event management

Widgets 能够响应事件,并使用父子系统、信号和槽机制。所有 Widgets 都继承自 QObject。最基本的 widget 是 QWidget。QWidget 包含用于描述窗口或控件的大多数属性,例如位置和大小、鼠标光标、工具提示等。

Remark : 在 Qt 中,widget 也可以是窗口。上一节中,我们展示了一个按钮,它虽然是一个控件,但直接显示为一个窗口,无需使用 QWindow 类。

几乎所有图形元素都继承自 QWidget。例如,我们可以列举如下:

  • QAbstractButton 所有按钮类型的基类
    • QPushButton
    • QCheckBox
    • QRadioButton
  • QFrame 显示框架
  • QLabel 显示文本或图片

这样做是为了方便属性管理。大小和光标等共享属性可用于其他图形组件,而 QAbstractButton 提供了所有按钮共享的基本属性。

父子系统

父子系统是 Qt 中处理对象(尤其是 widgets)的一种便捷方式。任何继承自 QObject 的对象都可以有父对象和子对象。这种层级结构使很多事情变得方便:

  • 当一个对象被销毁时,它的所有子对象也会被销毁。因此,在某些情况下,调用 delete 方法变得可有可无。
  • 所有 QObject 对象都有 findChildfindChildren 方法,可用于查找给定对象的子对象。
  • QWidget 中的子 widget 会自动显示在父 widget 内部。

以下代码片段在 QPushButton 中创建 QPushButton

#include <QApplication>
#include <QPushButton>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 QPushButton button1 ("test");
 QPushButton button2 ("other", &button1);

 button1.show();

 return app.exec();
}

Beginner-Test-Widget.jpg

您还可以注意到,当应用程序关闭时,在堆栈上分配的 button1 将被释放。 由于 button2 的父元素是 button1,因此它也被删除了。你甚至可以在 Qt Creator 的分析部分通过搜索内存泄漏来测试这一点 — 不会有任何内存泄漏。

显然,将按钮嵌套在另一个按钮中没有任何意义,但基于这个思路,我们或许会希望将按钮放在一个不显示任何内容的容器中。这个容器就是 QWidget

以下代码用于在控件内显示按钮:

#include <QApplication>
#include <QPushButton>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 QWidget window;
 window.setFixedSize(100, 50);

 QPushButton *button = new QPushButton("Hello World", &window);
 button->setGeometry(10, 10, 80, 30);

 window.show();
 return app.exec();
}

请注意,我们使用 setFixedSize 创建了一个固定大小的 widget(充当窗口)。该方法具有以下签名:

void QWidget::setFixedSize(int width, int height);

我们还使用 setGeometry 来定位按钮。该方法具有以下签名:

void QWidget::setGeometry(int x, int y, int width, int height);

QWidget 的子类

到目前为止,我们所有的代码都放在了 main 函数中。对于我们简单的示例来说,这不是问题,但对于越来越复杂的应用程序,我们可能需要将代码拆分成不同的类。通常的做法是创建一个用于显示窗口的类,并将此窗口中包含的所有控件都实现为该类的属性。

在 Qt Creator 中,您可以自动创建一个新类通过 File > New file or project > C++ > C++ Class

Beginner-Class-Wizard.jpg

让该类继承自 QWidget,您应该会得到类似以下的代码:

Header

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class Window : public QWidget
{
 Q_OBJECT
 public:
  explicit Window(QWidget *parent = 0);

 signals:
 public slots:
};

#endif // WINDOW_H

Source

#include "window.h"

Window::Window(QWidget *parent) :
 QWidget(parent) {}

您可以看到 Qt Creator 会自动生成一个类模板。请注意,头部有一些新的元素:

  • Q_OBJECT
  • 一种新的方法类别: signals
  • 一种新的方法类别: public slots

所有这些要素将在下一章中进行解释,现在都不需要用到它们。 窗口的实现是在构造函数中完成的。我们可以声明窗口的大小,以及该窗口包含的控件及其位置。例如,实现包含按钮的上一个窗口可以采用以下方式:

main.cpp

#include <QApplication>
#include "window.h"

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 Window window;
 window.show();

 return app.exec();
}

window.h

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class QPushButton;
class Window : public QWidget
{
 public:
  explicit Window(QWidget *parent = 0);
 private:
 QPushButton *m_button;
};

#endif // WINDOW_H

window.cpp

#include "window.h"
#include <QPushButton>

Window::Window(QWidget *parent) :
 QWidget(parent)
 {
 // Set size of the window
 setFixedSize(100, 50);

 // Create and position the button
 m_button = new QPushButton("Hello World", this);
 m_button->setGeometry(10, 10, 80, 30);
}

请注意,无需编写析构函数来删除 m_button。使用父子系统时,当 Window 实例从堆栈中移除时,m_button 会自动删除。

延伸阅读

A better overview of QPushButton is given in this wiki page How to Use QPushButton

观察者模式

几乎所有 UI 工具包都具有检测用户操作并响应该操作的机制。有些使用回调函数,有些使用监听器,但基本上,他们都受到了观察者模式的启发.

当一个可观察对象想要通知其他观察者对象其状态发生变化时,可以使用观察者模式。以下是一些具体示例:

  • 用户点击了一个按钮,应该显示一个菜单。
  • 网页刚刚加载完成,现在需要从该加载好的网页中提取一些信息。
  • 用户正在浏览商品列表(例如在应用商店中),并且已经到达列表末尾,因此应该加载更多商品。

观察者模式在 GUI 应用程序中随处可见,并且经常导致一些样板代码。 观察者模式在 GUI 应用程序中随处可见,并且经常导致一些 boilerplate code。Qt 的创建初衷就是为了消除这些重复代码,并提供简洁清晰的语法。信号和插槽机制使这一切成为可能。

信号和插槽

Qt 没有提供可观察对象和观察者,也没有注册它们,而是提供了两个高级概念:信号插槽

  • 信号 是可以被一个对象发送的消息,通常用于报告状态变化。
  • 插槽 是一种接收并响应信号的函数。

这里是一些来自我们常用的 QPushButton 类的信号与插槽示例。

  • clicked
  • pressed
  • released

他们的名字非常直白。当用户点击(按下然后释放)、按下或释放按钮时,会发送这些信号。

以下是一些来自不同类的插槽。

  • QApplication::quit
  • QWidget::setEnabled
  • QPushButton::setText

QObject::connect 方法。 要响应信号,插槽必须连接到一个信号。Qt 提供了 QObject::connect 方法。使用它需要结合两个宏 SIGNALSLOT

FooObjectA *fooA = new FooObjectA();
FooObjectB *fooB = new FooObjectB();

QObject::connect(fooA, SIGNAL (bared()), fooB, SLOT (baz()));

本示例假设 FooObjectA 具有 bared 信号,并且 FooObjectB 具有 baz 插槽。

你必须在两个宏 SIGNALSLOT 中编写信号和插槽的签名。如果你想了解这些宏的具体作用,请阅读本章最后一节。

Remark:基本上,信号和插槽都是方法,它们可能带有参数,也可能不带参数,但它们永远不会返回任何值。虽然将信号视为方法的概念很不寻常,但插槽实际上是一种真正的方法,可以像往常一样被其他方法调用,或者在响应信号时调用。

传输信息

信号和插槽机制可以响应按钮点击,但它的功能远不止于此。例如,它还可以用来传递信息。假设在播放歌曲时,需要一个进度条来显示歌曲还有多久结束。媒体播放器可能有一个类用于检查媒体播放进度。此类的一个实例可能会定期发送滴答信号,其中包含进度值。该信号可以连接到 QProgressBar,用于显示进度。

用于检查进度的假设类可能有一个具有以下特征的信号:

void MediaProgressManager::tick(int miliseconds);

我们从文档中得知,QProgressBar 有以下插槽:

void QProgressBar::setValue(int value);

你可以看到信号和插槽具有相同类型的参数,尤其是类型。如果将信号连接到参数类型不同的插槽,则连接完成后(运行时),您将收到如下警告:

QObject::connect: Incompatible sender/receiver arguments

这是因为信号使用参数将信息传输到插槽。信号的第一个参数传递给插槽的第一个参数,第二个、第三个参数也一样,依此类推。

连接代码如下所示:

MediaProgressManager *manager = new MediaProgressManager();
QProgressBar *progress = new QProgressBar(window);

QObject::connect(manager, SIGNAL (tick(int)), progress, SLOT (setValue(int)));

您可以看到,您必须在 SIGNALSLOT 宏中提供签名,以提供通过信号传递的值的类型。您也可以根据需要提供变量名称。(实际上,这样做更好。)

信号和插槽的功能

  • 一个信号可以连接到多个插槽
  • 多个信号可以连接到一个插槽。
  • 信号可以与信号连接:这就是信号中继。如果第一个信号已发送,则发送第二个信号。

Examples

Responding to an event

还记得 button app 吗?让我们尝试用这个应用程序做点实际的事情,比如通过点击按钮来关闭它。我们已经知道 QPushButton 提供了 clicked 信号。我们还必须知道 QApplication 提供了 quit 插槽,用于关闭应用程序。

为了使点击按钮关闭应用程序,我们必须将按钮发出的 clicked 信号连接到该 QApplication 实例的 quit 插槽。我们可以修改上一节中的代码来实现这一点,但在这样做之前,您可能想知道如何在另一个类中访问 QApplication 实例。实际上这很简单,因为 QApplication 中存在一个静态函数用于获取实例,其签名如下:

QApplication * QApplication::instance()

这导致我们对之前的代码进行了如下修改:

window.cpp

#include "window.h"

#include <QApplication>
#include <QPushButton>

Window::Window(QWidget *parent) :
 QWidget(parent)
 {
  // Set size of the window
  setFixedSize(100, 50);

  // Create and position the button
  m_button = new QPushButton("Hello World", this);
  m_button->setGeometry(10, 10, 80, 30);

  // NEW : Make the connection
  connect(m_button, SIGNAL (clicked()), QApplication::instance(), SLOT (quit()));
 }

点击窗口内的按钮,应用程序将退出。

利用信号和插槽传输信息

以下是一个更简单的信息传输示例。它只在一个窗口中显示进度条和滑块(由 QSlider 创建),当滑块移动时,进度条的值会通过一个非常简单的连接进行同步。

使用到的信号和槽位有:

void QSlider::valueChanged(int value);
void QProgressBar::setValue(int value);

当值发生改变时,QSlider 会自动发出信号 valueChanged,并将新值作为参数传递。如我们所见,QProgressBar 的 setValue 方法用于设置进度条的值。

这将带出以下代码:

#include <QApplication>
#include <QProgressBar>
#include <QSlider>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

 // Create a container window
 QWidget window;
 window.setFixedSize(200, 80);

 // Create a progress bar
 // with the range between 0 and 100, and a starting value of 0
 QProgressBar *progressBar = new QProgressBar(&window);
 progressBar->setRange(0, 100);
 progressBar->setValue(0);
 progressBar->setGeometry(10, 10, 180, 30);

 // Create a horizontal slider
 // with the range between 0 and 100, and a starting value of 0
 QSlider *slider = new QSlider(&window);
 slider->setOrientation(Qt::Horizontal);
 slider->setRange(0, 100);
 slider->setValue(0);
 slider->setGeometry(10, 40, 180, 30);

 window.show();

 // Connection
 // This connection set the value of the progress bar
 // while the slider's value changes
 QObject::connect(slider, SIGNAL (valueChanged(int)), progressBar, SLOT (setValue(int)));

 return app.exec();
}

技术方面

如果您只想使用 Qt 进行编程,可以暂时跳过本节。请记住,在调用 connect 时,需要在信号和插槽周围加上 SIGNALSLOT。如果你想了解 Qt 的工作原理,最好阅读这个部分。

元对象

Qt 提供了一个元对象(meta-object)系统。元对象(字面意思是“对象之上”)是一种实现某些纯 C++ 通常无法实现的编程范式的方法,例如:

  • 自省:在运行时检查类型的能力
  • 异步函数调用

要在应用程序中使用此类元对象功能,可以继承 QObject 并对其进行标记,以便元对象编译器 (moc,meta-object compiler) 可以解释和转换它。

moc 生成的代码包括信号和插槽签名、用于从标记类中检索元信息的方法、属性处理…… 所有这些信息都可以通过以下方法获取:

const QMetaObject * QObject::metaObject () const

QMetaObject 类包含所有处理元对象的方法。

重要的宏

最重要的宏是 Q_OBJECT。信号与插槽的连接及其语法无法被常规 C++ 编译器解释。moc 负责将 QT 语法(如 connect,signals,slots)转换成常规 C++ 语法。这是通过在包含使用此类语法的类定义的头文件中引入 Q_OBJECT 宏来实现的。

mywidget.h

class MyWidget : public QWidget
{
 Q_OBJECT
 public:
  MyWidget(QWidget *parent = 0);
}

其他用于 moc 的标记宏有:

  • signals
  • public / protected / private slots

标志着需要扩展的不同方法。

SIGNALSLOT 也是非常重要且有用的宏。当发出信号时,元对象系统用于比较信号的签名,检查连接,并根据其签名找到插槽。这些宏实际上是用来将提供的方法签名转换为与元对象中存储的字符串匹配的字符串。

创建自定义信号和插槽

本章介绍信号和插槽的第二部分:实现自定义信号和插槽。

创建自定义插槽和信号非常简单。插槽类似于普通方法,但周围有一些装饰,而信号几乎不需要任何实现。

以下是清单:

  • 添加 Q_OBJECT
  • 添加 signals 部分并编写信号原型
  • 添加 public slots 或者 protected slots 或者 private slots 部分并编写插槽原型
  • 如同普通方法一样实现插槽
  • 建立连接

创建自定义插槽

为了实现插槽,我们首先需要使这个类能够发送信号并拥有插槽(参见上一章)。这是通过在类声明(通常在头文件中)中设置 Q_OBJECT 宏来实现的。

之后,应在相应的部分中声明一个插槽,并作为普通方法实现。

最后,插槽与信号连接起来。

创建信号

和插槽一样,我们首先需要添加 Q_OBJECT 宏。

信号同样需要定义在 signals 部分,但不需要实现。

它们使用 emit 关键字触发:

emit mySignal();

请注意,要发送带有参数的信号,必须在信号发送过程中传递这些参数:

emit mySignal(firstParameter, secondParameter );

示例

创建自定义插槽

让我们从带有按钮的窗口开始:

window.h

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class QPushButton;
class Window : public QWidget
{
public:
 explicit Window(QWidget *parent = 0);
private:
 QPushButton *m_button;
};

#endif // WINDOW_H

window.cpp

#include "window.h"

#include <QPushButton>

Window::Window(QWidget *parent) :
 QWidget(parent)
{
 // Set size of the window
 setFixedSize(100, 50);

// Create and position the button
 m_button = new QPushButton("Hello World", this);
 m_button->setGeometry(10, 10, 80, 30);
}

我们希望按钮可以被“选中”,选中时显示“已选中”,取消选中时恢复“Hello World”。

我们需要移除之前点击按钮退出应用程序的连接。现在我们希望点击按钮时,文本能够发生变化。更准确地说,我们希望按钮可以被选中,选中时显示“checked”, 取消选中时恢复成“Hello World”。

QPushButton 没有实现这种特定的插槽,所以我们必须自己实现它。如前所述,我们需要添加 Q_OBJECT 宏。

class Window : public QWidget
{
 Q_OBJECT
public:
 explicit Window(QWidget *parent = 0);
private:
 QPushButton *m_button;
};

我们还要添加自定义插槽。因为我们试图对按钮被选中做出反应,而相应的信号是

void QPushButton::clicked(bool checked)

我们可以实现一个具有以下签名的插槽:

void Window::slotButtonClicked(bool checked);

大多数情况下,按照惯例,我们在实现私有插槽和受保护插槽时加上 slot 前缀。在这里,我们并不想将此插槽作为公共函数公开,我们可以将其设为私有函数。新的头文件内容变为

window.h

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class QPushButton;
class Window : public QWidget
{
 Q_OBJECT
public:
 explicit Window(QWidget *parent = 0);
private slots:
 void slotButtonClicked(bool checked);
private:
 QPushButton *m_button;
};

#endif // WINDOW_H

这个插槽的实现是

void Window::slotButtonClicked(bool checked)
{
 if (checked) {
 m_button->setText("Checked");
 } else {
 m_button->setText("Hello World");
 }
}

我们需要使按钮可选中并建立连接,因此需要在构造函数中添加以下代码:

m_button->setCheckable(true);

connect(m_button, SIGNAL (clicked(bool)), this, SLOT (slotButtonClicked(bool)));

最终得到的代码如下:

window.cpp

#include "window.h"

#include <QPushButton>

Window::Window(QWidget *parent) :
 QWidget(parent)
{
 // Set size of the window
 setFixedSize(100, 50);

 // Create and position the button
 m_button = new QPushButton("Hello World", this);
 m_button->setGeometry(10, 10, 80, 30);
 m_button->setCheckable(true);

connect(m_button, SIGNAL (clicked(bool)), this, SLOT (slotButtonClicked(bool)));
}

void Window::slotButtonClicked(bool checked)
{
 if (checked) {
 m_button->setText("Checked");
 } else {
 m_button->setText("Hello World");
 }
}

发出自定义信号

基于前面的例子,我们希望如果按钮被点击(选中或取消选中)10 次,则关闭应用程序。我们首先需要实现一个计数器来统计点击次数。以下修改实现了该功能:

class Window : public QWidget
{
 Q_OBJECT
public:
 explicit Window(QWidget *parent = 0);
private slots:
 void slotButtonClicked(bool checked);
private:
 int m_counter;
 QPushButton *m_button;
};

以及

Window::Window(QWidget *parent) :
 QWidget(parent)
{
 // Set size of the window
 setFixedSize(100, 50);

// Create and position the button
 m_button = new QPushButton("Hello World", this);
 m_button->setGeometry(10, 10, 80, 30);
 m_button->setCheckable(true);

 m_counter = 0;

connect(m_button, SIGNAL (clicked(bool)), this, SLOT (slotButtonClicked(bool)));
}

void Window::slotButtonClicked(bool checked)
{
 if (checked) {
 m_button->setText("Checked");
 } else {
 m_button->setText("Hello World");
 }

m_counter ++;
}

现在我们需要创建一个自定义信号,用于通知其他组件计数器已达到 10。为了声明一个信号,我们需要在头文件添加一个 signals 部分。我们还可以声明一个具有以下签名的信号:

void Window::counterReached()

然后,按如下方式声明头部类:

class Window : public QWidget
{
 Q_OBJECT
public:
 explicit Window(QWidget *parent = 0);
signals:
 void counterReached();
private slots:
 void slotButtonClicked(bool checked);
private:
 int m_counter;
 QPushButton *m_button;
};

即使信号被声明为一个方法,也无需实现它。moc 会处理它。

现在我们需要在计数器达到 10 时发出信号。这在插槽中很容易实现:

void Window::slotButtonClicked(bool checked)
{
 if (checked) {
 m_button->setText("Checked");
 } else {
 m_button->setText("Hello World");
 }

m_counter ++;
 if (m_counter == 10) {
 emit counterReached();
 }
}

我们需要写入关键字 emit 来发送信号。

将新创建的信号连接到 quit 插槽的操作与往常一样:

connect(this, SIGNAL (counterReached()), QApplication::instance(), SLOT (quit()));

最终代码如下:

window.h

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class QPushButton;
class Window : public QWidget
{
 Q_OBJECT
public:
 explicit Window(QWidget *parent = 0);
signals:
 void counterReached();
private slots:
 void slotButtonClicked(bool checked);
private:
 int m_counter;
 QPushButton *m_button;
};

#endif // WINDOW_H

window.cpp

#include "window.h"

#include <QPushButton>
#include <QApplication>

Window::Window(QWidget *parent) :
 QWidget(parent)
{
 // Set size of the window
 setFixedSize(100, 50);

// Create and position the button
 m_button = new QPushButton("Hello World", this);
 m_button->setGeometry(10, 10, 80, 30);
 m_button->setCheckable(true);

 m_counter = 0;

connect(m_button, SIGNAL (clicked(bool)), this, SLOT (slotButtonClicked(bool)));
 connect(this, SIGNAL (counterReached()), QApplication::instance(), SLOT (quit()));
}

void Window::slotButtonClicked(bool checked)
{
 if (checked) {
 m_button->setText("Checked");
 } else {
 m_button->setText("Hello World");
 }

m_counter ++;
 if (m_counter == 10) {
 emit counterReached();
 }
}

您可以尝试检查一下,点击按钮十次后,应用程序是否会退出。

问题排除

在编译程序时,尤其是在添加宏 Q_OBJECT 时,可能会出现此编译错误。

main.cpp:(.text._ZN6WindowD2Ev[_ZN6WindowD5Ev]+0x3): undefined reference to `vtable for Window'

这是因为元对象编译器没有在应该包含元对象的类上运行。您应该通过 Build > Run qmake 重新运行 qmake

Widgets

单选按钮是一个标准图形用户界面组件。它通常用于从列表中做出唯一的选择。在 Qt 中,QRadioButton 用于创建单选按钮。

得益于一个良好的继承,QRadioButton 的行为与 QPushButton 完全相同。QPushButton 的所有属性在 QRadioButton 中也相同,第二章中学到的所有内容都可以在这里重用。

  • text
  • icon
  • tooltip
  • ...

默认情况下,QRadioButton 不会分组,因此可以同时选中多个。为了实现多个单选按钮的互斥行为,我们需要使用 QButtonGroup。这个类可以这样使用:我们分配一个新的按钮组并将其附加到父对象上。请注意,父对象可能是主窗口,或者 this

QButtonGroup *buttonGroup = new QButtonGroup(object);

// Add buttons in the button group
buttonGroup->addButton(button1);
buttonGroup->addButton(button2);
buttonGroup->addButton(button3);
...

我们想要创建一个菜单选择器。窗口中显示一列美味菜肴,并配有单选按钮,以及一个按钮,用来选择所选菜肴。

显然,现在点击按钮不会发生任何事情。

信号和插槽

以下是一个关于信号和插槽的例子。我们将编写一个包含两个按钮的应用程序。第一个按钮应该显示有关 Qt 的信息。

我们为您提供以下代码以供完成:

#include <QApplication>
#include <QPushButton>

int main(int argc, char **argv)
{
 QApplication app (argc, argv);

QWidget window;
 window.setFixedSize(100, 80);

QPushButton *buttonInfo = new QPushButton("Info", &window);
 buttonInfo->setGeometry(10, 10, 80, 30);

QPushButton *buttonQuit = new QPushButton("Quit", &window);
 buttonQuit->setGeometry(10, 40, 80, 30);

window.show();

// Add your code here

return app.exec();
}

为了显示有关 Qt 的信息,您需要使用以下方法

void QApplication::aboutQt();

您还可以给按钮添加图标,或者调整按钮大小。显然,“退出”按钮应该更醒目,为什么不把它做得更大一些呢?

但我们强烈建议您尝试自己想办法解决这些练习题。

Qt 入门指南 — 在文档中查找信息

Qt 文档是非常有价值的信息。 这里可以找到与 Qt 相关的一切。但是,Qt 文档并不是 Qt 的使用教程。它收集了类相关的所有信息以及一些示例。本章的目标是向您介绍文档,以此作为使用 Qt 进行编程的基础。

在哪里可以找到文档

最好的文档来源就在互联网上,在这个开发者网络中:

Qt documentation on developer network

它提供完整的文档,以及用户可以添加的一些文档注释。这些文档注释提供了更多示例,并重点介绍了一些棘手的问题。在线文档还具有相当强大的搜索引擎,并且包含了所有 Qt 版本的文档。

虽然在线版本需要互联网连接,但 DocNotes 仍然可用。如果 QtSDK 正确安装,与当前 Qt 版本匹配的文档也会被安装,并且 QtCreator 的“帮助”部分不会为空。您也可以使用 Qt Assistant,一个独立的文档浏览器。

文档中的重要部分

如果您在 Qt Creator 或 Qt Assistant 中运行离线文档查看器,您会在摘要中发现 Qt SDK 不同组件的文档。

  • Qt Assistant documentation
  • Qt Designer documentation
  • Qt Linguist documentation
  • QMake documentation
  • Qt reference documentation

最重要的部分当然是 Qt 参考文档。

Qt 文档对许多组件进行了很好的介绍,并且还提供了 Qt 中所有类的文档。 这个列表可以从页面 All classes 中找到。另一个有帮助的页面是 All modules。这个页面提供有关 Qt 中不同组件的信息。

在本教程中,我们将主要使用这些模块

搜索功能也相当重要。如果您知道要使用的类,并且想要查找文档,您可以在搜索字段中输入该类的名称(在线),或者在索引的筛选器中输入该类的名称(离线)。您还可以这写地方搜索方法和枚举。

浏览类的文档

类的文档结构始终相同:

  • 该类的名称和简介
  • 继承
  • 枚举
  • 属性
  • 公开方法
  • 公开插槽
  • 信号
  • 受保护的方法

我们以 QTextEdit 类为例。