QML Book 第十二章 存储

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);
                }
            }
        });
    }
}

** 疯狂矩形 **

例如,假设我们想在我们的场景中存储矩形的位置。

crazy_rect

这里是我们的基础例子。

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++ 部分为我们提供的丰富且强大的接口是个好主意。

本章完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容