如何在QML应用中动态修改ListModel中的数据并存储它为JSON格式

我们知道JSON数据格式被广泛使用在很多的应用中,它可以帮我们保存我们应用的设置数据等。在QML中的ListViewGridView中,我们使用ListModel来显示它里面的数据。这个数据可以来源于xml或JSON。在ListView或GridView中,我们也可以动态修改ListModel中的数据。那么我们将如何保存这个数据呢?本篇文章也同样适用于xml格式的保存。这个练习就留个开发者自己了。当然保存ListModel中的数据也可以使用到SQLite数据库。这个因人而已!

为了方便我们的设计,我们使用了Ubuntu SDK中提供的“QML App with C++ plugin (qmake)”。这个项目的模版只适用于15.04及以上的target(在14.10中不被支持)。

在plugin中,我们设计了如下的fileio.cpp文件:

fileio.cpp

#include <QStandardPaths>
#include "fileio.h"

FileIO::FileIO(QObject *parent) : QObject(parent)
{
}

FileIO::~FileIO()
{
}

void FileIO::read()
{
    if(m_path.isEmpty()) {
        return;
    }

//    QFile file(m_path.toLocalFile());
    QFile file(m_path.path());

    if(!file.exists()) {
        qWarning() << "Does not exits: " << m_path.toLocalFile();
        return;
    }
    if(file.open(QIODevice::ReadOnly)) {
        QTextStream stream(&file);
        m_text = stream.readAll();
        emit textChanged(m_text);
        qDebug() << "Text has been successfully read!";
    }
}

void FileIO::write()
{
    if(m_source.isEmpty()) {
        return;
    }

    qDebug() << "filename: " << m_path.fileName();
    qDebug() << "path: " << m_path.path();

    QFile file(m_path.path());
    qDebug() << "File path: " << file.fileName();

    if(file.open(QIODevice::WriteOnly)) {
        QTextStream stream(&file);
        stream << m_text;
        qDebug() << "Successfully write to file";
    } else {
        qWarning() << "Failed to write to the file: " << m_path;
    }
}

QString FileIO::source() const
{
    return m_source;
}

QString FileIO::text()
{
    qDebug() << "Going to read the text";
    read();
    return m_text;
}

void FileIO::setSource(QString source)
{
    if (m_source == source)
        return;

    m_source = source;
    emit sourceChanged(source);

    // at the same time update the path
    m_path = QUrl(getFilePath(source));
}

void FileIO::setText(QString text)
{
    if (m_text == text)
        return;

    m_text = text;
    write();
    emit textChanged(text);
}

QString FileIO::getFilePath(const QString filename) const
{
//    QString APP_ID = getenv("APP_ID");
//    QString app_pkgname = APP_ID.split('_')[0];
//    QString path = getenv("XDG_DATA_HOME") +
//            "/" + app_pkgname + "/" + filename;
//    qDebug() << "path: " << path;
//    return path;

    QString writablePath = QStandardPaths::
            writableLocation(QStandardPaths::DataLocation);
    qDebug() << "writablePath: " << writablePath;

    QString absolutePath = QDir(writablePath).absolutePath();
    qDebug() << "absoluePath: " << absolutePath;

    // We need to make sure we have the path for storage
    QDir dir(absolutePath);
    if ( dir.mkdir(absolutePath) ) {
        qDebug() << "Successfully created the path!";
    }

    QString path = absolutePath + "/" + filename;

    qDebug() << "path: " << path;

    return path;
}

在这里特别值得指出的是,由于Ubuntu应用的security,每个应用只有自己的独特的目录可以访问。在这个文件中,我们使用了Qt API QStandardPaths来获得应用的私有目录来访问。在以前的文章“如何使用Ubuntu手机平台中的照相机API来存储照片”中,我们也曾尝试使用环境变量的方法来获取这个目录,但是这些环境变量在电脑Desktop的环境中没有设置。

我们的主程序Main.qml也非常简单:

Main.qml

