[译]开始使用 Qt 编程

文章翻译自 Qt 文档里的一个教程,原文地址为 http://doc.qt.io/qt-4.8/gettingstartedqt.html .

欢迎来到 Qt ──一个跨平台 GUI 工具集── 的世界。在这个新手教程里,我们将通过实现一
个简单的 Notepad 应用程序来教授基本的 Qt 知识。在完成这个引导之后,你应该可以通
过自己查阅我们的概述和 API 文档来找到开发你自己应用所需要的信息。

这个教程里的代码在你 Qt 安装目录下的examples/tutorials/gettingStarted/gsQt。如
果你正在使用 Qt SDK,你会在Examples/4.7/tutorials/gettingStarted/gsQt下找到它
(如果你用的 Qt 版本比较新,把 4.7 改为相应的值)。

Hello Notepad

在这第一个例子中,我们只是在桌面上简单地建立并显示一个窗口(window frame),里面是
一个文本框(text edit)。这展示了有 GUI 的最简单的 Qt 程序。

part1

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <QApplication>
#include <QTextEdit>

int main(int argv, char *args[])
{
QApplication app(argv, args);

QTextEdit textEdit;
textEdit.show();

return app.exec();
}

让我们一行一行地来看一看。前两行中,我们包含了QApplicationQTextEdit所需要
的头文件,我们的例子中要用到这两个类。所有的 Qt 类都有一个根据它命令的头文件。

第 6 行新建了一个QApplication对象。这个对象管理整个应用(application-wide)的资
源并且是所有有 GUI 的 Qt 程序必需的。因为 Qt 接受一些命令行参数所以需要argv
args

第 8 行新建了一个QTextEdit对象。文本框(text edit)是 GUI 里的一个可见元素。在
Qt 中,我们把这样的元素叫做widget。其他的 widget 还包括滚动条(scroll bar),标
签(label)和单选按钮(radio button)。一个 widget 也可以包含其他 widget 的容器,比
如对话框(dialog)或者主程序窗口(main application window)。

第 9 行显示一个处于窗口中的文本框。由于widget也有容器的功能(比如一个QMainWindow
会包含工具栏,菜单,状栏和其他一些 widget),使得让一个 widget 在它自己的窗口中
显示也是可能的。Widget 默认不可见,函数show()可以使它们可见。

第 11 行使得 QApplication 进入它的事件循环。当一个 Qt 程序运行时,会有事件产生并
被发送给程序的 widget。按下鼠标和键盘按键都是事件。当你在文本框中输入文本时,它
会收到按键按下的事件并以画出输入的文本作为响应。

要运行这个程序,打开一个命令窗口,进入 .cpp 文件所在的那个目录。以下的 shell 命
令会构建这个程序。

1
2
3
qmake -project
qmake
make

这会在part1目录下生成一个可执行文件(在 Windows 下,你可能要使用 nmake 而非
make。且可执行文件会被放在part1\debugpart1\release目录下(这些目录在运行
make时生成))。qmake是Qt的构建工具,它需要一个配置文件。如果指定了
-project选项的话qmake会为我们生成。给定了配置文件(后缀为 .pro),qmake
生成一个给make命令使用的文件,它会帮你构建这个程序。我们会在以后讲解编写自己的
.pro 文件。

Learn More

About Here
Widgets and Window Geometry Window and Dialog Widgets
Events and event handling The Event System

添加退出按钮 Adding a Quit Button

在一个真实的程序里,通常会需要不止一个 widget。我们现在将要在文本框下面添加一个
QPushButton。这个按钮在被按下时会退出 Notepad 程序。

part2

先看一眼代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <QtGui>

int main(int argv, char *args[])
{
QApplication app(argv, args);

QTextEdit *textedit = new QTextEdit;
QPushButton *quitButton = new QPushButton("&Quit");

QObject::connect(quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));

QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(textedit);
layout->addWidget(quitButton);

QWidget window;
window.setLayout(layout);

