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 <QObject>
#include <QFile>
#include <QTextStream>
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 <QDebug>
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<FileContent>("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
來進行異步請求。