Qt Widgets 编程快速入门

基于 Qt Widgets 的记事本应用程序教程。

在本主题中,我们通过使用 C++ 和 Qt Widgets 模块实现一个简单的记事本应用程序来教授基本的 Qt 知识。该应用程序是一个小型文本编辑器,允许您创建文本文件、保存它、打印它,或者重新打开并再次编辑它。您还可以设置要使用的字体。

"Notepad application"

运行示例

要从 Qt Creator 运行示例,请打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

创建记事本项目

在 Qt Creator 中创建一个新的项目由一个向导辅助,该向导会引导您一步步完成项目创建过程。向导会提示您输入特定类型项目所需的设置并为您创建项目。

注意: Qt Creator 中的 UI 文本和生成文件的内容取决于您使用的 Qt Creator 版本。

"Qt Creator New Project dialog"

要创建记事本项目,请选择 文件 > 新建项目 > 应用程序(Qt) > Qt Widgets 应用程序 > 选择,并遵循向导的说明。在 类信息 对话框中,将类名键入为 记事本 并选择 QMainWindow 作为基类。

"Class Information Dialog"

Qt Widgets 应用程序 向导创建了一个包含主源文件和一组指定用户界面(记事本小部件)的项目的项目。

  • CMakeLists.txt - 项目文件。
  • main.cpp - 应用程序的主源文件。
  • notepad.cpp - 记事本小部件中记事本类的源文件。
  • notepad.h - 记事本小部件中记事本类的头文件。
  • notepad.ui - 记事本小部件的 UI 窗口。

这些文件包含了您构建和运行项目所需的必要模板代码。我们将在接下来的部分中更详细地查看文件内容。

了解更多

关于这里
使用 Qt CreatorQt Creator
使用 Qt Creator 创建其他类型的应用程序Qt Creator 教程

主源文件

向导在 main.cpp 文件中生成了以下代码

#include "notepad.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Notepad w;
    w.show();
    return a.exec();
}

我们将逐行通过 code。以下几行包含记事本小部件和 QApplication 的头文件。所有 Qt 类都有一个以它们命名的头文件。

#include "notepad.h"

#include <QApplication>

以下行定义了主函数,它所有 C 和 C++ 应用程序入口点

int main(int argc, char *argv[])

以下行创建了一个 QApplication 对象。该对象管理应用程序级别的资源,对于使用 Qt Widgets 的任何 Qt 程序都是必要的。它使用 argc 命令行参数和 argv 中运行的参数来构造应用程序对象。(对于不使用 Qt Widgets 的 GUI 应用程序,您可以改用 QGuiApplication。)

    QApplication a(argc, argv);

以下行创建了一个记事本对象。这是向导创建的类和 UI 文件的对象。用户界面包含在 Qt 中被称作 widgets 的可视元素。例如,控件可以是文本编辑器、滚动条、标签和单选按钮。一个控件也可以是其他控件的容器;例如,对话框或主应用程序窗口。一个控件也可以充当其他控件(如对话框或主应用程序窗口)的容器。

    Notepad w;

以下行在自带的窗口中显示了记事本窗口。控件也可以作为容器使用。其中一个例子是 QMainWindow,通常包含多种类型的控件。控件默认时不可见;使用 show() 函数可以使得控件可见。

    w.show();

以下行使得 QApplication 进入事件循环。当 Qt 应用程序运行时,事件会被生成并发送到应用程序的控件。事件的例子包括鼠标点击和按键。

    return a.exec();

了解更多

关于这里
控件和窗口几何形状窗口和对话框控件
事件和事件处理事件系统

UI 设计

向导使用 XML 格式生成一个用户界面定义:notepad.ui。当您在 Qt Creator 中打开 notepad.ui 文件时,它会自动在集成的 Qt Designer 中打开。

当您构建应用程序时,Qt Creator 将启动 Qt 的 用户界面编译器 (uic),它读取 .ui 文件并创建一个相应的 C++ 头文件,名为 ui_notepad.h。

使用 Qt Designer

向导创建了一个使用 QMainWindow 的应用程序。它有一个自带的布局,您可以向其中添加菜单栏、停靠窗口、工具栏和状态栏。中心区域可以被任何类型的控件占据。向导将记事本控件放置在那里。