import QtQuick 2.4
import Ubuntu.Components 1.2
import Savejson 1.0
import "savedata.js" as Data

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "savejson.liu-xiao-guo"

    /*
     This property enables the application to change orientation
     when the device is rotated. The default is false.
    */
    //automaticOrientation: true

    width: units.gu(60)
    height: units.gu(85)

    Page {
        id: mainPage
        title: i18n.tr("savejson")
        property string path: ""

        FileIO {
            id: fileio
            source: "sample.json"
        }

        // The model:
        ListModel {
            id: fruitModel

            objectName: "fruitModel"

            ListElement {
                name: "Apple"; cost: 2.45
                image: "pics/apple.jpg"
                description: "Deciduous"
            }
            ListElement {
                name: "Banana"; cost: 1.95
                image: "pics/banana.jpg"
                description: "Seedless"
            }
            ListElement {
                name: "Cumquat"; cost: 3.25
                image: "pics/cumquat.jpg"
                description: "Citrus"
            }
            ListElement {
                name: "Durian"; cost: 9.95
                image: "pics/durian.jpg"
                description: "Tropical Smelly"
            }
        }

        Component {
            id: listDelegate

            ListItem {
                id: delegateItem
                width: listView.width; height: units.gu(10)
                onPressAndHold: ListView.view.ViewItems.dragMode =
                                !ListView.view.ViewItems.dragMode

                Image {
                    id: pic
                    height: parent.height - units.gu(1)
                    width: height
                    anchors.verticalCenter: parent.verticalCenter
                    anchors.left: parent.left
                    anchors.leftMargin: units.gu(0.5)
                    source: image
                }

                Column {
                    id: content
                    anchors.top: parent.top
                    anchors.left: pic.right
                    anchors.leftMargin: units.gu(2)
                    anchors.topMargin: units.gu(1)
                    width: parent.width - pic.width - units.gu(1)
                    height: parent.height
                    spacing: units.gu(1)

                    Label {
                        text: name
                    }

                    Label { text: description }

                    Label {
                        text: '$' + Number(cost).toFixed(2)
                        font.bold: true
                    }
                }

                trailingActions: ListItemActions {
                    actions: [
                        Action {
                            iconName: "add"

                            onTriggered: {
                                console.log("add is triggered!");
                                fruitModel.setProperty(index, "cost", cost + 0.25);
                            }
                        },
                        Action {
                            iconName: "remove"

                            onTriggered: {
                                console.log("remove is triggered!");
                                fruitModel.setProperty(index, "cost", Math.max(0,cost-0.25));
                            }
                        },
                        Action {
                            iconName: "delete"

                            onTriggered: {
                                console.log("delete is triggered!");
                                fruitModel.remove(index)
                            }
                        }
                    ]
                }

                color: dragMode ? "lightblue" : "lightgray"

                ListView.onAdd: SequentialAnimation {
                    PropertyAction { target: delegateItem; property: "height"; value: 0 }
                    NumberAnimation { target: delegateItem; property: "height"; to: delegateItem.height; duration: 250; easing.type: Easing.InOutQuad }
                }

                ListView.onRemove: SequentialAnimation {
                    PropertyAction { target: delegateItem; property: "ListView.delayRemove"; value: true }
                    NumberAnimation { target: delegateItem; property: "height"; to: 0; duration: 250; easing.type: Easing.InOutQuad }

                    // Make sure delayRemove is set back to false so that the item can be destroyed
                    PropertyAction { target: delegateItem; property: "ListView.delayRemove"; value: false }
                }
            }

        }

        ListView {
            id: listView
            anchors.fill: parent
            anchors.margins: 20
            model: fruitModel
            delegate: listDelegate

            ViewItems.onDragUpdated: {
                if (event.status === ListItemDrag.Moving) {
                    model.move(event.from, event.to, 1);
                }
            }
            moveDisplaced: Transition {
                UbuntuNumberAnimation {
                    property: "y"
                }
            }
        }

        Row {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottomMargin: units.gu(1)
            spacing: units.gu(1)

            Button {
                id: save
                text: "Save JSON"

                onClicked: {
                    console.log("Going to save data!")
                    var data = fruitModel;
                    console.log("model data: " + JSON.stringify(fruitModel, null, 4));

                    var res = Data.serialize(data);
                    console.log("res: " + res);
                    fileio.text = res;
                }
            }

            Button {
                id: load
                text: "Load JSON"

                onClicked: {
                    var json = JSON.parse(fileio.text);
                    console.log("count: " + json.fruits.length);

                    fruitModel.clear();
                    var count = json.fruits.length;
                    for (var i in json.fruits) {
                        var fruit = json.fruits[ i ];
                        console.log("name: " + fruit.name);
                        console.log("image: " + fruit.image );
                        console.log("description: " + fruit.description);
                        console.log("cost: " + fruit.cost);
                        fruitModel.append( fruit );
                    }

                }
            }

        }
    }
}

运行我们的应用,我们的界面如下:

  

 

我们创建了一个ListView列表。在列表中,我们可以通过“+”及“-”来修改水果的价钱,我们也可以删除一个水果。当然,我们也可以长按列表并移动列表中的项来重新排序我们的水果顺序。

最重要的是,我们可以通过FileIO来存储或读取我们所需要的JSON文件。为了方便,我们设置了一个“sample.json”。它在电脑中的路径为“:~/.local/share/savejson.liu-xiao-guo$”。存储的JSON文件例子为:

{
"fruits": [

	{       "name": "Banana",
	        "image": "pics/banana.jpg",
	        "description": "Seedless",
	        "cost": 2.2

	},
	{       "name": "Cumquat",
	        "image": "pics/cumquat.jpg",
	        "description": "Citrus",
	        "cost": 3.25

	},
	{       "name": "Durian",
	        "image": "pics/durian.jpg",
	        "description": "Tropical Smelly",
	        "cost": 9.95

	}
	]
}

