• 第十四章:存储


    第十四章:存储

    存储

    本章将讨论在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的矩形,矩形可被拖动并以文本显示当前位置的xy值。

    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值,并应用于下次程序启动时的矩形位置信息。

  • 相关阅读:
    微服务概念
    Oracle 语法汇总
    Docker 安装 MSSqlServer
    数据库和缓存一致性
    Redis Cluster 集群搭建与扩容、缩容
    阿里云centos 8无法安装应用
    [JavaScript] 异步加载
    ArrayList
    Java面经之:HashMap
    JVM面试题
  • 原文地址:https://www.cnblogs.com/sammy621/p/16064339.html
Copyright © 2020-2023  润新知