# 使用文件读写

现在可以使用新创建的文件来访问一些数据。 在这个例子中,将一些JSON格式的城市数据显示在表格中。 这里将其构建为两个项目:一个用于扩展插件(称为fileio),它提供了一种从文件读取和写入文本的方法;另一个用于在表格中显示数据(称为CityUI)。 CityUI使用扩展fileio来读写文件。

image

JSON数据只是格式化的文本的方式,它可以转换为有效的JS对象/数组并返回文本。 这里使用FileIO读取JSON格式的数据,并使用内置的Javascript函数JSON.parse()将其转换为JS对象。 该数据稍后用作表格视图的模型。 读取和写入文档的函数实现如下所示:

FileIO {
    id: io
}

function readDocument() {
    io.source = openDialog.fileUrl
    io.read()
    view.model = JSON.parse(io.text)
}

function saveDocument() {
    var data = view.model
    io.text = JSON.stringify(data, null, 4)
    io.write()
}

此示例中使用的JSON数据位于cities.json文件中。 它包含一个城市数据条目列表,其中每个条目都包含有关城市的有趣数据,如下所示:

[
    {
        "area": "1928",
        "city": "Shanghai",
        "country": "China",
        "flag": "22px-Flag_of_the_People's_Republic_of_China.svg.png",
        "population": "13831900"
    },
    ...
]

# 应用程序窗口

这里使用Qt Creator的QtQuick Application向导来创建基于Qt Quick Controls 2的应用程序。 尽管带有ui.qml文件的新表单方法比以前更有用,因为这在书中很难解释,这里不会使用新的QML表单。 因此,现在可以移除/删除表单文件。

基本设置是一个可以包含工具栏、菜单栏和状态栏的ApplicationWindow。 这里只会使用菜单栏来创建一些标准的菜单条目,用于打开和保存文档。 基本设置将只显示一个空窗口。

import QtQuick 2.5
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
    id: root
    title: qsTr("City UI")
    width: 640
    height: 480
    visible: true
}

# 使用动作

为了更好地使用/重用命令,这里使用QML的Action(动作)类型。 这将允许稍后对潜在的工具栏使用相同的动作。 打开和保存以及退出动作是相当标准的动作。 打开和保存操作还没有包含任何逻辑,稍后会介绍。 菜单栏是使用文件菜单和这三个动作条目创建的。 另外,这里已经准备了一个文件对话框,这将允许稍后选择城市文件。 对话框在声明时是不可见的,需要使用open()方法来显示它。

Action {
    id: save
    text: qsTr("&Save")
    shortcut: StandardKey.Save
    onTriggered: {
        saveDocument()
    }
}

Action {
    id: open
    text: qsTr("&Open")
    shortcut: StandardKey.Open
    onTriggered: openDialog.open()
}

Action {
    id: exit
    text: qsTr("E&xit")
    onTriggered: Qt.quit();
}

menuBar: MenuBar {
    Menu {
        title: qsTr("&File")
        MenuItem { action: open }
        MenuItem { action: save }
        MenuSeparator {}
        MenuItem { action: exit }
    }
}

FileDialog {
    id: openDialog
    onAccepted: {
        root.readDocument()
    }
}

# 格式化表格

城市数据的内容以表格形式展示。 为此,这里使用了TableView控件并声明了4个列(columns):城市(city)、国家(country)、地区(area)、人口(population)。 每列都是一个标准的TableViewColumn。 稍后将为列添加标志和删除操作,这需要自定义列的代理。

TableView {
    id: view
    anchors.fill: parent
    TableViewColumn {
        role: 'city'
        title: "City"
        width: 120
    }
    TableViewColumn {
        role: 'country'
        title: "Country"
        width: 120
    }
    TableViewColumn {
        role: 'area'
        title: "Area"
        width: 80
    }
    TableViewColumn {
        role: 'population'
        title: "Population"
        width: 80
    }
}

