QML 对本地文件的读写
QML 里似乎没有提供直接访问本地文件的模块,但是我们能够自己扩展 QML,给它加上访问本地文件的能力。
Qt 官方文档对 QML 是这样介绍的:
It defines and implements the language and engine infrastructure, and provides an API to enable application developers to extend the QML language with custom types and integrate QML code with JavaScript and C++.
自定义模块
我们可以通过自定义 C++ 类,实现文件的读写并整合进 QML 中,使其作为一个文件读写的独立模块。
C++ 里这个类叫做 FileContent
头文件 FileContent.h:
#ifndef FILECONTENT_H
#define FILECONTENT_H
#include
#include
#include
class FileContent : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QString content READ getContent)
Q_PROPERTY(QString filename READ getFileName WRITE setFileName)
Q_INVOKABLE QString getContent();
Q_INVOKABLE QString getFileName();
FileContent(QObject *parent = 0);
~FileContent();
private:
QFile *file;
QString content;
QString filename;
public slots:
void setFileName(const QString& filename);
void clearContent();
};
#endif // FILECONTENT_H
FileContent 的实现:
#include "filecontent.h"
#include
FileContent::FileContent(QObject *parent) {
}
FileContent::~FileContent() {
delete file;
}
QString FileContent::getFileName() {
return this->filename;
}
void FileContent::setFileName(const QString &filename) {
this->filename = filename;
file = new QFile(filename);
}
QString FileContent::getContent() {
if( content.length() == 0 ) {
file->open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream in(file);
content = in.readAll();
if( content.length() == 0) {
qDebug() << "[Warning] FileContent: file " << this->filename << "is empty" << endl;
}
}
return content;
}
void FileContent::clearContent() {
content.clear();
}
FileContent 需要继承 QObject 类,并且在类内使用 Qt 的一系列宏。
这里用到了 Q_PROPERTY 宏,声明该类的一个属性,并给出 set 和 get 对应的方法名。还有 Q_INVOKABLE 宏,以便在 QML 中可以调用 FileContent 类的方法。
这里的 FileContent 类有两个属性,一个是文件名 filename
,另一个是文件的内容 content
。这两个属性可以直接在 QML 中作为 Item 的属性进行赋值。
我们把 FileContent 在 QML 中的名字叫做 FileContentItem,但现在还不能直接在 QML 文件中引用 FileContentItem,我们还需要通过 QmlEngine 提供的 qmlRegisterType 方法,向 Qml 系统注册我们写的这个类。
在 main 函数里面添加:
qmlRegisterType("test.filecontent", 1, 0, "FileContentItem");
然后在 QML 文件里面引用我们定义的 FileContent,然后就可以像使用普通的 Item 一样使用 FileContentItem 了。
import test.filecontent 1.0
FileContentItem {
id: content
filename: ":/obj/craft.obj" // default is craft.obj
property bool ready: false
Component.onCompleted: {
ready = true;
GLcode.readFile = processContent;
}
function processContent(process, source) {
while( !ready ) {
;
}
if( source !== undefined ) {
filename = source;
}
console.time('Read file: "' + source + '"');
process(getContent());
console.timeEnd('Read file: "' + source + '"');
clearContent(); // save memory
}
}
这里 FileContentItem 里的 filename 和 content 属性其实分别对应的 C++ 里面用 Q_PROPERTY 定义的属性。这里并没有考虑要读取的文件内容大小,而是直接用 getContent() 方法直接返回文件的所有内容,如果文件过大,也可以考虑流式读取文件内容。
JavaScript 异步读取文件
如果需要在 QML 里面读取资源文件而不需要将数据写入到文件中,那么其实可以使用 JavaScript 的 XMLHttpRequest 方法来读取文件。当然这个方法与浏览器里面的使用有一点点区别。
这是我从 Qt 自带的 Planets Example 中扎到的实现:
/**
* this function is copied from planets demo of qt version of threejs
* I modified some of it, now it works fine for me
**/
function readFile(url, onLoad, onProgress, onError) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === XMLHttpRequest.DONE) {
// TODO: Re-visit https://bugreports.qt.io/browse/QTBUG-45581 is solved in Qt
if (request.status == 200 || request.status == 0) {
// var response;
// response = request.responseText;
console.time('Process file: "' + url + '"');
onLoad( request.responseText );
console.timeEnd('Process file: "' + url + '"');
}
else if ( onError !== undefined ) {
onError();
}
}
else if (request.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
if ( onProgress !== undefined ) {
onProgress();
}
}
};
request.open( 'GET', url, true );
request.send( null );
}
因为我暂时只需要回调 onLoad 方法,所以我只关注这一部分的逻辑,该方法和浏览器中 AJAX 的异步请求并没有太大区别,不过需要注意的是这里有个 bug: request 放回的状态码有可能是 0,而这有可能意味着请求成功。所以在检测请求是否成功返回时应该要加上 request.status == 0
的判断。
总结
此外,如果想要在 QML 里面读写本地的配置文件,还可以使用 QML 已经提供的 Settings 模块,它对应的是 C++ 部分的 QSettings 类,提供平台无关的程序配置。
在 QML 中实现文件的读写有多种方法,具体的做法需要结合具体的需求,由于我做的程序可能需要迁移到 Web 上,因此最终使用 JavaScript 提供的 XMLHttpRequest
来进行异步请求。