12.存储(Storage)
本章的作者:jryannel
** 注意: **
最新的构建时间:2016/03/21
这章的源代码能够在assetts folder找到。
本章将介绍如何在 Qt 5 中使用 Qt Quick 存储数据。Qt Quick 仅提供直接存储本地数据的有限方法。在这个意义上,它的行为更像一个浏览器。在许多存储数据的事情上其实是由 C++ 后端进行处理的,QML 所需的功能被导出到 Qt Quick 前端。Qt Quick 不提供对主机文件系统的访问权限,可以从 Qt/C ++ 方面使用这些文件来读取和写入文件。因此,后端工程师的任务是编写这样的插件,或者可以使用网络通道来与本地服务器进行通信,从而提供这些功能。
每个应用程序都需要持续存储较小或较大的信息。这可以在本地的文件系统或远程的服务器上完成。一些信息将被结构化并且简单(例如 settings),一些信息将会变大而且很复杂,例如文档文件,一些信息将被大量且结构化,并且将需要某种数据库连接。这里我们将主要介绍 Qt Quick 内置的存储数据的功能以及网络方式。
12.1 Settings
Qt 本身具有 C++ 的 QSettings 类,它允许我们以系统依赖的方式存储应用程序设置(aka 选项,首选项)。它使用我们的操作系统提供的基础架构。此外,它还支持用于处理跨平台设置文件的常见 INI 文件格式。
在 Qt 5.2 Settings 已进入 QML 世界。但是这些 API 仍在实验模块中,这意味着这些 API 可能在将来有所改变。所以还是需要小心使用的。
下面是一个小例子,它将一个颜色值应用于一个基本矩形。每次用户点击窗口时,都会生成一个新的随机颜色。当应用程序关闭并重新启动时,我们应该会看到最后一个颜色。默认颜色应该是最初在根矩形上设置的颜色。
import QtQuick 2.5
import Qt.labs.settings 1.0
Rectangle {
id: root
width: 320; height: 240
color: '#000000'
Settings {
id: settings
property alias color: root.color
}
MousArea {
anchors.fill: parent
onClicked: root.color = Qt.hsla(Math.random(), 0.5, 0.5, 1.0);
}
}
每次值更改时都会存储设置值。 这可能并不总是你想要的。要仅在需要时存储设置,可以使用标准属性:
Rectangle {
id: root
color: settings.color
Settings {
id: settings
property color color: '#000000'
}
function storeSettings() { // executed maybe on destruction
settings.color = root.color
}
}
也可以使用 category 属性将设置存储到不同的类别中。
Settings {
category: 'window'
property alias x: window.x
property alias y: window.x
property alias width: window.width
property alias height: window.height
}
设置根据我们的应用程序名称,组织和域存储。该信息通常设置在您的 C++ 代码的主要功能中。
int main(int argc, char** argv) {
...
QCoreApplication::setApplicationName("Awesome Application");
QCoreApplication::setOrganizationName("Awesome Company");
QCoreApplication::setOrganizationDomain("org.awesome");
...
}
12.2 本地存储 - SQL
Qt Quick 支持从本地存储 API 的 Web 浏览器知道的本地存储 API。该 API 在加入 “import QtQuick.LocalStorage 2.0” 语句的情况下可用。
一般来说,它基于给定的数据库名称和版本,将内容存储在基于唯一 ID 的文件中的系统特定位置的 SQLITE 数据库中。无法列出或删除现有的数据库。我们可以从 QQmlEngine::offlineStoragePath() 中找到存储位置。
我们可以先使用 API 创建数据库对象,然后在数据库上创建事务。每个事务可以包含一个或多个 SQL 查询。当 SQL 查询在事务中失败时,事务将回滚。
例如,要从一个简单的笔记表中读取一个文本列,您可以使用本地存储,如下所示:
import QtQuick 2.5
import QtQuick.LocalStorage 2.0
Item {
Component.onCompleted: {
var db = LocalStorage.openDatabaseSync("MyExample", "1.0", "Example database", 10000);
db.transaction( function(tx) {
var result = tx.executeSql('select * from notes');
for(var i = 0; i < result.rows.length; i++) {
print(result.rows[i].text);
}
}
});
}
}
** 疯狂矩形 **
例如,假设我们想在我们的场景中存储矩形的位置。
这里是我们的基础例子。
import QtQuick 2.5
Item {
width: 400
height: 400
Rectangle {
id: crazy
objectName: 'crazy'
width: 100
height: 100
x: 50
y: 50
color: "#53d769"
border.color: Qt.lighter(color, 1.1)
Text {
anchors.centerIn: parent
text: Math.round(parent.x) + '/' + Math.round(parent.y)
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}
我们的目标是,我们可以随意拖动矩形。当我们关闭应用程序并再次启动它时,矩形将处于上次关闭时相同的位置。
现在我们要补充的是,矩形的 x/y 位置应该存储在 SQL DB 中。为此,我们需要添加一个 init,read 和 store 数据库函数。当组件完成和组件销毁时,将调用这些功能:
import QtQuick 2.5
import QtQuick.LocalStorage 2.0
Item {
// reference to the database object
property var db;
function initDatabase() {
// initialize the database object
}
function storeData() {
// stores data to DB
}
function readData() {
// reads and applies data from DB
}
Component.onCompleted: {
initDatabase();
readData();
}
Component.onDestruction: {
storeData();
}
}
我们还可以在自己的 JS 库中提取数据库代码,这样就可以执行所有的逻辑。如果逻辑变得更加复杂,这将是首选方式。
在数据库初始化函数中,我们创建了 DB 对象,并确保创建了 SQL 表。
function initDatabase() {
print('initDatabase()')
db = LocalStorage.openDatabaseSync("CrazyBox", "1.0", "A box who remembers its position", 100000);
db.transaction( function(tx) {
print('... create table')
tx.executeSql('CREATE TABLE IF NOT EXISTS data(name TEXT, value TEXT)');
});
}
应用程序接下来调用读取功能从数据库读取现有数据。这里我们需要区分表中是否有数据。该检查我们可以通过查看 select 子句返回的行数实现。
function readData() {
print('readData()')
if(!db) { return; }
db.transaction( function(tx) {
print('... read crazy object')
var result = tx.executeSql('select * from data where name="crazy"');
if(result.rows.length === 1) {
print('... update crazy geometry')
// get the value column
var value = result.rows[0].value;
// convert to JS object
var obj = JSON.parse(value)
// apply to object
crazy.x = obj.x;
crazy.y = obj.y;
}
});
}
我们期望数据在值列中存储一个 JSON 字符串。这不是典型的 SQL,但 JS 代码能很好地工作。因此,不是将 x 和 y 作为属性存储在表中,而是使用 JSON stringify/parse 方法将它们存储为完整的 JS 对象。最后我们得到一个有 x 和 y 属性的有效的 JS 对象,我们可以应用在我们的疯狂矩形上。
要存储数据,我们需要区分更新和插入案例。当记录已经存在时,我们使用update,如果没有名称 “crazy” 下的记录,则进行 insert。
function storeData() {
print('storeData()')
if(!db) { return; }
db.transaction( function(tx) {
print('... check if a crazy object exists')
var result = tx.executeSql('SELECT * from data where name = "crazy"');
// prepare object to be stored as JSON
var obj = { x: crazy.x, y: crazy.y };
if(result.rows.length === 1) {// use update
print('... crazy exists, update it')
result = tx.executeSql('UPDATE data set value=? where name="crazy"', [JSON.stringify(obj)]);
} else { // use insert
print('... crazy does not exists, create it')
result = tx.executeSql('INSERT INTO data VALUES (?,?)', ['crazy', JSON.stringify(obj)]);
}
});
}
不止是选择整个记录集,我们也可以使用 SQLITE 计数函数,如下所示:SELECT COUNT(*) from data where name = "crazy",这将返回使用一行与 select 查询影响的行数。否则这是常见的 SQL 代码。作为附加功能,我们使用 SQL 值绑定使用 “?” 在查询中。
现在,我们可以拖动矩形,当我们退出应用程序时,数据库存储 x/y 的位置,并将其应用于下一次应用程序运行时。
12.3 其他存储 API
要直接从 QML 中存储,这些是主要的存储类型。Qt Quick 的真正实力来自于将其与 C++ 扩展到与本机存储系统接口或使用网络 API 与远程存储系统(如Qt云)进行接口的能力。因此多多了解 Qt/C++ 部分为我们提供的丰富且强大的接口是个好主意。
本章完。