在QML(Qt)中读写文件

10
我正在尝试实现QML中的文件读写,并阅读了来自Nokia的相关文章,但似乎无法成功使用明显的代码示例。我想我不用说我在这个领域是一个完全的新手。

我应该把这段代码片段(它是页面上的第二段代码片段)放在哪里呢?

#include "fileio.h"
Q_DECL_EXPORT int main(int argc, char *argv[])
{
    qmlRegisterType<FileIO, 1>("FileIO", 1, 0, "FileIO");
}

当我将上述代码片段放在我的主窗体中时,我还不断收到关于qmlRegisterType未在上下文中注册的错误。请问有人能提供一些关于如何实现这个(或任何读写QML / Qt文件的方法)的建议吗?

1
这绝对是在C++中要做的事情。保持QML作为它应该是的轻量级UI层。 - Frank Osterfeld
1
Frank,感谢您的回复。实际上,链接中的代码是C++文件访问的实现,我的问题是如何在C++中完成它。 - Nepaluz
你如何称呼你的“主窗体”? - alexisdm
alexisdm - 我的主要for循环没有ID,它位于main.qml中。 - Nepaluz
5个回答

26
如果您的文件仅为文本,您可以使用XMLHttpRequest(用于读取和写入),像这样:

如果您的文件仅为文本,您可以使用XMLHttpRequest(用于读取和写入),像这样:

function openFile(fileUrl) {
    var request = new XMLHttpRequest();
    request.open("GET", fileUrl, false);
    request.send(null);
    return request.responseText;
}

function saveFile(fileUrl, text) {
    var request = new XMLHttpRequest();
    request.open("PUT", fileUrl, false);
    request.send(text);
    return request.status;
}

这是演示应用程序(Qt 5.6):

import QtQuick 2.6
import QtQuick.Dialogs 1.2
import QtQuick.Controls 1.5

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Demo App")

    function openFile(fileUrl) {
        var request = new XMLHttpRequest();
        request.open("GET", fileUrl, false);
        request.send(null);
        return request.responseText;
    }

    function saveFile(fileUrl, text) {
        var request = new XMLHttpRequest();
        request.open("PUT", fileUrl, false);
        request.send(text);
        return request.status;
    }

    FileDialog {
        id: openFileDialog
        nameFilters: ["Text files (*.txt)", "All files (*)"]
        onAccepted: textEdit.text = openFile(openFileDialog.fileUrl)
    }

    FileDialog {
        id: saveFileDialog
        selectExisting: false
        nameFilters: ["Text files (*.txt)", "All files (*)"]
        onAccepted: saveFile(saveFileDialog.fileUrl, textEdit.text)
    }

    menuBar: MenuBar {
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("&Open")
                onTriggered: openFileDialog.open()
            }
            MenuItem {
                text: qsTr("&Save")
                onTriggered: saveFileDialog.open()
            }
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit();
            }
        }
    }

    TextArea {
        id: textEdit
        anchors.fill: parent
        text:
            "Lorem ipsum dolor sit amet, consectetur adipisicing elit, " +
            "sed do eiusmod tempor incididunt ut labore et dolore magna " +
            "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " +
            "ullamco laboris nisi ut aliquip ex ea commodo cosnsequat. ";
    }
}

注意,所有现代浏览器如果尝试使用上述函数将会抛出安全异常,但是QML允许使用(即使进行文件重写)。虽然不确定是否设计如此,还是错误。


这在最新版本的Qt中已经不太适用了! - undefined

9
教程中由Nokia编写的示例不是一个纯QML程序。它包含了C++和QML。这种程序通常是一个C++程序,它加载一个QML文件并将其呈现。C++程序通常以一个叫做 "main()" 的函数开始,格式为:int main(int argc, char *argv[]);在你的情况下,就是这个"main()"函数加载你的QML主文件(main.qml)并呈现它。
但在加载QML主文件之前,您必须告诉QML系统,您将使用一个名为FileIO的自定义QML类。为此,您需要使用int qmlRegisterType<T>(const char * package, int majorVersion, int minorVersion, char * classNameInQML) C++函数。它有大约5个参数:
  • T:C++模板参数。它是您的C++类(FileIO)。
  • package:所有QML类都在版本化的包中。这是包的名称。
  • majorVersion:所有QML类都在版本化的包中。这是包的主版本号。
  • minorVersion:所有QML类都在版本化的包中。这是包的次要版本号。
  • classNameInQML:所有QML类都在版本化的包中。这是您在QML文件中将使用的类的名称。大多数情况下,名称与C++类名相同。
要使用此函数,您必须在编写它的C++文件中包含一个C++头文件:
  • 如果您使用Qt 4,则头文件是<QtDeclarative>
  • 如果您使用Qt 5,则头文件是<QtQml>
最终,你应该有一些像这样的东西:

main.cpp(带有main() C++函数的文件):

// C++ header to include for using qmlRegisterType();
#include <QtDeclarative>    // If you use Qt4
#include <QtQml>            // If you use Qt5

// Some stuff used by the main(); function
#include <QApplication>
#include <QLatin1String>

#include "ui/qtquickapplicationviewer.hpp"    // Something which manages your QML files. Qt Creator will generate it for you if you use it to code..
#include "fileio.h"    // Your FileIO C++ class

