这一章介绍了如何使用Qt5进行开发。我们将告诉你如何安装Qt软件开发工具包(Qt SDK)和如何使用Qt Creator集成开发环境(Qt Creator IDE)创建并运行一个简单的hello word应用程序。
注意
这章的源代码能够在assetts folder找到。
安装Qt5软件工具包(Installing Qt 5 SDK)
Qt软件工具包包含了编译桌面或者嵌入式应用程序的工具。最新的版本可以从Qt-Project下载。我们将使用这种方法开始。
软件工具包自身包含了一个维护工具允许你更新到最新版本的软件工具包。
Qt软件工具包非常容易安装,并且附带了一个它自身的快速集成开发环境叫做Qt Creator。这个集成开发环境可以让你高效的使用Qt进行开发,我们推荐给所有的读者使用。在任何情况下Qt都可以通过命令的方式来编译,你可以自由的选择你的代码编辑器。
当你安装软件工具包时,你最好选择默认的选项确保Qt 5.x可以被使用。然后一切准备就绪。
你好世界(Hello World)
为了测试你的安装,我们创建一个简单的应用程序hello world.打开Qt Creator并且创建一个Qt Quick UI Project(File->New File 或者 Project-> Qt Quick Project -> Qt Quick UI)并且给项目取名 HelloWorld。
注意
Qt Creator集成开发环境允许你创建不同类型的应用程序。如果没有另外说明,我们都创建Qt Quick UI Project。
提示
一个典型的Qt Quick应用程序在运行时解释,与本地插件或者本地代码在运行时解释代码一样。对于才开始的我们不需要关注本地端的解释开发,只需要把注意力集中在Qt5运行时的方面。
Qt Creator将会为我们创建几个文件。HellWorld.qmlproject文件是项目文件,保存了项目的配置信息。这个文件由Qt Creator管理,我们不需要编辑它。
另一个文件HelloWorld.qml保存我们应用程序的代码。打开它,并且尝试想想这个应用程序要做什么,然后再继续读下去。
// HelloWorld.qml
import QtQuick 2.0
Rectangle {
width: 360
height: 360
Text {
anchors.centerIn: parent
text: "Hello World"
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
HelloWorld.qml使用QML语言来编写。我们将在下一章更深入的讨论QML语言,现在只需要知道它描述了一系列有层次的用户界面。这个代码指定了显示一个360乘以360像素的一个矩形,矩形中间有一个“Hello World”的文本。鼠标区域覆盖了整个矩形,当用户点击它时,程序就会退出。
你自己可以运行这个应用程序,点击左边的运行或者从菜单选择select Bulid->Run。
如果一切顺利,你将看到下面这个窗口:
Qt 5似乎已经可以工作了,我们接着继续。
建议
如果你是一个名系统集成人员,你会想要安装最新稳定的Qt版本,将这个Qt版本的源代码编译到你特定的目标机器上。
从头开始构建
如果你想使用命令行的方式构建Qt5,你首先需要拷贝一个代码库并构建他。
git clone git://gitorious.org/qt/qt5.git
cd qt5
./init-repository
./configure -prefix $PWD/qtbase -opensource
make -j4
等待两杯咖啡的时间编译完成后,qtbase文件夹中将会出现可以使用的Qt5。任何饮料都好,不过我喜欢喝着咖啡等待最好的结果。
如果你想测试你的编译,只需简单的启动qtbase/bin/qmlscene并且选择一个QtQuick的例子运行,或者跟着我们进入下一章。
为了测试你的安装,我们创建了一个简单的hello world应用程序。创建一个空的qml文件example1.qml,使用你最喜爱的文本编辑器并且粘贴一下内容:
// HelloWorld.qml
import QtQuick 2.0
Rectangle {
width: 360
height: 360
Text {
anchors.centerIn: parent
text: "Greetings from Qt5"
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
你现在使用来自Qt5的默认运行环境来可以运行这个例子:
$ qtbase/bin/qmlscene
应用程序类型(Application Types)
这一节贯穿了可能使用Qt5编写的不同类型的应用程序。没有任何建议的选择,只是想告诉读者Qt5通常情况下能做些什么。
2.3.1 控制台应用程序
一个控制台应用程序不需要提供任何人机交互图形界面通常被称作系统服务,或者通过命令行来运行。Qt5附带了一系列现成的组件来帮助你非常有效的创建跨平台的控制台应用程序。例如网络应用程序编程接口或者文件应用程序编程接口,字符串的处理,自Qt5.1发布的高效的命令解析器。由于Qt是基于C++的高级应用程序接口,你能够快速的编程并且程序拥有快速的执行速度。不要认为Qt仅仅只是用户界面工具,它也提供了许多其它的功能。
字符串处理
在第一个例子中我们展示了怎样简单的增加两个字符串常量。这不是一个有用的应用程序,但能让你了解本地端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自带大量方便使用的容器类,并且其中的元素使用相同的应用程序接口模式。
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文件并且遍历提取每一行的每一个单元的数据。我们从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 empty string 'QString("")'
if(line.isEmpty()) {
continue;
}
// test for null string 'String()'
if(line.isNull()) {
break;
}
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 窗口应用程序
基于控制台的应用程序非常方便,但是有时候你需要有一些用户界面。但是基于用户界面的应用程序需要后端来写入/读取文件,使用网络进行通讯或者保存数据到一个容器中。
在第一个基于窗口的应用程序代码片段,我们仅仅只创建了一个窗口并显示它。没有父对象的窗口部件是Qt世界中的一个窗口。我们使用智能指针来确保当智能指针指向范围外时窗口会被删除掉。
这个应用程序对象封装了Qt的运行,调用exec开始我们的事件循环。从这里开始我们的应用程序只响应由鼠标或者键盘或者其它的例如网络或者文件IO的事件触发。应用程序也只有在事件循环退出时才退出,在应用程序中调用”quit()”或者关掉窗口来退出。
当你运行这段代码的时候你可以看到一个240乘以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();
}
自定义窗口部件
当你使用用户界面时你需要创建一个自定义的窗口部件。典型的窗口是一个窗口部件区域的绘制调用。附加一些窗口部件内部如何处理外部触发的键盘或者鼠标输入。为此我们需要继承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
在实现中我们绘制了窗口的边界并在鼠标最后的位置上绘制了一个小的矩形框。这是一个非常典型的低层次的自定义窗口部件。鼠标或者键盘事件会改变窗口的内部状态并触发重新绘制。我们不需要更加详细的分析这个代码,你应该有能力分析它。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的开发者们已经为你做好大量现成的桌面窗口部件,在不同的操作系统中他们看起来都像是本地的窗口部件。你的工作只需要在一个打的窗口容器中安排不同的的窗口部件。在Qt中一个窗口部件能够包含其它的窗口部件。这个操作由分配父子关系来完成。这意味着我们需要准备类似按钮(button),复选框(check box),单选按钮(radio button)的窗口部件并且对它们进行布局。下面展示了一种完成的方法。
这里有一个头文件就是所谓的窗口部件容器。
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidgetQWidget *parent = 0);
private slots:
void itemClicked(QListWidgetItem* item);
void updateItem();
private:
QListWidget *m_widget;
QLineEdit *m_edit;
QPushButton *m_button;
};
在实现中我们使用布局来更好的安排我们的窗口部件。当容器窗口部件大小被改变后它会按照窗口部件的大小策略进行重新布局。在这个例子中我们有一个链表窗口部件,行编辑器与按钮垂直排列来编辑一个城市的链表。我们使用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 graphics view是一个很好的选择。一个图形视窗(graphics view)能够在一个场景(scene)排列简单的几何图形。用户可以与这些图形交互,它们使用一定的算法放置在场景(scene)上。填充一个图形视图你需要一个图形窗口(graphics view)和一个图形场景(graphics scene)。一个图形场景(scene)连接在一个图形窗口(view)上,图形对象(graphics item)是被放在图形场景(scene)上的。这里有一个简单的例子,首先头文件定义了图形窗口(view)与图形场景(scene)。
class CustomWidgetV2 : public QWidget
{
Q_OBJECT
public:
explicit CustomWidgetV2(QWidget *parent = 0);
private:
QGraphicsView *m_view;
QGraphicsScene *m_scene;
};
在实现中首先将图形场景(scene)与图形窗口(view)连接。图形窗口(view)是一个窗口部件,能够被我们的窗口部件容器包含。最后我们添加一个小的矩形框在图形场景(scene)中。然后它会被渲染到我们的图形窗口(view)上。
#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 数据适配
到现在我们已经知道了大多数的基本数据类型,并且知道如何使用窗口部件和图形视图(graphics views)。通常在应用程序中你需要处理大量的结构体数据,也可能需要不停的储存它们,或者这些数据需要被用来显示。对于这些Qt使用了模型的概念。下面一个简单的模型是字符串链表模型,它被一大堆字符串填满然后与一个链表视图(list view)连接。
m_view = new QListView(this);
m_model = new QStringListModel(this);
view->setModel(m_model);
QList<QString> cities;
cities << "Munich" << "Paris" << "London";
model->setStringList(cities);
另一个比较普遍的用法是使用SQL(结构化数据查询语言)来存储和读取数据。Qt自身附带了嵌入式版的SQLLite并且也支持其它的数据引擎(比如MySQL,PostgresSQL,等等)。首先你需要使用一个模式来创建你的数据库,比如像这样:
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,我们需要在我们的项目文件(*.pro)中加入sql模块。
QT += sql
然后我们需要c++来打开我们的数据库。首先我们需要获取一个指定的数据库引擎的数据对象。使用这个数据库对象我们可以打开数据库。对于SQLLite这样的数据库我们可以指定一个数据库文件的路径。Qt提供了一些高级的数据库模型,其中有一种表格模型(table model)使用表格标示符和一个选项分支语句(where clause)来选择数据。这个模型的结果能够与一个链表视图连接,就像之前连接其它数据模型一样。
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)
过滤代理模型比这里演示的要强大的多,现在我们只需要知道有它的存在就够了。
注意
这里是综述了你可以在Qt5中开发的不同类型的经典应用程序。桌面应用程序正在发生着改变,不久之后移动设备将会为占据我们的世界。移动设备的用户界面设计非常不同。它们相对于桌面应用程序更加简洁,只需要专注的做一件事情。动画效果是一个非常重要的部分,用户界面需要生动活泼。传统的Qt技术已经不适于这些市场了。
接下来:Qt Quick将会解决这个问题。
2.3.4 Qt Quick应用程序
在现代的软件开发中有一个内在的冲突,用户界面的改变速度远远高于我们的后端服务。在传统的技术中我们开发的前端需要与后端保持相同的步调。当一个项目在开发时用户想要改变用户界面,或者在一个项目中开发一个用户界面的想法就会引发这个冲突。敏捷项目需要敏捷的方法。
Qt Quick 提供了一个类似HTML声明语言的环境应用程序作为你的用户界面前端(the front-end),在你的后端使用本地的c++代码。这样允许你在两端都游刃有余。
下面是一个简单的Qt Quick UI的例子。
import QtQuick 2.0
Rectangle {
width: 240; height: 1230
Rectangle {
width: 40; height: 40
anchors.centerIn: parent
color: '#FFBB33'
}
}
这种声明语言被称作QML,它需要在运行时启动。Qt提供了一个典型的运行环境叫做qmlscene,但是想要写一个自定义的允许环境也不是很困难,我们需要一个快速视图(quick view)并且将QML文档作为它的资源。剩下的事情就只是展示我们的用户界面了。
QQuickView* view = new QQuickView();
QUrl source = Qurl::fromLocalUrl("main.qml");
view->setSource(source);
view.show();
回到我们之前的例子,在一个例子中我们使用了一个c++的城市数据模型。如果我们能够在QML代码中使用它将会更加的好。
为了实现它我们首先要编写前端代码怎样展示我们需要使用的城市数据模型。在这一个例子中前端指定了一个对象叫做cityModel,我们可以在链表视图(list view)中使用它。
import QtQuick 2.0
Rectangle {
width: 240; height: 120
ListView {
width: 180; height: 120
anchors.centerIn: parent
model: cityModel
delegate: Text { text: model.city }
}
}
为了使用cityModel,我们通常需要重复使用我们以前的数据模型,给我们的根环境(root context)加上一个内容属性(context property)。(root context是在另一个文档的根元素中)。
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);
view->rootContext()->setContextProperty("cityModel", m_model);
警告
这不是完全正确的用法,作为包含在SQL表格模型列中的数据,一个QML模型的任务是来表达这些数据。所以需要做一个在列和任务之间的映射关系。请查看来QML and QSqlTableModel获得更多的信息。
总结( Summary)
我们已经知道了如何安装Qt软件开发工具包,并且知道如何创建我们的应用。我们向你展示和概述了使用Qt开发不同类型的应用程序。展示Qt可以给你的应用程序开发提供的一些功能。我希望你对Qt留下一个好的印象,Qt是一个非常好的用户界面开发工具并且尽可能的提供了一个应用开发者期望的东西。当前你也不必一直锁定使用Qt,你也可以使用其它的库或者自己来扩展Qt。Qt对于不同类型的应用程序开发支持非常丰富:包括控制台程序,经典的桌面用户界面程序和触摸式用户界面程序。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2291184112@qq.com