第十四章:存储
存储
本章将讨论在Qt Quick里如何存储和检索数据。Qt Quick仅提供了有限的几种直接存储本地数据的方式。这咱场景下,它的角色更象浏览器。在很多项目中,存储数据是由C++ 后端处理的,并需要将功能函数暴露给Qt Quick前端。Qt Quick并不提供象Qt C++ 那样直接对本地文件系统的读写访问方法。所以后端工程师的职责是写一个这样的插件,或是通过网络访问本地数据服务器,来提供这样的数据访问能力。
每个应用都需要持久化存储或大或小的信息。这可以存在本地文件系统或远程服务器上。有些信息是可以结构化并简化(比如配置信息),有些信息可能大些并复杂一些,比如文档文件,而有些信息是更大的结构化信息并且需要某咱类型的数据库连接。这里主要涵盖Qt Quick内置的存储数据及能力,及通过网络存取数据的能力。
配置
Qt 自带一个Settings
元素来加载和保存设置。但它仍然在试验模块中,这意味着将来其API可能会有较大变化。要注意这一点。
下面是一个小例子,对一个基本的矩形应用了颜色。每次用户点击窗体,会随机生成一个新的颜色。当应用关闭并重新启动后,会看到上次的颜色。默认颜色应该在顶层矩形上定义和初始化的颜色。
import QtQuick
import Qt.labs.settings 1.0
Rectangle {
id: root
width: 320
height: 240
color: '#fff' // default color
Settings {
property alias color: root.color
}
MouseArea {
anchors.fill: parent
// random color
onClicked: root.color = Qt.hsla(Math.random(), 0.5, 0.5, 1.0);
}
}
settings的值在每次值变化时被记录下来。可能你并不想让其总是这样。想在指定时机保存配置值,可以用标准属性来绑定一个在指定时机调用的函数,由函数来修改配置值。
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++ 的main 函数中设置。
int main(int argc, char** argv) {
...
QCoreApplication::setApplicationName("Awesome Application");
QCoreApplication::setOrganizationName("Awesome Company");
QCoreApplication::setOrganizationDomain("org.awesome");
...
}
如果你写的是纯QML的应用,你可以使用全局属性来设置同名的配置,这些全局属性是:Qt.application.name, Qt.application.organization, Qt.application.domain
本地存储—SQL
Qt Quick支持本地存储API,就象页面浏览本地存储那样的API。API要导入"import QtQuick.LocalStorage 2.0"才可用。
通常,内容将被存储到一个命名唯一的Sqlite数据库文件,其存储路径是依赖于给定的数据库名称与版本的系统特定位置。不能列出或删除已存在的数据库。你可以通过QQmlEngine::offlineStoragePath()
来找出存储位置。
使用API首先要创建数据库对象,然后在数据库上创建事务。每个事务可以包括一个或多个SQL 查询。当事务中的一个查询语句执行失败了,事务将被回滚。
比如,从一个简单的记事本表里读取一个text列,可以象如下使用本地存储:
import QtQuick
import QtQuick.LocalStorage 2.0
Item {
Component.onCompleted: {
const db = LocalStorage.openDatabaseSync("MyExample", "1.0", "Example database", 10000)
db.transaction( function(tx) {
const result = tx.executeSql('select * from notes')
for(let i = 0; i < result.rows.length; i++) {
print(result.rows[i].text)
}
})
}
}
疯狂的矩形
下面的例子假定想要存储矩形在屏幕上的位置。
这是例子的主要代码。它包含一个名为crazy
的矩形,矩形可被拖动并以文本显示当前位置的x
和y
值。
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 数据库中。为此,需要添加init, read , store
数据库函数。这些函数在组件完成和销毁时被调用。
import QtQuick
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库文件,来完成所有的相关逻辑。如果逻辑较为复杂,这也是较推荐的方法。
在数据库初始化函数,我们创建数据库对象并确保SQL表格被创建。注意数据库函数做了不少友好的输出,以便可以在控制台来跟踪它的执行情况。
function initDatabase() {
// initialize the database object
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)')
})
}
应用程序接着从数据库读取已存在的数据。这里我们需要区分表中是否已经存在数据。仔细观察查询语句所返回的行数。
function readData() {
// reads and applies data from DB
print('readData()')
if(!db) { return }
db.transaction(function(tx) {
print('... read crazy object')
const result = tx.executeSql('select * from data where name="crazy"')
if(result.rows.length === 1) {
print('... update crazy geometry')
// get the value column
const value = result.rows[0].value
// convert to JS object
const obj = JSON.parse(value)
// apply to object
crazy.x = obj.x
crazy.y = obj.y
}
})
}
我们期待数据以JSON格式字串形式存储于数据列。这不是典型的SQL的样子,但在JS代码中运行良好。所以,我们并非将x/y属性值存在于表中,而是使用JSON的stringify/parse方法将其存储为纯粹的JS对象。最后,我们获得了一个可用的JS对象,这个对象有x和y属性,可以被用于crazy矩形。
存储数据时,需要区分插入和更新的场景。当记录已经存在时要用更新update语句,当在表中没有名为crazy的记录时,要用插入insert语句。
function storeData() {
// stores data to DB
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的count函数,如下:SELECT COUNT(*) from data where name = "crazy"
,它可能返回满足查询语句条件的数据行的数量。这是很普通的SQL代码。作为一个附加的特性,我们在查询语句中使用?
来绑定SQL值。
现在你可以拖动矩形,当退出应用时,数据库会存储矩形位置信息的x/y值,并应用于下次程序启动时的矩形位置信息。