/**
 * @fn Q_DECL_EXPORT int main(int argc, char *argv[])
 * @brief The C++ main(); function. Your program begins HERE.
 */
Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // ...

    // Declaring your C++ class to the QML system
    qmlRegisterType<FileIO>("MyCustomClasses", 1, 0, "FileIOQML");

    // ...

    // Loading your main QML file
    QLatin1String mainQMLFile = "./ui/qml/main.qml";
    QtQuickApplicationViewer viewer;
    viewer.setMainQmlFile(mainQMLFile);

    // Showing how beautiful your QML interface is :)
    viewer.showExpanded();

    // Now let's play with your QML interface is :)
    return app.exec();
}

要加载的main.qml文件(直接从诺基亚的教程中复制):

import QtQuick 1.1
import MyCustomClasses 1.0

Rectangle {
    width: 360
    height: 360
    Text {
        id: myText
        text: "Hello World"
        anchors.centerIn: parent
    }

    FileIOQML {
        id: myFile
        source: "my_file.txt"
        onError: console.log(msg)
    }

    Component.onCompleted: {
        console.log( "WRITE"+ myFile.write("TEST"));
        myText.text =  myFile.read();
    }
}

注意:我已经修改了一些来自诺基亚教程的“FileIO”,以避免混淆。


air-dex - 谢谢你。我想这个教程是为有经验的程序员准备的,它从来没有清楚地解释 qmlRegisterType<T> 的参数,这让我完全困惑了!但就像我怀疑的那样,事实证明它只是那么简单的一件事。再次感谢,我在 stackoverflow 上已经失去了希望! - Nepaluz

2

在这个页面上可以找到FileIO的完整示例:http://qmlbook.github.io/ch13-networking/networking.html#local-files

class FileIO : public QObject {
    ...
    Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
    ...
public:
    Q_INVOKABLE void read();
    Q_INVOKABLE void write();
    ...
}

我们将省略属性,因为它们只是简单的设置器和获取器。
read方法以读取模式打开文件,并使用文本流读取数据。
void FileIO::read()
{
    if(m_source.isEmpty()) {
        return;
    }
    QFile file(m_source.toLocalFile());
    if(!file.exists()) {
        qWarning() << "Does not exits: " << m_source.toLocalFile();
        return;
    }
    if(file.open(QIODevice::ReadOnly)) {
        QTextStream stream(&file);
        m_text = stream.readAll();
        emit textChanged(m_text);
    }
}

当文本发生变化时,需要使用emit textChanged(m_text)通知其他人进行更改。否则,属性绑定将无法工作。
写入方法与此相同,但以写入模式打开文件并使用流来写入内容。
void FileIO::write()
{
    if(m_source.isEmpty()) {
        return;
    }
    QFile file(m_source.toLocalFile());
    if(file.open(QIODevice::WriteOnly)) {
        QTextStream stream(&file);
        stream << m_text;
    }
}

源代码可以在这里找到: http://qmlbook.github.io/ch13-networking/networking.html#local-files


链接已经失效 :( 我认为这是新链接:https://qmlbook.github.io/ch17-extensions/extensions.html#fileio-implementation - Chris
1
感谢您报告这些损坏的链接。我已经进行了更新。 - Jake W
那个链接现在也失效了。移动目标!这是一个更新的链接。 http://qmlbook.github.io/ch13-networking/networking.html#local-files - Alan Ezust

1
使用 V-Play SDK,您可以在 QML 中使用 FileUtils 类来访问任何平台上的文件系统。具体实现如下:
var success = fileUtils.writeFile("TextFiles/myFile.txt", "test text")

1
这是一个关于Qt 6的最新衍生问题,源自于一个受欢迎但现在已过时的回答https://dev59.com/nnTYa4cB1Zd3GeqP0fDY#37460883
import QtQuick
import QtQuick.Window

// NOTE: You must add the following env vars to your run Run Environment!
// Set QML_XHR_ALLOW_FILE_READ to 1
// Set QML_XHR_ALLOW_FILE_WRITE to 1

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Local File I/O Demo")

    readonly property string _XMLHTTP_GET       : "GET"
    readonly property string _XMLHTTP_PUT       : "PUT"
    readonly property bool   _XMLHTTP_SYNCHRO   : false
    readonly property int    _XMLHTTP_DONE      : 4

    property var _xmlHttpWriteReq : null

    signal fileWritten( url fileUrl )
    onFileWritten: ( fileUrl )=>{ console.log( readTextFile( fileUrl ) ) }

    function readTextFile( fileUrl ) {
        var req = new XMLHttpRequest()
        req.open( _XMLHTTP_GET, fileUrl, _XMLHTTP_SYNCHRO )
        req.send( null )
        return req.responseText
    }

    function writeTextFile( fileUrl, fileContent ) {
        if( !_xmlHttpWriteReq ) _xmlHttpWriteReq = new XMLHttpRequest()
        _xmlHttpWriteReq.open( _XMLHTTP_PUT, fileUrl, !_XMLHTTP_SYNCHRO )
        _xmlHttpWriteReq.onreadystatechange = function() {
            if( _xmlHttpWriteReq.readyState === _XMLHTTP_DONE ) {
                // Note: _xmlHttpWriteReq.status does not reflect success!
                // but QIODevice errors are implicitly written to the console
                fileWritten( fileUrl )
            }
        }
        _xmlHttpWriteReq.send( fileContent  )
    }

    Component.onCompleted: {
        const fileUrl     = "file:///C:/Users/UserName/Desktop/test.txt"
        const fileContent = "Current D/T: " + Date.now()
        writeTextFile( fileUrl, fileContent )
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接