本章將介紹使用Qt5開發。我們將告訴你如何安裝Qt SDK,如何使用Qt Creator IDE創建以及運行一個簡單的Hello World應用程序。
一、安裝Qt5 SDK
Qt SDK包括構建桌面或嵌入式應用所需的工具,最新版本可以從Qt-Project homepage上獲取(推薦方式)。該SDK本身具有維護工具,可以讓你更新SDK到最新版本。
Qt SDK易於安裝並配有自己的IDE,促使Qt Creator快速發展。該IDE是一個高效Qt編程環境,推薦給所有的讀者。在任何情況下,Qt可以在命令行中使用,也可以使用自己選擇的代碼編輯器。
安裝SDK時,應該選擇默認選項,並確保Qt5.x已啟用,那么就准備好了。
二、Hello World
為了測試安裝,我們將創建一個小型的Hello World應用程序。請打開的Qt Creator並創建一個Qt Quick圖形界面項目(File->New File or Project->Qt Quick Project->Qt Quick UI),並將該項目命名HelloWorld。
注:
Qt Creator IDE可以讓你創建多種類型的應用程序。如果沒有特別聲明,我們總是使用Qt Quick UI項目。
Qt Creator中會給你創建幾個文件。HelloWorld.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();
}
}
}
HelloWord.qml是用QML語言寫的。我們將在下一章對QML語言做深入討論。只是大多的描述在一系列分層的元素的用戶界面上,特別是這段代碼顯示一個中心包含“Hello World”文本的360x360像素的矩形。鼠標區域跨越整個矩形,當用戶點擊時,程序退出。
運行程序,請按左邊的運行工具或從菜單中選擇Build->Run。如果一切順利的話應該看到這樣的界面:
三、應用程序類型
這一部分是一個貫穿不同類型的應用程序類型,有人可能在Qt5用到。它不局限於所提出的選擇,但應該給讀者一個更好的主意—關於Qt5能夠做些什么。
3.1、控制台應用程序
控制台應用程序不提供任何圖形人機界面,通常被稱作系統服務的一部分或者命令行,或者在命令行中。 Qt5也有一系列現成的組件,幫你跨平台的和非常有效的創建控制台應用程序。例如網絡API或文件API。還字符串處理或自Qt5.1開始高效的命令行解析器。Qt是一個在C++之上的讓你獲取編程速度與執行速度的高級API。不要認為Qt只是UI工具包,它提供了很多。
字符串處理
第一個例子,我們演示如何很簡單的添加2個常量字符串。這個程序不是非常有用,但讓你知道沒有一個事件循環的本機C++應用程序看起來是什么樣的。
// module or class includes
#include
// 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;
}
容器類
本示例將一個list和list迭代應用程序。Qt提供了大量的容器類,易於使用,並使用相同的API范式。
QString s1("Hello");
QString s2("Qt");
QList<</span>QString> list;
// stream into containers
list << s1 << s2;
// Java and STL like iterators
QListIterator<</span>QString> iter(list);
while(iter.hasNext()) {
cout << iter.next();
if(iter.hasNext()) {
cout << " ";
}
}
cout << "!" << endl;
這里,我們將展示一些先進的list功能,允許給一個字符串中加入一系列字符串。當進行基本的文本輸入時非常方便。倒轉(string->string-list)也可以使用的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<</span>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基於控制台的應用程序部分就到這里。
3.2、窗口應用程序
基於控制台的應用程序非常方便,但有時需要有一些圖形界面來顯示。而且基於圖形界面的應用程序將需要一些后台文件的讀取/寫入,通過網絡進行通信或將數據保存在一個容器中。
第一個片段為基礎的窗口應用程序,做的較少——創建一個窗口並顯示它。沒有父親的窗體在Qt的世界是一個窗口。我們使用的范圍指針,以確保作用域指針超出范圍時部件刪除。應用程序對象封裝了Qt運行時,並使用exec調用開始事件循環。從那里上的應用程序重新僅作用於通過鼠標或鍵盤或類似網絡或文件IO等事件提供觸發的事件。當事件循環退出該應用程序將退出,這是通過調用‘quit()’來完成對窗口的關閉。
自定義部件
當工作在需要自定義部件(widget)的用戶接口上。通常情況下一個小部件是一個充滿繪畫調用的窗口區域。附加部件有內置的知識關於如何處理鍵盤或鼠標輸入在外部觸發反應。要在Qt中做到這一點,我們需要從QWidget派生並重寫一些繪制和事件處理的函數。
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include
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
#include
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QScopedPointer<</span>QWidget> widget(new CustomWidget());
widget->resize(240, 120);
widget->show();
return app.exec();
}
實現時,我們畫一個小邊界小部件和一個小矩形在鼠標最后的位置,這是非常典型的一個低級別的自定義部件。鼠標或鍵盤事件改變窗口小部件的內部狀態,並觸發一個繪畫更新。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中,一個窗口小部件也可以是其他部件的一個容器,這由父子關系來決定。這意味着我們需要使現成的部件如按鈕、復選框、單選按鈕做為子部件排列布局到另一個部件中。實現這一目標的方法之一如下。
下面是一個所謂的部件容器的頭文件。
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;
};
在實現中,我們使用布局以便更好地安排我們的小部件。根據一些大小策略,當容器部件重新調整大小時,重新布局小部件。在這個例子中,我們有一個列表,輸入框和按鈕垂直排列,以允許編輯城市列表。我們使用信號和槽連接發送和接收對象。
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是一個很好的選擇。圖形視圖在一個場景中排列了簡單的幾何形狀,用戶可以與這些形狀進行交互,或者使用一種算法定位。填充圖形視圖需要一個圖形視圖和一個圖形場景。場景附加到視圖上並填入圖形項。這有一個簡短的例子。首先聲明視圖和場景的頭文件。
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);
}
3.3、自適應數據
到現在我們已經涵蓋大部分基本數據類型以及如何使用小部件和圖形視圖。通常在應用程序中,將需要更大量的結構化數據,也需要持久存儲。這些數據也需要被顯示出來。對於這種情況,Qt使用了模型機型。一個簡單的模型是字符串列表模型,將會充滿字符串,然后附加到一個列表視圖里。
m_view = new QListView(this);
m_model = new QStringListModel(this);
view->setModel(m_model);
QList<</span>QString> cities;
cities << "Munich" << "Paris" << "London";
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 = 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實現救贖。
3.4、Qt Quick應用程序
在現代軟件開發中有一個內在沖突,用戶界面比我們的后台服務移動得快地多。在傳統的技術里在開發所謂的前台具有與后台相同的速度,其結果都是矛盾的,當客戶想要在一個項目中更改用戶界面,或在項目中有開發用戶界面的想法。敏捷項目,需要敏捷方法。
Qt Quick的提供了一個聲明環境,其用戶界面(前台)可以像HTML一樣聲明,后台是本機C++代碼。這可以從兩個世界得到的。
這是一個簡單的Qt Quick用戶界面,如下:
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對象,其可以用在一個列表視圖內。
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我們主要重用以前的模型並添加上下文屬性到根上下文(根上下文是主文檔中的其它根元素)
m_model = QSqlTableModel(this);
... // some magic code
QHash<</span>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頁面。
總結
我們已經看到了如何安裝Qt SDK和如何創建第一個應用程序,介紹了不同類型的應用程序和Qt的概述,展示了Qt提供應用程序的開發的一些功能。我希望你有一個良好的印象,Qt是一個非常豐富的用戶界面工具包,並提供了的應用程序開發人員希望的一切。當然,Qt不限制你,仍可以用到其他庫或自己擴展Qt。它還富含支持不同的應用模式:控制台,經典的桌面用戶界面和觸摸用戶界面。