本章将向您介绍使用 Qt 5 进行开发。我们将向您展示如何安装 Qt SDK,以及如何使用 Qt Creator IDE 创建和运行一个简单的 hello world 应用程序。
2.1 安装 Qt 5 SDK
Qt SDK 包括您构建桌面或嵌入式应用程序所需的工具。 您可以从 Qt 公司的主页获取最新版本。 有一个离线和在线安装程序。 作者个人更喜欢在线安装包,因为它允许你安装和更新多个 Qt 版本。 这是推荐的开始方式。 SDK 本身有一个维护工具,可以让你将 SDK 更新到最新版本。
Qt SDK 易于安装,并带有自己的 IDE,用于快速开发,称为 Qt Creator。 IDE 是一个高效的 Qt 编码环境,推荐给所有读者。 然而,许多开发人员从命令行使用 Qt,您可以自由使用您选择的代码编辑器。
安装 SDK 时,应选择默认选项并确保启用 Qt 5.12。 然后你准备好了。
2.2 Hello World
为了测试您的安装,我们将创建一个小型 hello world 应用程序。 请打开 Qt Creator 并创建一个 Qt Quick UI 项目(文件 → 新文件或项目 → 其他项目 → Qt Quick UI 原型)并将项目命名为 HelloWorld。
注意:Qt Creator IDE 允许您创建各种类型的应用程序。 如果没有特别说明,我们总是使用 Qt Quick UI 原型项目。
提示:一个典型的 Qt Quick 应用程序是由一个名为 QmlEngine 的运行时构成的,它加载初始 QML 代码。 开发人员可以将 C++ 类型注册到运行时以与本机代码交互。 这些 C++ 类型也可以捆绑到插件中,然后使用 import 语句动态加载。 qmlscene 和 qml 工具是预先制作好的运行时,可以直接使用。 首先,我们不会涵盖开发的原生方面,而只关注 Qt 5 的 QML 方面。这就是我们从原型项目开始的原因。
Qt Creator 为您创建了几个文件。 HelloWorld.qmlproject 文件就是项目文件,存放了相关的项目配置。 此文件由 Qt Creator 管理,因此请勿自行编辑。
另一个文件 HelloWorld.qml 是我们的应用程序代码。 在继续阅读之前,打开它并尝试了解应用程序的作用。
// HelloWorld.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
}
HelloWord.qml 程序是用 QML 语言编写的。 我们将在下一章更深入地讨论 QML 语言。 QML 将用户界面描述为层次元素树。
要自行运行应用程序,请按左侧的 Run 工具,或从菜单中选择 Build > Run。
在后台,Qt Creator 运行 qmlscene 并将您的 QML 文档作为第一个参数传递。 qmlscene 应用程序解析文档,然后启动用户界面。 您应该看到如下内容:
Qt 5 可以工作! 这意味着我们已准备好继续。
提示:如果您是系统集成商,您需要安装 Qt SDK 以获得最新的稳定 Qt 版本,以及为您的特定设备目标从源代码编译的 Qt 版本。
从头开始构建
如果您想从命令行构建 Qt 5,您首先需要获取代码存储库的副本并构建它。 访问 Qt 的 wiki 以获取有关如何从 git 构建 Qt 的最新说明。
编译成功后(和 2 杯咖啡),Qt 5 将在 qtbase 文件夹中可用。 任何饮料都可以,但是,我们建议使用咖啡以获得最佳效果。
如果您想测试您的编译,您现在可以使用 Qt5 附带的默认运行时运行该示例:如果您想从命令行构建 Qt 5,您首先需要获取代码存储库的副本并构建它。 访问 Qt 的 wiki 以获取有关如何从 git 构建 Qt 的最新说明。
编译成功后(喝两杯 2 杯咖啡),Qt 5 将在 qtbase 文件夹中可用。 任何饮料都可以,但是,我们建议使用咖啡以获得最佳效果。
如果您想测试您的编译,您现在可以使用 Qt5 附带的默认运行时运行该示例:$ qtbase/bin/qmlscene
2.3 应用类型
本节介绍了可以使用 Qt 5 编写的不同应用程序类型。它不限于此处提供的选择,但它会让您更好地了解使用 Qt 5 可以实现的总体目标。
2.3.1 控制台应用
控制台应用程序不提供图形用户界面,并且通常作为系统服务的一部分或从命令行调用。 Qt 5 附带了一系列现成的组件,可以帮助您非常高效地创建跨平台控制台应用程序。 例如,网络文件 API、字符串处理和高效的命令行解析器。 由于 Qt 是 C++ 之上的高级 API,因此您可以获得与执行速度相匹配的编程速度。 不要认为 Qt 只是一个 UI 工具包——它可以提供的东西太多了!
字符串处理
第一个示例演示了如何添加 2 个常量字符串。 诚然,这不是一个非常有用的应用程序,但它让您了解没有事件循环的本机 C++ 应用程序可能是什么样子。
// module or class includes
#include <QtCore>
// text stream is text-codec aware
QTextStream cout(stdout, QIODevice::WriteOnly);
int main(int argc, char** argv)
{
// avoid compiler warnings
Q_UNUSED(argc)
Q_UNUSED(argv)
QString s1("Paris");
QString s2("London");
// string concatenation
QString s = s1 + " " + s2 + "!";
cout << s << endl;
}
容器类
此示例将列表和列表迭代添加到应用程序。 Qt 附带了大量易于使用的容器类,并且与其他 Qt 类具有相同的 API 范例。
QString s1("Hello");
QString s2("Qt");
QList<QString> list;
// stream into containers
list << s1 << s2;
// Java and STL like iterators
QListIterator<QString> iter(list);
while(iter.hasNext()) {
cout << iter.next();
if(iter.hasNext()) {
cout << " ";
}
}
cout << "!" << endl;
这是一个更高级的列表函数,它允许您将字符串列表连接到一个字符串中。 这在您需要进行基于行的文本输入时非常方便。 使用 QString::split() 函数也可以实现逆向(字符串到字符串列表)。
QString s1("Hello");
QString s2("Qt");
// convenient container classes
QStringList list;
list << s1 << s2;
// join strings
QString s = list.join(" ") + "!";
cout << s << endl;
文件 IO
在下一个片段中,我们从本地目录中读取一个 CSV 文件并遍历行以从每一行中提取单元格。 这样做,我们从 ca.csv 文件中获取表数据。 20行代码。 文件读取为我们提供了一个字节流,为了能够将其转换为有效的 Unicode 文本,我们需要使用文本流并将文件作为较低级别的流传入。 要写入 CSV 文件,您只需要以写入模式打开文件,并将这些行通过管道传输到文本流中。
QList<QStringList> data;
// file operations
QFile file("sample.csv");
if(file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
// loop forever macro
forever {
QString line = stream.readLine();
// test for null string 'String()'
if(line.isNull()) {
break;
}
// test for empty string 'QString("")'
if(line.isEmpty()) {
continue;
}
QStringList row;
// for each loop to iterate over containers
foreach(const QString& cell, line.split(",")) {
row.append(cell.trimmed());
}
data.append(row);
}
}
// No cleanup necessary.
关于使用 Qt 的基于控制台的应用程序的部分到此结束。
2.3.2 C++ Widget 应用
基于控制台的应用程序非常方便,但有时您需要具有图形用户界面 (GUI)。 此外,基于 GUI 的应用程序可能需要后端来读取/写入文件、通过网络进行通信或将数据保存在容器中。
在基于Widgets的应用程序的第一个片段中,我们只需要创建一个窗口并显示它就可以了。 在 Qt 中,没有父窗口的Widgets是窗口。 我们使用一个作用域指针来确保当指针超出作用域时该Widgets被删除。 应用程序对象封装了 Qt 运行时,我们通过调用 exec() 启动事件循环。 从那时起,应用程序仅对由用户输入(例如鼠标或键盘)或其他事件提供程序(例如网络或文件 IO)触发的事件做出反应。 仅当退出事件循环时应用程序才会退出。 这可以通过在应用程序上调用 quit() 或关闭窗口来完成。
运行代码时,您将看到一个大小为 240 x 120 像素的窗口。 就这样。
#include <QtGui>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QScopedPointer<QWidget> widget(new CustomWidget());
widget->resize(240, 120);
widget->show();
return app.exec();
}
自定义Widgets
在处理用户界面时,您可能需要创建定制的Widgets。 通常,Widgets是一个充满绘画调用的窗口区域。 此外,Widgets具有如何处理键盘和鼠标输入以及如何对外部触发器做出反应的内部知识。 要在 Qt 中做到这一点,我们需要从 QWidget 派生并覆盖几个用于绘制和事件处理的函数。
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QtWidgets>
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
QPoint m_lastPos;
};
#endif // CUSTOMWIDGET_H
在实现中,我们在Widgets上绘制了一个小边框,并在最后一个鼠标位置上绘制了一个小矩形。
这对于低级自定义Widgets来说是非常典型的。 鼠标和键盘事件改变Widgets的内部状态并触发绘画更新。 我们不会对此代码进行太多详细介绍,但很高兴知道您有这种可能性。 Qt 附带了大量现成的桌面Widgets,因此您可能不必这样做。
#include "customwidget.h"
CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
}
void CustomWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QRect r1 = rect().adjusted(10,10,-10,-10);
painter.setPen(QColor("#33B5E5"));
painter.drawRect(r1);
QRect r2(QPoint(0,0),QSize(40,40));
if(m_lastPos.isNull()) {
r2.moveCenter(r1.center());
} else {
r2.moveCenter(m_lastPos);
}
painter.fillRect(r2, QColor("#FFBB33"));
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
m_lastPos = event->pos();
update();
}
void CustomWidget::mouseMoveEvent(QMouseEvent *event)
{
m_lastPos = event->pos();
update();
}
桌面Widgets
Qt 开发人员已经为您完成了所有这些工作,并提供了一组桌面Widgets,在不同的操作系统上具有原生外观。 那么,您的工作就是将这些不同的Widgets在Widgets容器中排列成更大的面板中。 Qt 中的一个部件也可以是其他部件的容器。 这是通过父子关系实现的。 这意味着我们需要使我们现成的Widgets,例如按钮、复选框、单选按钮、列表和网格成为其他Widgets的子级。 下面显示了完成此操作的一种方法。
下面是一个所谓的Widgets容器的头文件。
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = 0);
private slots:
void itemClicked(QListWidgetItem* item);
void updateItem();
private:
QListWidget *m_widget;
QLineEdit *m_edit;
QPushButton *m_button;
};
在实现中,我们使用布局来更好地排列Widgets。 当容器Widgets重新调整大小时,布局管理器会根据一些尺寸策略重新布局Widgets。 在这个例子中,我们有一个列表、一个行编辑和一个按钮,它们是垂直排列的,允许用户编辑一个城市列表。 我们使用 Qt 的信号和插槽来连接发送方和接收方对象。
CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
m_widget = new QListWidget(this);
layout->addWidget(m_widget);
m_edit = new QLineEdit(this);
layout->addWidget(m_edit);
m_button = new QPushButton("Quit", this);
layout->addWidget(m_button);
setLayout(layout);
QStringList cities;
cities << "Paris" << "London" << "Munich";
foreach(const QString& city, cities) {
m_widget->addItem(city);
}
connect(m_widget, SIGNAL(itemClicked(QListWidgetItem*)), this,SLOT(itemClicked(QListWidgetItem*)));
connect(m_edit, SIGNAL(editingFinished()), this, SLOT(updateItem()));
connect(m_button, SIGNAL(clicked()), qApp, SLOT(quit()));
}
void CustomWidget::itemClicked(QListWidgetItem *item)
{
Q_ASSERT(item);
m_edit->setText(item->text());
}
void CustomWidget::updateItem()
{
QListWidgetItem* item = m_widget->currentItem();
if(item) {
item->setText(m_edit->text());
}
}
绘制形状
有些问题可以更好地可视化。 如果手头的问题看起来很像几何对象,那么 Qt 图形视图是一个不错的选择。 图形视图在场景中排列简单的几何形状。 用户可以与这些形状进行交互,或者使用算法对其进行定位。 要填充图形视图,您需要一个图形视图和一个图形场景。 场景附加到视图并填充有图形项目。 这是一个简短的示例。 首先是声明视图和场景的头文件。
class CustomWidgetV2 : public QWidget
{
Q_OBJECT
public:
explicit CustomWidgetV2(QWidget *parent = 0);
private:
QGraphicsView *m_view;
QGraphicsScene *m_scene;
};
在实现中,场景首先连接到视图。 该视图是一个Widgets,并被安排在我们的容器Widgets中。 最后,我们在场景中添加了一个小矩形,然后在视图上渲染。
#include "customwidgetv2.h"
CustomWidget::CustomWidget(QWidget *parent) :
QWidget(parent)
{
m_view = new QGraphicsView(this);
m_scene = new QGraphicsScene(this);
m_view->setScene(m_scene);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
layout->addWidget(m_view);
setLayout(layout);
QGraphicsItem* rect1 = m_scene->addRect(0,0, 40, 40, Qt::NoPen, QColor("#FFBB33"));
rect1->setFlags(QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsMovable);
}
2.3.3 调整数据
到现在为止,我们主要介绍了基本的数据类型以及如何使用Widgets和图形视图。 在您的应用程序中,您通常需要大量的结构化数据,这些数据可能还需要持久存储。
最后,还需要显示数据。 对于这个,Qt 使用模型。 一个简单的模型是字符串列表模型,它被字符串填充,然后附加到列表视图。
m_view = new QListView(this);
m_model = new QStringListModel(this);
m_view->setModel(m_model);
QList<QString> cities;
cities << "Munich" << "Paris" << "London";
m_model->setStringList(cities);
另一种存储和检索数据的流行方式是 SQL。 Qt 嵌入了 SQLite,还支持其他数据库引擎(例如 MySQL 和 PostgreSQL)。 首先,您需要使用架构来创建数据库,如下所示:
CREATE TABLE city (name TEXT, country TEXT);
INSERT INTO city value ("Munich", "Germany");
INSERT INTO city value ("Paris", "France");
INSERT INTO city value ("London", "United Kingdom");
要使用 SQL,我们需要将 SQL 模块添加到我们的 .pro 文件中
QT += sql
然后我们可以使用C++打开我们的数据库。 首先,我们需要为指定的数据库引擎检索一个新的数据库对象。 有了这个数据库对象,我们就打开了数据库。 对于 SQLite,只需要指定数据库文件的路径即可。 Qt 提供了一些高级数据库模型,其中之一就是表模型。 表模型使用表标识符和一个可选的 where 子句来选择数据。 生成的模型可以像之前的其他模型一样附加到列表视图。
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("cities.db");
if(!db.open()) {
qFatal("unable to open database");
}
m_model = new QSqlTableModel(this);
m_model->setTable("city");
m_model->setHeaderData(0, Qt::Horizontal, "City");
m_model->setHeaderData(1, Qt::Horizontal, "Country");
m_view->setModel(m_model);
m_model->select();
对于更高层次的模型操作,Qt 提供了一个排序文件代理模型,允许您对模型进行排序、过滤和转换。
QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
proxy->setSourceModel(m_model);
m_view->setModel(proxy);
m_view->setSortingEnabled(true);
过滤是根据要作为过滤器的列以及作为过滤器参数的字符串来完成的。
proxy->setFilterKeyColumn(0);
proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
proxy->setFilterFixedString(QString)
过滤器代理模型比这里演示的要强大得多。 就目前而言,记住它的存在就足够了。
注意:这是您可以使用 Qt 5 开发的不同类型的经典应用程序的概述。桌面正在移动,很快移动设备将成为我们未来的桌面。 移动设备具有不同的用户界面设计。 它们比桌面应用程序更加简单。 他们只做一件事,并以简单和专注的方式去做。 动画是移动体验的一个重要组成部分。 用户界面需要让人感觉生动和流畅。 传统的 Qt 技术并不适合这个市场。
接下来是:Qt Quick 来救援。
2.3.4 Qt Quick应用程序
现代软件开发存在着内在的冲突。 用户界面的移动速度比我们的后端服务快得多。 在传统技术中,您开发所谓的前端与后端的速度相同。 当客户想要在项目期间更改用户界面或在项目期间开发用户界面的想法时,这会导致冲突。 敏捷的项目,需要敏捷的方法。
Qt Quick 提供了一个声明性环境,其中您的用户界面(前端)像 HTML 一样声明,而您的后端则使用本机 C++ 代码。 这可以让您两全其美。
下面是一个简单的 Qt Quick UI
import QtQuick 2.5
Rectangle {
width: 240; height: 1230
Rectangle {
width: 40; height: 40
anchors.centerIn: parent
color: '#FFBB33'
}
}
声明语言被称为 QML,它需要一个运行时来执行它。 Qt 提供了一个名为 qmlscene 的标准运行时,但编写自定义运行时也不是那么难。 为此,我们需要快速查看并设置主 QML 文档作为源。 剩下的唯一事情就是显示用户界面。
QQuickView* view = new QQuickView();
QUrl source = QUrl::fromLocalFile("main.qml");
view->setSource(source);
view->show();
让我们回到前面的例子。 在一个示例中,我们使用了 C++ 城市模型。 如果我们可以在我们的声明性 QML 代码中使用这个模型,那就太好了。
为了实现这一点,我们首先对前端进行编码,看看我们希望如何使用城市模型。 在本例中,前端需要一个名为 cityModel 的对象,我们可以在列表视图中使用它。
import QtQuick 2.5
Rectangle {
width: 240; height: 120
ListView {
width: 180; height: 120
anchors.centerIn: parent
model: cityModel
delegate: Text { text: model.city }
}
}
要启用 cityModel,我们主要可以重用之前的模型,并在根上下文中添加上下文属性。 根上下文是主文档中的其他根元素。
m_model = new QSqlTableModel(this);
... // some magic code
QHash<int, QByteArray> roles;
roles[Qt::UserRole+1] = "city";
roles[Qt::UserRole+2] = "country";
m_model->setRoleNames(roles);
view->rootContext()->setContextProperty("cityModel", m_model);
提示:这并不完全正确,因为 SQL 表模型包含列中的数据,而 QML 模型期望数据作为角色。 因此需要在列和角色之间建立映射。 请参阅 QML 和 QSqlTableModel wiki 页面。
2.4 总结
我们已经了解了如何安装 Qt SDK 以及如何创建我们的第一个应用程序。 然后我们向您介绍了不同的应用程序类型,为您提供了 Qt 的概述,展示了 Qt 为应用程序开发提供的一些功能。 我希望您对 Qt 是一个非常丰富的用户界面工具包有一个良好的印象,它提供了应用程序开发人员所希望的一切以及更多。 尽管如此,Qt 不会将您锁定在特定的库中,因为您始终可以使用其他库,甚至可以自己扩展 Qt。 它在支持不同的应用模型方面也很丰富:控制台、经典桌面用户界面和触摸用户界面。