在 Qt Designer 中添加控件

  1. 在 Qt Creator 的 编辑 模式下,在 项目 视图中双击 notepad.ui 文件以在集成的 Qt Designer 中启动文件。
  2. 将文本编辑器 QTextEdit 拖放到表单中。
  3. Ctrl+A(或 Cmd+A)选择控件,然后点击 垂直布局(或按 Ctrl+L)应用垂直布局(QVBoxLayout)。
  4. Ctrl+S(或 Cmd+S)保存您的更改。

现在在 Qt Designer 中的 UI 如下所示

您可以在代码编辑器中查看生成的 XML 文件

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Notepad</class>
 <widget class="QMainWindow" name="Notepad">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>400</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Notepad</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QTextEdit" name="textEdit"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
    ...

以下行包含 XML 声明,它指定了文档中使用的 XML 版本和字符编码

<?xml version="1.0" encoding="UTF-8"?>

文件的其他部分指定了一个 ui 元素,该元素定义了一个记事本控件

<ui version="4.0">

UI 文件与记事本类的头文件和源文件一起使用。我们将在后续部分中查看 UI 文件的其余部分。

记事本头文件

向导为记事本类生成了一个头文件,其中包含了必要的 #includes,构造函数、析构函数以及 Ui 对象。文件看起来如下

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui {
class Notepad;
}
QT_END_NAMESPACE

class Notepad : public QMainWindow
{
    Q_OBJECT

public:
    explicit Notepad(QWidget *parent = nullptr);
    ~Notepad();

private:
    Ui::Notepad *ui;
    QString currentFile;
};

以下行包含了提供主应用程序窗口的 QMainWindow

#include <QMainWindow>

以下行声明了在 Ui 命名空间中的 Notepad 类,这是由 uic 工具从 .ui 文件生成 UI 类时使用的标准命名空间

namespace Ui {
class Notepad;
}

类声明包含 Q_OBJECT 宏。它必须在类定义中首先出现,并声明我们的类为 QObject。当然,它也必须继承自 QObject。一个 QObject 为普通的C++类添加了几个功能。值得注意的是,可以查询类名和槽名。还可以查询槽的参数类型并调用它。

class Notepad : public QMainWindow
{
    Q_OBJECT

下面的代码行声明了一个名为 parent 的默认参数的构造函数。值0表示该小部件没有父元素(它是一个顶层小部件)。

public:
    explicit Notepad(QWidget *parent = nullptr);

下面声明了一个虚拟析构函数来释放对象在其生命周期中获取的资源。根据C++的命名约定,析构函数具有与其关联的类的相同名称,前面加一个波浪号 (~)。在 QObject 中,析构函数是虚拟的,以确保在通过基类指针删除对象时正确调用派生类的析构函数。

    ~Notepad();

下面的代码行声明了一个成员变量,它是指向记事本UI类的指针。成员变量与特定类相关联,并且对其所有方法都是可访问的。

private:
    Ui::Notepad *ui;
    QString currentFile;
};
记事本源文件

为记事本类生成的源文件夹如下所示

#include "notepad.h"
#include "ui_notepad.h"

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

}

以下行包括由向导生成的记事本类头文件和由 uic 工具生成的UI头文件

#include "notepad.h"
#include "ui_notepad.h"

以下行定义了 Notepad 构造函数

Notepad::Notepad(QWidget *parent) :

以下行调用 QMainWindow 构造函数,它记事本类的基类

    QMainWindow(parent),

以下行创建了UI类实例并将其赋值给 ui 成员

    ui(new Ui::Notepad)

以下行设置了UI

