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

** 疯狂矩形 **

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

QML Book 第十二章 存储_第1张图片
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++ 部分为我们提供的丰富且强大的接口是个好主意。

本章完。

你可能感兴趣的:(QML Book 第十二章 存储)