第三章:正式开始
第三章:正式开始
本章摘要
本章将介绍如何使用Qt 6进行应用开发。涉及如何安装Qt SDK以及使用Qt Creator IDE 来创建一个Hello World
应用。
安装Qt 6 SDK
Qt SDK 是构建桌面和嵌入式应用的一系列工具。你可以从Qt 公司主项获取最新版本,有离线和在线安装两种方式。本人更偏向使用在线安装方式,这种方式有更多版本可以选择安装或更新。推荐以这种方式开始。SDK自身有一个维护工具,可以将SDK更新至最新版本。
Qt SDK很容易安装,且自带一个IDE:Qt Creator,可快速进入开发。它是Qt开发的很高效的开发环境,推荐开发者安装使用。也有不少开发者以命令行的形式使用Qt,也是一种开发的方式选择吧。
在开始安装选择安装内容时,应该选择默认选项且至少保证Qt 6.2是被选中的。然后就可以安装了。安装可参考本人的另一篇文章 如何在Windows安装Qt 以及 Qt Quick开发环境搭建
升级 Qt
Qt SDK附带维护工具,位于安装目录下$(intall_dir)
(一般会以Qt Mantance Tool
出现在启动菜单中),通过它可以增加/更新Qt SDK组件。
通过源码安装
通过编译源码方式安装Qt可参考 Qt Wiki
Hello World
通过创建一个Hello World来验证一下环境是否正常。打开Qt创建一个Qt Quick UI 类型的工程(File ‣ New File or Project ‣ Other Project ‣ Qt Quick UI prototype)并将工程命名为HelloWorld。
注意
Qt Creator可以创建各种类型的应用。如果没有特别说明,我们都选择Qt Quick UI prototype类型的工程。生产环境中,你可能更倾向于创建以CMake为编译器的工程,但这种类型(Qt Quick UI prototype)更适合创建原型类的应用。
注意
典型的Qt Quick应用是基于被称为QmlEngine的运行时创建的,正是QmlEngine初始化了QML代码。开发者可以向运行时注册C类实现与原生代码交互。这些C类也可以打包成一个插件,通过import引入来动态加载。qml工具是直接使用的预运行时工具。刚开始时,我们不会涉及原生代码,而是仅涵盖Qt 6 的QML方面的内容。这就是为什么从原型工程开始讲起。
Qt Creator创建了几个文件。HelloWorld.qmlproject
是项目文件,存储了项目相关的配置。这个文件是由Qt Creatort管理的,避免人工修改。
另一个文件,HelloWorld.qml
是项目代码。打开文件,在开始阅读前,应理解应用都做了什么。
- // HelloWorld.qml
- import QtQuick
- import QtQuick.Window
- Window {
- width: 640
- height: 480
- visible: true
- title: qsTr("Hello World")
- }
HelloWorld.qml
是以QML语法来写的。我们将在下一章深入讨论QML语言。QML以树形结构的元素描述了用户界面。在本例中,描述了一个尺寸为640*480,标题为 “Hello World”的窗体。
点左侧的Run 或者 从菜单中选择Build > Run 启动程序运行。
Qt Creator启动qml(解释器引擎)并将QML文件作为第一个参数传给qml。qml解析文档,并启动生成用户界面,如下:
Qt 6环境搭建成功,可以接着往下进行了。
注意
如果你是一个系统集成商,你需要安装Qt SDK来获得最新的稳定Qt版本,以及针对特定设备目标的源代码编译的Qt版本。
注意
从头构建
如果您想从命令行构建Qt 6,首先需要获取代码库的副本并构建它。访问Qt的wiki以获得关于如何从git构建Qt的最新解释。
编译成功后(约2杯咖啡的时间),Qt 6将在qtbase文件夹中可用。如果你想测试你的编译,你现在可以用Qt 6自带的默认运行时运行这个例子:
$ qtbase/bin/qml
应用类型
本章会涉及Qt 6所能创建的各类应用概述。不局限本文所列,Qt 6能带给你更好的创意。
控制台应用
控制台应用没有图形用户界面,通常用于系统服务或命令行程序。Qt 6自带一系列组件可以高效地创建跨平台的控制台应用,如网络API,字符处理,命令行解析等。Qt是基于C++的高级API,所以在编写效率和执行效率上都有不错表现。不要以为Qt仅是UI工具,他能实现的比这要多。
字符处理
用一个例子来验证两个常量字符串相加。可能这不是一个有实际意义的应用,便它展示了一个没有消息事件循环的原生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文件,并遍历行来获取列信息。20行代码便可实现从CSV文件中获取表格数据。从文件中读取出来的是字节流,想得到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控制台应用的部分到些结束。
C++ 控件应用
控制台应用挺方便的,但有时需要图形用户界面。而图形用户界面的应用也很可能需要后台读写文件,通过网络传传输,或在容器中保存数据。
在控件应用的示例代码中,我们仅创建窗体并显示。在Qt里,没有parent的控件就是一个window窗体。我们用一个范围指针来确保当指针失效时,控件相应被销毁。应用程序对象(application)封闭了Qt 运行时,以调用exec()
方法来启动事件循环。从这里开始,应用会对用户输入(如鼠标、键盘)事件、网络及文件IO等其它事件的触发做出响应。当事件循环结束时,程序退出。通过调用quite()
函数或关闭窗体来退出应用程序。
运行如下代码,会弹出一个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();
- }
用户控件
在开发用户界面时,可能会用到用户自定义控件。通常,一个控件就是一个窗体区域内填充绘画内容。而且,控件应能感知并处理鼠标键盘事件以及其它外部事件的能力。在Qt里,这需要继承QWidget类并重写绘画和事件处理函数。
- 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;
- };
实现时,我们为控件画上边,并在鼠标末次位置画一个小矩形。这是较典型的较底层的用户控件。鼠标与键盘事件改变了控件的状态并触发重绘。可以不必追究这些代码的细节,仅需知道,程序具备自定义并实现控件的能力。Qt自带大量成熟控件,基本上不必自己编写控件。
- 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();
- }
桌面控件
Qt 开发者已经编写了大量的桌面控件,在各平台表现优秀。开发应用,主要就是在容器中将这些控件组织安排好。一个控件也可以成为其它控件的容器。这是通过‘父-子’(parent-child) 关系实现的。这意味着,现成的控件,如按钮、单选框、复选框、列表控件、网格控件等,会成为其它控件的子控件。通过以下代码演示。
这是被称为控件容器的头文件
- 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;
- };
实现时使用了布局控件(layouts)以更好的编排布局。当容器小部件重新调整大小时,布局管理器根据一些大小策略重新布局小部件。本例,我们有一个列表、一个行编辑框和一个按钮,它们是垂直排列的,允许用户编辑一个城市列表。我们使用 Qt 的信号(signal)和槽(slots)来连接发送者和接收者对象。
- 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;
- };
在实现中,场景首先附加到视图。视图是一个小部件,并排列在我们的容器小部件中。最后,我们在场景中添加一个小矩形,然后将其渲染到视图上。
- 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);
数据适配
到目前为止,我们主要介绍了基本数据类型以及如何使用小部件和图形视图。在实际应用程序中,通常需要大量的结构化数据,这些数据可能还需要持久存储。最后,还需要显示数据。为此,Qt 使用模型(models)。一个简单的模型(model)是字符串列表模型,它用字符串填充,然后附加到列表视图。
- m_view = new QListView(this);
- m_model = new QStringListModel(this);
- view->setModel(m_model);
- QList<QString> cities;
- cities << "Munich" << "Paris" << "London";
- m_model->setStringList(cities);
存储和检索数据的另一种流行方法是 SQL。 Qt 带有嵌入式 SQLite,并且还支持其他数据库引擎(例如 MySQL 和 PostgreSQL)。首先,需要创建结构(schema)数据库,如下所示:
- 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 = QSqlTableModel(this);
- m_model->setTable("city");
- m_model->setHeaderData(0, Qt::Horizontal, "City");
- m_model->setHeaderData(1, Qt::Horizontal, "Country");
- view->setModel(m_model);
- m_model->select();
对于更高级别的模型操作,Qt 提供了排序文件代理模型,允许对模型进行排序、过滤和转换。
- QSortFilterProxyModel* proxy = new QSortFilterProxyModel(this);
- proxy->setSourceModel(m_model);
- view->setModel(proxy);
- view->setSortingEnabled(true);
指定哪些列成为过滤器并指定过滤器的字符串参数,来实现过滤。
- proxy->setFilterKeyColumn(0);
- proxy->setFilterCaseSensitive(Qt::CaseInsensitive);
- proxy->setFilterFixedString(QString)
过滤器代理模型比这里演示的要强大得多。现在,记住它的存在就足够了。
注意
以上是可以使用 Qt 5 开发的不同类型的经典应用程序的概述。桌面开发正经历快速变化,很快移动设备将成为我们明天的桌面。移动设备具有不同的用户界面设计。它们比桌面应用程序简单得多。他们只做一件事,而且做事简单而专注。动画是移动体验的重要组成部分。用户界面需要生动流畅。传统的 Qt 技术不太适合这个市场。
Qt Quick Application
现代软件开发中存在固有的冲突。用户界面的发展速度比我们的后端服务快得多。在传统技术中,开发所谓的前端与后端的速度相同。当客户想要在项目期间更改用户界面或在项目期间开发用户界面的想法时,这会导致冲突。敏捷项目,需要敏捷方法。
Qt Quick 提供了一个声明性环境,其中用户界面(前端)像 HTML 一样声明,而后端是本机 C++ 代码。可谓两全其美。
以下是一个简单的 Qt Quick UI
- import QtQuick
- Rectangle {
- width: 240; height: 240
- Rectangle {
- width: 40; height: 40
- anchors.centerIn: parent
- color: '#FFBB33'
- }
- }
声明语言称为 QML,它需要运行时来执行它。 Qt 提供了一个称为 qml 的标准运行时。您还可以编写自定义运行时。为此,如果想快速查看运行效果,需要在C++ 源码中将设置为`main.qml`。然后可以显示用户界面。
- #include <QtGui>
- #include <QtQml>
- int main(int argc, char *argv[])
- {
- QGuiApplication app(argc, argv);
- QQmlApplicationEngine engine("main.qml");
- return app.exec();
- }
让我们回到我们之前的例子。在一个示例中,我们使用了 C++ 城市模型。如果我们可以在我们的声明性 QML 代码中使用这个模型,那就太好了。
为了实现这一点,我们首先对前端进行编码,看看我们希望如何使用城市模型。在这种情况下,前端需要一个名为 cityModel 的对象,我们可以在列表视图中使用它。
- import QtQuick
- Rectangle {
- width: 240; height: 120
- ListView {
- width: 180; height: 120
- anchors.centerIn: parent
- model: cityModel
- delegate: Text { text: model.city }
- }
- }
要启用 cityModel,我们主要可以重用之前的模型,并将上下文属性添加到我们的根上下文中。根上下文是主文档中的另一个根元素。
- m_model = QSqlTableModel(this);
- ... // some magic code
- QHash<int, QByteArray> roles;
- roles[Qt::UserRole+1] = "city";
- roles[Qt::UserRole+2] = "country";
- m_model->setRoleNames(roles);
- engine.rootContext()->setContextProperty("cityModel", m_model);
总结
我们已经了解了如何安装 Qt SDK 以及如何创建我们的第一个应用程序。然后我们向您介绍了不同的应用程序类型,为您提供了 Qt 的概述,展示了 Qt 为应用程序开发提供的一些功能。我希望您对 Qt 是一个非常丰富的用户界面工具包有一个很好的印象,它为应用程序开发人员提供了所希望的一切,甚至更多。尽管如此,Qt 不局限在特定的库中,因为您始终可以使用其他库,甚至可以自己扩展 Qt。它在支持不同的应用类型方面也很丰富:控制台、经典桌面用户界面和触摸用户界面。