{
    ui->setupUi(this);

在析构函数中,我们删除了 ui

Notepad::~Notepad()
{
    delete ui;
}
项目文件

向导为我们生成了以下项目文件,CMakeLists.txt

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

cmake_minimum_required(VERSION 3.16)
project(notepad LANGUAGES CXX)

find_package(Qt6
    REQUIRED COMPONENTS Core Gui Widgets
    OPTIONAL_COMPONENTS PrintSupport
)

qt_standard_project_setup()

qt_add_executable(notepad
    main.cpp
    notepad.cpp notepad.h notepad.ui
)

set_target_properties(notepad PROPERTIES
    WIN32_EXECUTABLE TRUE
    MACOSX_BUNDLE TRUE
)

target_link_libraries(notepad PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
)

if(TARGET Qt6::PrintSupport)
    target_link_libraries(notepad PRIVATE Qt6::PrintSupport)
endif()

# Resources:
set(notepad_resource_files
    "images/bold.png"
    "images/copy.png"
    "images/create.png"
    "images/cut.png"
    "images/edit_redo.png"
    "images/edit_undo.png"
    "images/exit.png"
    "images/font.png"
    "images/info.png"
    "images/italic.png"
    "images/new.png"
    "images/open.png"
    "images/paste.png"
    "images/pencil.png"
    "images/print.png"
    "images/save.png"
    "images/save_as.png"
    "images/underline.png"
)

qt_add_resources(notepad "notepad"
    PREFIX
        "/"
    FILES
        ${notepad_resource_files}
)

install(TARGETS notepad
    BUNDLE  DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

qt_generate_deploy_app_script(
    TARGET notepad
    OUTPUT_SCRIPT deploy_script
    NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})

项目文件指定了项目中包含的源文件、头文件和UI文件。

了解更多

关于这里
使用 Qt DesignerQt Designer手册
布局布局管理小部件和布局布局示例
Qt提供的UI小部件Qt小部件库
主窗口和主窗口类应用程序主窗口主窗口示例
QObjects和Qt对象模型(这是理解Qt的关键)对象模型
qmake和Qt构建系统qmake手册

添加用户交互

要向编辑器添加功能,我们首先在工具栏上添加菜单项和按钮。

点击“在此输入”,添加新建、打开、保存、另存为、打印和退出选项。这在以下动作编辑器中创建了5行。要将动作连接到槽,右键单击动作并选择 转到槽 > 触发(),并完成给定槽的代码。

如果我们还想将动作添加到工具栏,可以为每个 QAction 分配一个图标,然后将 QAction 拖到工具栏。您可以在动作的图标属性中输入图标名称来分配图标。当QAction 拖到工具栏时,点击图标将启动相关槽。

完善 newDocument() 方法

void Notepad::newDocument()
{
    currentFile.clear();
    ui->textEdit->setText(QString());
}

currentFile 变量是一个全局变量,包含当前正在编辑的文件,clear() 清除文本缓冲区。currentFile 变量定义在 notepad.h 的私有部分

private:
    Ui::Notepad *ui;
    QString currentFile;
打开文件

notepad.ui 中,右击 actionOpen 并选择 转到槽

完成方法 open()

void Notepad::open()
{
    QString fileName = QFileDialog::getOpenFileName(this, "Open the file");
    if (fileName.isEmpty())
        return;
    QFile file(fileName);
    currentFile = fileName;
    if (!file.open(QIODevice::ReadOnly | QFile::Text)) {
        QMessageBox::warning(this, "Warning", "Cannot open file: " + file.errorString());
        return;
    }
    setWindowTitle(fileName);
    QTextStream in(&file);
    QString text = in.readAll();
    ui->textEdit->setText(text);
    file.close();
}

QFileDialog::getOpenFileName 打开一个对话框,允许你选择文件。 QFile 对象 myfile 以选定的 file_name 作为参数。我们也将选定的文件存储到全局变量 currentFile 以供以后使用。我们以只读文本文件的方式使用 file.open 打开文件。如果无法打开,将发出警告,并停止程序。

我们为参数 myfile 定义了一个 QTextStream instream。文件 myfile 的内容被复制到 QString text。使用 setText(text)text 填充到我们的编辑器缓冲区。

保存文件

我们通过右击 actionSave 并选择 转到槽,以与 打开文件 相同的方式创建保存文件的方法。

void Notepad::save()
{
    QString fileName;
    // If we don't have a filename from before, get one.
    if (currentFile.isEmpty()) {
        fileName = QFileDialog::getSaveFileName(this, "Save");
        if (fileName.isEmpty())
            return;
        currentFile = fileName;
    } else {
        fileName = currentFile;
    }
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, "Warning", "Cannot save file: " + file.errorString());
        return;
    }
    setWindowTitle(fileName);
    QTextStream out(&file);
    QString text = ui->textEdit->toPlainText();
    out << text;
    file.close();
}