在我们修改完设置后,我们可以选择退出应用。在下次启动应用后,我们可以选择“Load JSON”按钮,我们可以看到上次修改的内容被成功地重新装载到应用中。

整个项目的源码在: git clone https://gitcafe.com/ubuntu/savejson.git

时间: 2024-10-24 00:52:25

如何在QML应用中动态修改ListModel中的数据并存储它为JSON格式的相关文章

安卓如何动态修改fragment中的 Menu

1.复写onPrepareOptionsMenu方法 @Override public void onPrepareOptionsMenu(Menu menu) {  menu.clear();//先清除已经建好的menu MenuInflater inflater = getActivity().getMenuInflater(); //根据各种条件,重新设置menu if (isDeleteIconOnActionBar){ inflater.inflate(R.menu.delete_ac

来篇文章:ASP。NET程序中动态修改web.config中的设置项目 (后台CS代码)

朋友们可以自行测试,我这里都没有问题了,鳖了一上午的问题总算解决了 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; usi

Java反射机制可以动态修改实例中final修饰的成员变量吗?

问题:Java反射机制可以动态修改实例中final修饰的成员变量吗? 回答是分两种情况的. 1. 当final修饰的成员变量在定义的时候就初始化了值,那么java反射机制就已经不能动态修改它的值了. 2. 当final修饰的成员变量在定义的时候并没有初始化值的话,那么就还能通过java反射机制来动态修改它的值. 实验: 1. 当final修饰的成员变量在定义的时候就初始化了值 1 public Class Person { 2 private final String name = "damon

如何在.cs中统一动态修改xaml中style资源定义的样式

关于设置控件属性样式的方法已经在之前的博客中有提及过,博客地址:设置控件样式的方法 当然在实际项目编写过程中,不光单纯的需要设置元素样式,有时候需要动态的修改元素的样式,这个时候就有些不 同了.需要针对不同的情形来选择不同的方法修改样式. 情形一:单纯的修改一个控件元素的样式,那么只要在.cs中仅仅针对这个控件的样式属性的修改即可. 情形二:如果是要针对同一类的所有控件的样式进行相同的属性修改,比如针对页面中所有的Label控件进行修改, 那么对应之前你在给这些Label控件设置样式的不同做法,

ASP.NET MVC程序中动态修改form的Action值

在练习ASP.NET MVC时,为了实现一个小功能,POST数据至服务器执行时,需要动态修改form的action值. 下面Insus.NET列举一个例子来演示它.让它简单,明白易了解. 你可以在控制器中,创建3个操作action: 标记1是实现视图,而标记2与3是为form的action.其中Isus.NET有使用ContentResult来替代Response.Write向视图输出结果. 在视图中,我们在form中,放一个文件框,两个铵钮,但没有在form中,设置action值.稍后我们在铵

Java中如何修改Jar中的内容

一.摘要 好长时间没写blog了,之前换了一家公司.表示工作更有战斗力了,可惜就是没时间写文章了.在这段时间其实是遇到很多问题的,只是都是记录下来,并没有花时间去研究解决.但是这周遇到这个问题没办法让我继续前进了.必须记录一下.以被后人使用.不多说了,进入主题. 二.前提 1.对于GA的了解(自行google) 2.对CampaignTrackingReceiver类的了解,他是当从GP上下载并且安装完成一个app的时候,发送一个广播,会在Intent中携带一些数据,一般是Refer值,这里可以

jquery.form插件中动态修改表单数据

jquery.form jquery.form插件(http://malsup.com/jquery/form/)是大家经常会用到的一个jQuery插件,它可以很方便将表单转换为ajax的方式进行提交.以下是官网给出的一个栗子: $(document).ready(function() { var options = { target: '#output1', // target element(s) to be updated with server response beforeSubmit

Android——修改Button样式,动态修改Button中的图片大小

原文地址: http://www.cnblogs.com/gzggyy/archive/2013/05/17/3083218.html http://www.xuebuyuan.com/2173740.html http://blog.csdn.net/u012246458/article/details/50387308 Android开发:shape和selector和layer-list的(详细说明)

动态修改母版页中的DIV标签中的LI的A的CLASS属性

这个知识点比较简单,只是关于转义方面,或什么时候用双引号,什么时候单引号老搞不清,特此备忘之用 <ul class="nav"> <!-- 实现点击当前页后,图片悬停在上面,如:点击首页,首页有图片效果,由于是母版页,每次点击都会刷新当前页面,因此用js无效 --> <!-- 此种方式必须有真实存在的地址才可调用 --> <li><a href="<%=Page.ResolveUrl("Index.aspx