现在应用程序应该显示一个带有文件菜单的菜单栏和一个带有4个表头的空表。 下一步将是使用扩展FileIO在表格中填充有用的数据。

image

cities.json文档是一个城市实体的数组。如下面例子所示:

[
    {
        "area": "1928",
        "city": "Shanghai",
        "country": "China",
        "flag": "22px-Flag_of_the_People's_Republic_of_China.svg.png",
        "population": "13831900"
    },
    ...
]

这里要做的工作是允许用户选择文件、读取文件、转换文件并将其设置到表格视图中。

# 读数据

为此,这里让打开动作打开文件对话框。 当用户选择一个文件时,在文件对话框中调用onAccepted方法,在那里调用了readDocument()函数。 readDocument()函数将文件对话框中的URL设置到FileIO对象并调用read()方法。 然后,使用JSON.parse()方法解析从FileIO加载的文本,并将生成的对象作为模型直接设置到表格视图上。 这很方便吧?

Action {
    id: open
    ...
    onTriggered: {
        openDialog.open()
    }
}

...

FileDialog {
    id: openDialog
    onAccepted: {
        root.readDocument()
    }
}

function readDocument() {
    io.source = openDialog.fileUrl
    io.read()
    view.model = JSON.parse(io.text)
}


FileIO {
    id: io
}

# 写数据

为了保存文档,这里将save(保存)动作连接到saveDocument()函数。 保存文档函数从视图中获取模型,这个模型是一个JS对象,并使用JSON.stringify()函数将其转换为字符串。 生成的字符串设置为FileIO对象的text属性,调用write()将数据保存到磁盘。 stringify函数上的null4参数将使用4个空格的缩进格式化生成的JSON数据。 这种格式化只是为了更好地阅读保存的文档。

Action {
    id: save
    ...
    onTriggered: {
        saveDocument()
    }
}

function saveDocument() {
    var data = view.model
    io.text = JSON.stringify(data, null, 4)
    io.write()
}

FileIO {
    id: io
}

这基本上是具有读取、写入和显示JSON文档的应用程序了。 想想编写XML读取器和写入器所花费的所有时间;使用 JSON,只需要一种读取和写入文本文件或发送接收文本缓冲区的方法就可以了。

image

# 收尾工作

到目前为止,该应用程序尚未完全实现。 这里仍然希望显示标志并允许用户通过从模型中删除城市来修改文档。

在本例中,标志文件存储在与main.qml文档同目录法人flags文件夹中。 为了能够显示它们,表格列需要定义一个自定义的代理来呈现标志图像。

TableViewColumn {
    delegate: Item {
        Image {
            anchors.centerIn: parent
            source: 'flags/' + styleData.value
        }
    }
    role: 'flag'
    title: "Flag"
    width: 40
}

这就是展示国旗所需做的一切。 它将JS模型中的flag属性作为styleData.value暴露给代理。 然后,代理调整图像路径以向前添加'flags/'并将其显示为Image元素。

对于移除操作,这里使用了类似的技术来显示移除按钮。

TableViewColumn {
    delegate: Button {
        iconSource: "remove.png"
        onClicked: {
            var data = view.model
            data.splice(styleData.row, 1)
            view.model = data
        }
    }
    width: 40
}

对于数据删除操作,这里保留视图模型,然后使用JS的splice函数删除一个条目。 这里可以使用此方法,是因为模型来自JS类型的数组。 splice方法通过删除现有元素和/或添加新元素来更改数组的内容。

不幸的是,JS数组不如QAbstractItemModel之类的Qt模型聪明,后者会通知视图有关行更改或数据更改。 因为它永远不会收到任何更改的通知,该视图现在不会显示任何更新的数据。 只有将数据设置回视图时,视图才会识别出有新数据并刷新视图内容。 使用view.model = data再次设置模型是一种让视图知道数据更改的方法。

image

最后更新: 2/10/2022, 8:12:32 PM