QFile 对象 myfile 与全局变量 current_file 相连,该变量包含我们正在处理的文件。如果我们不能打开 myfile,则发出错误消息,并停止方法。我们创建了一个 QTextStream outstream。编辑器缓冲区的内容被转换为纯文本,然后写入到 outstream

以另一个名称保存文件
void Notepad::saveAs()
{
    QString fileName = QFileDialog::getSaveFileName(this, "Save as");
    if (fileName.isEmpty())
        return;
    QFile file(fileName);

    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, "Warning", "Cannot save file: " + file.errorString());
        return;
    }
    currentFile = fileName;
    setWindowTitle(fileName);
    QTextStream out(&file);
    QString text = ui->textEdit->toPlainText();
    out << text;
    file.close();
}

这与 保存文件 的过程相同,唯一的不同之处在于在这里你需要输入要创建的新文件名。

打印文件

如果你想要使用打印功能,你需要将 PrintSupport 添加到项目文件

find_package(Qt6
    REQUIRED COMPONENTS Core Gui Widgets
    OPTIONAL_COMPONENTS PrintSupport
)

notepad.cpp 中,我们声明了一个名为 printDev 的 QPrinter 对象

void Notepad::print()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
    QPrinter printDev;
#if QT_CONFIG(printdialog)
    QPrintDialog dialog(&printDev, this);
    if (dialog.exec() == QDialog::Rejected)
        return;
#endif // QT_CONFIG(printdialog)
    ui->textEdit->print(&printDev);
#endif // QT_CONFIG(printer)
}

我们启动打印机对话框,并将选定的打印机存储在对象 printDev 中。如果我们点击了 取消 并没有选择打印机,则方法返回。实际的打印机命令以 ui->textEdit->print 的形式给出,其中 QPrinter 对象作为参数。

选择字体
void Notepad::selectFont()
{
    bool fontSelected;
    QFont font = QFontDialog::getFont(&fontSelected, this);
    if (fontSelected)
        ui->textEdit->setFont(font);
}

我们使用 QFontDialog 声明一个表示我们是否选择字体的布尔值。如果是的,我们通过 ui->textEdit->setFont(myfont) 设置字体。

复制、剪切、粘贴、撤销和重做

如果你选择了一些文本,并且想要将其复制到剪贴板,你可以调用 ui->textEdit 的相应方法。剪切、粘贴、撤销和重做的操作也是如此。

此表显示了要使用的方法名称。

任务调用方法
复制ui->textEdit->copy()
剪切ui->textEdit->cut()
粘贴ui->textEdit->paste()
撤销ui->textEdit->undo()
重做ui->textEdit->redo()

了解更多

关于这里
文件和 I/O 设备QFileQIODevice
tr() 和国际化Qt 语言手册编写用于翻译的源代码使用 Qt 进行国际化

在命令行中构建和运行

要从命令行构建示例应用程序,为它创建一个构建目录。切换到构建目录,运行qt-cmake来配置您的项目以进行构建。如果项目配置成功,生成的文件将使您能够构建项目。

md <build_directory>
cd <build_directory>
<qt_installation_directory>\bin\qt-cmake -GNinja <source_directory>
<generator>

这些命令在构建目录中创建一个可执行文件。《CMake》工具读取项目文件,生成构建应用程序的说明。然后,生成器使用这些说明生成可执行二进制文件。

例如,要在 Windows 上构建 Notepad 示例,使用 Ninja 作为生成器时,请输入以下命令

md notepad-build
cd notepad-build
C:\Qt\6.7.2\msvc2019_64\bin\qt-cmake -GNinja C:\Examples\notepad
ninja

如果您不使用 Ninja 作为生成器,请使用与生成器无关的 CMake 命令来构建应用程序,而不是ninja

cmake --build

示例项目 @ code.qt.io

© 2024 Qt 公司有限公司。此处包含的文档贡献权属各自所有者。本提供的文档是根据自由软件基金会发布的< Scatterlink="http://www.gnu.org/licenses/fdl.html">GNU 自由文档许可证版本 1.3条款许可的。Qt 和相应的标志是芬兰和/或其他国家的 Qt 公司的商标。所有其他商标属于其各自所有者。