window.show();

return app.exec();
}

第 1 行包含了 QtGui,里面包括了所有的 Qt 的 GUI 类。

第 10 行使用了 Qt 的 Signal/Slot 机制使得当退出按钮被按下时程序能够退出。Slot 是
一个在运行时可以通过名字(作为字符串)被调用的函数。Signal 是一个函数,被调用时会
调用与之注册的 Slots;我们把这称为‘关联 slot 和 signal’以及‘产生 signal’。

quit()QApplication退出程序的 slot。clicked()QPushButton被按下时发出
的 signal。静态方法QObject::connect()将 slot 和 signal 关联起来。SIGNAL()
SLOT()是两个宏,它们接受要关联的 signal 和 slot 的函数签名。我们还需要给定发出
和接受信号的对象的指针。

第 12 行创建了一个QVBoxLayout。正如前面所说,widget 可以包含其他 widget。直接
设置一个子 widget 的边界(位置和大小)是可以的,但使用 layout 通常更简单。layout
会管理子 widget 的边界。QVBoxLayout会将子 widget 放在一竖列上。

第 13~14 行把文本框和按钮添加到 layout 里。在 17 行中,我们将设置了一个 widget
的 layout。

Learn More

About Here
Signals and Slots Signals & Slots
Layouts Layout Management, Widgets and Layouts, Layout examples
The widgets that come with Qt Qt Widget Gallery, Widget Examples

Subclassing QWidget

当用户想要退出程序时,你可能想要弹出一个对话框请求确认。在这个例子里,我们创建一
QWidget的子类并添加一个我们关联到退出按钮的 slot。

Notepad

请看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Notepad : public QWidget
{
Q_OBJECT

public:
Notepad();

private slots:
void quit();

private:
QTextEdit *textEdit;
QPushButton *quitButton;
};

Q_OBJECT宏必须在类定义的第一行从而将类声明为QObject(自然地,它必须也继承自
QObject)。QObject为普通的 C++ 类增加了些许功能。重要地功能如,类名和 slot 名
可以在运行时被查询。查询一个 slot 的参数类型并调用它也是可行的。

第 9 行声明了 slot quit()。使用 slots宏可以很简单地做到。quit()这个 slot
可以被关联到 signal 了。我们一会儿再做这件事。

这次我们不再 main() 函数中设置 GUI 和关联 slot 了,而是使用 Notepad 的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Notepad::Notepad()
{
textEdit = new QTextEdit;
quitButton = new QPushButton(tr("Quit"));

connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));

QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(textEdit);
layout->addWidget(quitButton);

setLayout(layout);

setWindowTitle(tr("Notepad"));
}

如你在类定义中所见,我们使用了指向QObject(textEdit 和 quitButton)的指针。作为
规定,你应该总是在堆上分配QObject且永不复制它们。

我们使用函数tr()处理用户可见字符串。当你想为你的程序提供不止一种语言的支持(如
英语和汉语)时就是必需的。我们不会在此详述,你可以点击 Learn more 表中的链接查看
更多信息。

下面是 slot quit()的代码:

1
2
3
4
5
6
7
8
9
10
void Notepad::quit()
{
QMessageBox messageBox;
messageBox.setWindowTitle(tr("Notepad"));
messageBox.setText(tr("Do you really want to quit?"));
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
messageBox.setDefaultButton(QMessageBox::No);
if (messageBox.exec() == QMessageBox::Yes)
qApp->quit();
}

我们使用QMessageBox类来显示一个对话框询问用户是否确认退出。

Learn More

About Here
tr() and internationlization Qt Linguist Manual, Writing Source Code for Translation, Hello tr() Example, Internationalization with Qt
QObjects and the Qt Object model (This is essential to understand Qt) Object Model
qmake and the Qt build system qmake Manual

建立 .pro 文件

在这个例子中,我们会创建自己的 .pro 文件,而不是使用 qmake 的 -project 选项自动
生成。

