美文网首页Qt QML 杂记
QML Book 第十二章 存储

QML Book 第十二章 存储

作者: 赵者也 | 来源:发表于2017-07-15 15:06 被阅读31次

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

    本章完。

    相关文章

      网友评论

        本文标题:QML Book 第十二章 存储

        本文链接:https://www.haomeiwen.com/subject/wtkxhxtx.html