Getting Started Programming with Qt Widgets
在本主题中, 我们通过使用 C++ 和 Qt Widgets 模块, 实现一个简单的记事本应用程序, 教授基本的 Qt 知识. 这个应用程序是一个小型文本编辑器, 允许你创建文本文件, 保存, 打印或重新打开并再次编辑. 你还可以设置要使用的字体.
你可以在 snippets/widgets-tutorial/notepad
目录找到 Nodepad 源码. 你也可以在 Qt 项目的源码或Qt 5的初始化目录找到. 你可以在 Qt Creator 的欢迎页面查找.
Creating the Notepad Project
在 Qt Creator 中创建新项目时, 有一个向导帮助你逐步完成项目创建过程. 这个向导会提示你输入该特定类型项目所需的设置并为你创建项目.
创建 Notepad 项目, 选择 File > New File 或 Project > Applications > Qt Widgets Application > Choose, 然后按照向导说明操作. 在 Class Information 对话框, 输入 Notepad 作为类名, 选择 QMainWindow 作为基类.
Qt Widgets Application 的向导(程序)会创建一个工程, 包含一个 main 源码文件, 一组 UI 文件 (Notepad widget):
- notepad.pro - 工程文件.
- main.cpp - 应用程序的 main 源码文件.
- notepad.cpp - Notepad widget 的源码文件.
- notepad.h - Notepad widget 的头文件.
- notepad.ui - the Notepad widget 的 UI 文件.
.cpp, .h, .ui 文件附带必要的样板代码, 帮助你构建和运行项目. .pro 文件是编译配置文件. 我们在以下部分详细讲解文件内容.
Learn More
About | Here |
---|---|
Using Qt Creator | Qt Creator |
Creating other kind of applications with Qt Creator | Qt Creator Tutorials |
Main Source File
向导(程序)在 main.cpp 文件中生成以下代码:
#include "notepad.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Notepad w; w.show(); return a.exec(); }
我们将逐行解释代码. 下面两行包含 Notepad widget 和 QApplication 的头文件. 所有的 Qt 类都有一个以它们命名的头文件.
#include "notepad.h" #include <QApplication>
随后, 定义一个 main 函数, 作为所有基于 C 和 C++ 的应用程序的入口点:
int main(int argc, char *argv[]) {
随后, 创建一个 QApplication 对象. 这个对象管理应用程序范围的资源, 是运行任何使用 Qt Widget 的 Qt 程序所必须的. 它使用命令行参数 argc
和 argv
构造一个应用程序对象. (对于不使用 Qt Widget 的 GUI应用程序, 你可以使用 QGuiApplication 替代.)
QApplication a(argc, argv);
随后, 创建一个 Notepad 对象. 这个对象的类和 UI 文件是由向导(程序)创建. 在 Qt 中, 用户界面包含的可视元素被称为 widget
. widget 的示例有文本编辑, 滚动条, 标签和单选按钮. 一个 widget 也可是其他 widget 的容器; 例如, 对话框或主应用程序窗口.
Notepad w;
随后, 在屏幕上显示 Notepad widget. widget也作为一个容器 (例如 QMainWindow, 它包含工具栏, 菜单, 状态栏等 widget). 默认情况下, widget 不可见; show() 函数使 widget 可见.
w.show();
随后, QApplication 进入事件循环. Qt 应用程序运行时, 会生成事件, 并将其发送给应用程序的 widget. 如鼠标按下事件和键盘事件.
return a.exec();
Learn More
About | Here |
---|---|
Widgets and Window Geometry | Window and Dialog Widgets |
Events and event handling | The Event System |
Designing a UI
向导(程序)生成 XML 格式的用户界面定义: notepad.ui. 当你在 Qt Creator 中打开 notepad.ui 时, 它会自动在集成的 Qt Designer 中打开.
构建应用程序时, Qt Creator 会启动 Qt 用户界面编译器 (uic), 这个编译器读取 .ui 文件, 并创建对应的 C++ 头文件, ui_notepad.h.
Using Qt Designer
向导(程序)创建一个使用 QMainWindow 的应用程序. 它有自己的布局, 你可以向布局添加菜单栏, 停靠 widget, 工具栏, 状态栏. 中心区域可以被任何类型的 widget 占据. 向导(程序)将 Notepad widget 放入其中.
在 Qt Designer 中添加 widget:
- 在 Qt Creator 编辑 模式下, 双击 Projects 视图中的 notepad.ui 文件, Qt会在集成的 Qt Designer 中启动这个文件.
- 将 QTextEdit 拖入.
- 按下 Ctrl+A (或 Cmd+A), 选择 widget, 点击 Lay out Vertically (或 按下 Ctrl+L), 使用垂直布局 (QVBoxLayout).
- 按下 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>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Notepad</string> </property> <widget class="QWidget" name="centralWidget"> <widget class="QWidget" name=""> <property name="geometry"> <rect> <x>70</x> <y>0</y> <width>268</width> <height>235</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTextEdit" name="textEdit"/> </item> <item> <widget class="QPushButton" name="quitButton"> <property name="text"> <string>Quit</string> </property> </widget> </item> </layout> </widget> </widget> <widget class="QMenuBar" name="menuBar"> ...
下面这一行包含 XML 声明, 它指定文档中使用的 XML 版本和字符编码:
<?xml version="1.0" encoding="UTF-8"?>
文件的其余部分指定了一个定义 Notepad widget 的 ui
元素:
<ui version="4.0">
The following snippet creates a QVBoxLayout widget that contains a QTextEdit and QPushButton widget. As mentioned, widgets can contain other widgets. It is possible to set the bounds (the location and size) of child widgets directly, but it is usually easier to use a layout. A layout manages the bounds of a widget's children. QVBoxLayout, for instance, places the children in a vertical row.
<layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTextEdit" name="textEdit"/> </item> <item> <widget class="QPushButton" name="quitButton"> <property name="text"> <string>Quit</string> </property> </widget> </item> </layout>
UI 文件与 Notepad 类的头文件和源文件一起使用. 我们将在后面的部分中查看 UI 文件的其余部分.
Notepad 头文件
向导(程序)为 Notepad 类生成了一个头文件, 其中包含必要的 #include, 构造函数, 析构函数和 Ui 对象. 该文件如下所示:
#include <QMainWindow> namespace Ui { class Notepad; } class Notepad : public QMainWindow { Q_OBJECT public: explicit Notepad(QWidget *parent = 0); ~Notepad(); private: Ui::Notepad *ui; };
随后, 包含头文件 QMainWindow, 这个类提供一个应用程序的主界面:
#include <QMainWindow>
随后, 声明 Notepad 类包含在 Ui 命名空间, Ui 命名空间中是通过 uic
工具生成的标准命名空间:
namespace Ui { class Notepad; }
类的声明包含 Q_OBJECT
宏. 这个宏必须放在类定义的开始位置, 并声明这个类是一个 QObject. 当然, 这个类也必须继承自 QObject. QObject 为普通的 C++ 类添加了多种功能. 值得注意的是, 类名和槽函数的名称可以在运行时查询, 还可以查询槽函数的参数类型, 并调用它.
class Notepad : public QMainWindow { Q_OBJECT
以下几行声明一个构造函数, 该构造函数具有名为 parent
的默认参数. 值 0 表示 widget 没有父级(它是顶级 widget).
public: explicit Notepad(QWidget *parent = 0);
以下行声明一个虚拟析构函数来释放对象在其生命周期中获取的资源. 根据 C++ 命名约定, 析构函数与它们关联的类具有相同的名称, 并带有波浪号 (~) 前缀. 在 QObject 中, 析构函数是虚拟的, 以确保当通过指向基类的指针删除对象时, 可以正确调用派生类的析构函数.
~Notepad();
以下几行声明一个成员变量, 它是指向 Notepad UI 类的指针. 成员变量与特定类相关联, 并且可由其所有方法访问.
private: Ui::Notepad *ui;
Notepad Source File
向导(程序)为 Notepad 类生成的源文件如下:
#include "notepad.h" #include "ui_notepad.h" Notepad::Notepad(QWidget *parent) : QMainWindow(parent), ui(new Ui::Notepad) { ui->setupUi(this); } Notepad::~Notepad() { delete ui; }
以下行包括由向导(程序)生成的 Notepad 类头文件和由 uic
工具生成的 UI 头文件::
#include "notepad.h" #include "ui_notepad.h"
以下行定义 Notepad
构造函数:
Notepad::Notepad(QWidget *parent) :
以下行定义调用 QMainWindow 构造函数, 这个类是 Notepad 的基类:
QMainWindow(parent),
以下行创建 UI 类实例, 并分配给成员变量 ui
:
ui(new Ui::Notepad)
随后, 调用 UI 的 setupUi()
函数:
ui->setupUi(this);
我们在析构函数中删除 ui
:
Notepad::~Notepad() { delete ui; }
Project File
向导(程序)生成工程配置文件 notepad.pro
:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = Notepad TEMPLATE = app SOURCES += main.cpp\ notepad.cpp HEADERS += notepad.h FORMS += notepad.ui
工程配置文件指定应用程序名称和 qmake
模板, 及项目中包含的源文件, 头文件和 UI 文件.
你还可以使用 qmake
的 -project
选项生成 .pro 文件. 这时, 你必须手动添加 QT += widgets
, 以便链接到 Qt Widgets 模块.
Learn More
About | Here |
---|---|
Using Qt Designer | Qt Designer Manual |
Layouts | Layout Management, Widgets and Layouts, Layout Examples |
The widgets that come with Qt | Qt Widget Gallery |
Main windows and main window classes | Application Main Window, Main Window Examples |
QObjects and the Qt Object model (This is essential to understand Qt) | Object Model |
qmake and the Qt build system | qmake Manual |
Adding User Interaction
We now have a user interface, but it does not really do anything useful, as it only contains a text edit and a push button, as well as some standard functions for quitting, minimizing and maximizing the application. To make the application useful, we will add user interaction to it. First, we will add functionality to the push button. Second, we will add functions for loading a file to the text edit and for saving the contents of the text edit as a file.
Adding Push Buttons
Most desktop operating systems have standard ways of enabling users to quit applications. However, in this example we use this basic function to illustrate how you can add user interaction to applications. To do this, we add a slot that we connect to the Quit button.
To exit the application when the Quit button is pushed, you use the Qt signals and slots mechanism. A signal is emitted when a particular event occurs and a slot is a function that is called in response to a particular signal. Qt widgets have predefined signals and slots that you can use directly from Qt Designer.
To use Qt Designer to add a slot for the quit function, right-click the Quit button to open a context-menu and then select Go to slot > clicked().
A private slot, on_quitButton_clicked()
, is added to the Notepad widget class header file, notepad.h and a private function, Notepad::on_quitButton_clicked()
, is added to the Notepad widget class source file, notepad.cpp. We just need to write the code to execute the quit function in the source file.
Let us look at the modified code in the header file, notepad.h:
namespace Ui { class Notepad; } class Notepad : public QMainWindow { Q_OBJECT public: explicit Notepad(QWidget *parent = 0); ~Notepad(); private slots: void on_quitButton_clicked(); private: Ui::Notepad *ui; };
The following code uses Qt's signals and slots mechanism to make the application exit when the Quit button is pushed. Qt Designer uses QMetaObject auto-connection facilities to connect the button's clicked() signal to a slot in the Notepad class. The uic
tool automatically generates code in the dialog's setupUi()
function to do this, so Qt Designer only needs to declare and implement a slot with a name that follows a standard convention.
private slots: void on_quitButton_clicked();
The corresponding code in the source file, notepad.cpp, looks as follows:
void Notepad::on_quitButton_clicked() { }
The code defines the private function that is executed when QPushButton emits the clicked() signal.
We now complement the code to have the quit() slot of QApplication exit Notepad:
void Notepad::on_quitButton_clicked() { QCoreApplication::quit(); }
Learn More
About | Here |
---|---|
Signals and slots | Signals & Slots |
Adding Menu Items
Often, in a main window, the same slot should be invoked by several widgets. Examples are menu items and buttons on a tool bar. To make this easier, Qt provides QAction, which can be given to several widgets, and be connected to a slot. For instance, both QMenu and QToolBar can create menu items and tool buttons from the same QAction.
To learn how to use actions with signals and slots, we add menu items to open and save a document and connect them to slots.
As before, we use Qt Designer to add the widgets to the user interface. The wizard creates an application with a QMenu widget, with the text Type Here as a placeholder for menu and menu item names. Double-click the text to enter names for the File menu and Open and Save menu items. Qt Designer automatically generates the appropriate actions.
To connect the actions to slots, right-click an action and select Go to slot > triggered().
QAction instances are created with the text that should appear on the widgets that we add them to (in our case, menu items). If we also wanted to add the actions to a tool bar, we could have specified icons for them.
The modified code in notepad.ui now looks as follows:
<widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>22</height> </rect> </property> <widget class="QMenu" name="menuFile"> <property name="title"> <string>File</string> </property> <addaction name="actionOpen"/> <addaction name="actionSave"/> </widget> <addaction name="menuFile"/> </widget> <widget class="QToolBar" name="mainToolBar"> <attribute name="toolBarArea"> <enum>TopToolBarArea</enum> </attribute> <attribute name="toolBarBreak"> <bool>false</bool> </attribute> </widget> <widget class="QStatusBar" name="statusBar"/> <action name="actionOpen"> <property name="text"> <string>Open</string> </property> </action> <action name="actionSave"> <property name="text"> <string>Save</string> </property> </action> </widget>
Qt Designer adds the private slots on_actionOpen_triggered()
and on_actionSave_triggered()
to notepad.h and the private functions Notepad::on_actionOpen_triggered()
and Notepad::on_actionSave_triggered()
to notepad.cpp.
In the following sections, we complement the code to load and save files. When a menu item is clicked, the item triggers the action, and the respective slot is invoked.
Opening Files
In this section, we implement the functionality of the on_actionOpen_triggered()
slot. The first step is asking the user for the name of the file to open. Qt comes with QFileDialog, which is a dialog from which the user can select a file. The appearance of the dialog depends on the desktop platform that you run the application on. The following image shows the dialog on macOS:
We complement the code generated by Qt Designer in notepad.cpp, as follows:
void Notepad::on_actionOpen_triggered() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(), tr("Text Files (*.txt);;C++ Files (*.cpp *.h)")); if (!fileName.isEmpty()) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::critical(this, tr("Error"), tr("Could not open file")); return; } QTextStream in(&file); ui->textEdit->setText(in.readAll()); file.close(); } }
The static getOpenFileName() function displays a modal file dialog. It returns the file path of the file selected, or an empty string if the user canceled the dialog.
If we have a file name, we try to open the file with open(), which returns true if the file could be opened. We will not go into error handling here, but you can follow the links from the learn more section. If the file could not be opened, we use QMessageBox to display a dialog with an error message (see the QMessageBox class description for further details).
Actually reading in the data is trivial using the QTextStream class, which wraps the QFile object. The readAll() function returns the contents of the file as a QString. The contents can then be displayed in the text edit. We then close() the file to return the file descriptor back to the operating system.
We now use the function tr() around our user visible strings. This function is necessary when you want to provide your application in more than one language (for example, English and Chinese). We will not go into details here, but you can follow the Qt Linguist
link from the learn more table.
To use QFileDialog, QFile, QMessageBox, and QTextStream, add the following includes to notepad.cpp:
#include <QFileDialog> #include <QFile> #include <QMessageBox> #include <QTextStream>
Saving Files
Now, let us move on to the on_actionSave_triggered()
slot, which also uses QFileDialog to create a dialog in which the user can save a file with the specified name in the specified location.
We complement the code generated by Qt Designer in notepad.cpp, as follows:
void Notepad::on_actionSave_triggered() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), QString(), tr("Text Files (*.txt);;C++ Files (*.cpp *.h)")); if (!fileName.isEmpty()) { QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { // error message } else { QTextStream stream(&file); stream << ui->textEdit->toPlainText(); stream.flush(); file.close(); } } }
When we write the contents of the text edit to the file, we use the QTextStream class again. QTextStream can also write QStrings to the file with the << operator.
Learn More
About | Here |
---|---|
MDI applications | QMdiArea, MDI Example |
Files and I/O devices | QFile, QIODevice |
tr() and internationalization | Qt Linguist Manual, Writing Source Code for Translation, Internationalization with Qt |
Building and Running Notepad
现在, 你已拥有所有必需的文件, 选择 Build > Build Project Notepad 构建, 并运行应用程序. Qt Creator 使用 qmake
和 make
在项目构建设置中指定的目录创建可执行文件, 并运行它.
Building and Running from the Command Line
要从命令行构建应用程序,请切换到应用程序的 .cpp
文件所在的目录, 并添加前面描述的项目文件 (后缀为 .pro). 然后使用以下 shell 命令构建应用程序:
qmake
make (or nmake on Windows)
这些命令在项目目录中创建一个可执行文件. qmake
工具读取项目文件并生成一个 Makefile
文件, 其中包含有关如何构建应用程序的说明. 然后 make
工具 (或 nmake
工具) 读取 Makefile
, 并生成可执行的二进制文件.