1
2
3
HEADERS = notepad.h
SOURCES = notepad.cc \
main.cc

使用 QMainWindow

很多应用程序可以从QMainWindow中获益,它有自己的 layout,你可以向其中添加菜单栏,
widget,工具栏和状态栏。QMainWindow 有一个中间区域,任何 widget 都可以使用。在我
们的例子中,我们会把文本框放在那儿。

Notepad

让我们来看看新的 Notepad 类定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <QtGui>

class Notepad : public QMainWindow
{
Q_OBJECT

public:
Notepad();

private slots:
void open();
void save();
void quit();

private:
QTextEdit *textEdit:

QAction *openAction;
QAction *saveAction;
QAction *exitAction;

QMenu *fileMenu;
};

我们多包含了两个 slot 用来保存和打开文档。我们会在下一节实现他们。

在一个主窗口中,相同的 slot 经常会被多个 widget 调用。比如菜单项和工具栏上的按钮。
为了使这种情况变得简单,Qt 提供了QAction,它可以被多个 widget 拥有,并且被关联
到同一个 slot 上。例如,QMenuQToolBar都可以从相同的 QAction 创建菜单项或工
具按钮。我们马上就会看到这怎么做到。

像以前一样,我们使用 Notepad 构建函数来设置 GUI。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "notepad.h"

Notepad::Notepad()
{
openAction = new AQction(tr("&Open"), this);
saveAction = new AQction(tr("&Save"), this);
exitAction = new AQction(tr("&Exit"), this);

connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));

fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addSeparator();
fileMenu->addAction(exitAction);

textEdit = new QTextEdit;
setCentralWidget(textEdit);

setWindowTitle(tr("Notepad"));
}

QAction 应该在创建时给定它们在其它 widget(我们用的是菜单项)上应该显示的文字。
如果我们还想把它添加到工具栏上,我们还可以指定图标。

现在当一个菜单项被点击时,就会触发相应的行为,对应的 slot 也会被调用。

Learn More

About Here
Main Windows and main window classes Application Main Window, Main Window Examples
MDI applications QMdiArea, MDI Example

保存和载入

在这个例子中,我们会实现前面例子中添加的open()save()两个 slot 的功能。

Open File

我们从open()开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Notepad::open()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
tr("Text File (*.txt);;C++ Files (*.cpp *.h)"));

if (fileName != "") {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
return;
}
QTextStream in(&file);
textEdit->setText(in.readAll());
file.close();
}
}

第一步是询问用户要打开的文件名。Qt 提供了QFileDialog,用户可以通过它选择一个文
件。上面的图片展示的是 Kubuntu 上的对话框。静态方法getOpenFileName()会展示一个
文件对话框。它会返回所选文件的路径,如果用户取消了对话框则返回空字符串。

如果我们有一个文件名,我们就使用open()打开它,如果可以打开会返回 true。我们在
这里不会深入错误处理,你可以点击查看更多处的链接了解更多。如果文件不能打开,我们
用 QMessageBox 显示一个带错误信息的对话框(查看 QMessageBox
类描述可以了解更多)。

现在我们再来看看save()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Notepad::save()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));

if (fileName != "") {
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
// error message
} else {
QTextStream stream(&file);
stream << textEdit->toPlainText();
stream.flush();
file.close();
}
}
}

我们把文本框内容写入文件时,又用到了 QTextStream 类。QTextStream 还可以使用 <<
操作符把 QString 写入文件。

Learn More

About Here
Files and I/O devices QFile, QIODevice

后记

原以为翻译完这篇就知道个大概了,谁知道官方的教程这么不友好,后面的代码不完整,没
有 main 函数,完全不知道怎么使用定义好的类,真是无语了。

后面自己查了一些资料,好像也不太难,不过怎么也是面向新手的,不能这样吧。大概可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
#include "notepad.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Notepad notepad;
notepad.show();

return app.exec();
}

编译运行方式与前面相同。

0%