在学习qml过程中,可能大家都希望能有实时预览效果,在网上找了找,找到个解决方案,原理就是使用 QFileSystemWatcher
实时监控本地文件,一旦文件有变化,利用Qml 中的 Loader 加载qml文件
注意main.cpp中必须要设置本地绝对路径
engine.hotLoad("E:/QTCloud/QmlLearn/LivePreview/qml/main.qml");
左侧文件列表使用的是QFileSystemModel
这个qt官方有案例,本地文件增删时,列表也随之增删
其中qml 源码案例来自 qmlbook http://qmlbook.github.io/index.html
效果:
github https://github.com/tianxiaofan/LivePreview
以下是关键原码
main.cpp
#include "qmlliveengine.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QmlLiveEngine engine;
engine.hotLoad("E:/QTCloud/QmlLearn/LivePreview/qml/main.qml");
return app.exec();
}
qmlliveengine.h
#pragma once
#include <QFileSystemModel>
#include <QQmlApplicationEngine>
#include <QStringListModel>
class QFileSystemWatcher;
class QTimer;
class DisplayFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
explicit DisplayFileSystemModel(QObject* parent = nullptr) : QFileSystemModel(parent) { }
enum Roles {
SizeRole = Qt::UserRole + 4,
DisplayableFilePermissionsRole = Qt::UserRole + 5,
LastModifiedRole = Qt::UserRole + 6,
UrlStringRole = Qt::UserRole + 7
};
Q_ENUM(Roles)
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (index.isValid() && role >= SizeRole) {
switch (role) {
case UrlStringRole:
return QVariant(QUrl::fromLocalFile(filePath(index)).toString());
default:
break;
}
}
return QFileSystemModel::data(index, role);
}
QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> result = QFileSystemModel::roleNames();
return result;
}
};
class QmlLiveEngine : public QQmlApplicationEngine
{
Q_OBJECT
public:
explicit QmlLiveEngine(QObject* parent = nullptr);
void hotLoad(const QString& mainQml, bool integratedConsole = true);
Q_INVOKABLE void clearCache() { clearComponentCache(); }
Q_INVOKABLE QString getMainQml() { return m_mainQml; };
private slots:
void onFileSystemChanged();
void onReloadRequested();
private:
void watchFileSystemRecursively(const QString& dir);
void unwatchAll();
QFileSystemWatcher* m_watcher;
QTimer* m_timer;
QObject* m_window;
QString m_mainQml;
QString m_dir;
DisplayFileSystemModel m_qmlFiles;
};
qmlliveengine.cpp
#include "qmlliveengine.h"
#include <QFileInfo>
#include <QTimer>
#include <QFileSystemWatcher>
#include <QQmlContext>
#include <QDir>
QmlLiveEngine::QmlLiveEngine(QObject* parent) :
QQmlApplicationEngine(parent), m_watcher(nullptr), m_timer(nullptr), m_window(nullptr)
{
qmlRegisterUncreatableType<DisplayFileSystemModel>(
"io.qt.examples.quick.controls.filesystembrowser", 1, 0, "FileSystemModel",
"Cannot create a FileSystemModel instance.");
}
void QmlLiveEngine::hotLoad(const QString &mainQml, bool integratedConsole)
{
m_mainQml = mainQml;
m_dir = m_mainQml.left(m_mainQml.lastIndexOf('/'));
m_watcher = new QFileSystemWatcher(this);
m_timer = new QTimer(this);
m_qmlFiles.setRootPath(m_dir);
m_qmlFiles.setNameFilterDisables(false);
m_qmlFiles.setNameFilters({ "*.qml" });
watchFileSystemRecursively(m_dir);
m_timer->setInterval(500);
m_timer->setSingleShot(true);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this
, &QmlLiveEngine::onFileSystemChanged);
connect(m_watcher, &QFileSystemWatcher::fileChanged, this
, &QmlLiveEngine::onFileSystemChanged);
connect(m_timer, &QTimer::timeout, this, &QmlLiveEngine::onReloadRequested);
rootContext()->setContextProperty("QmlLiveEngine", this);
rootContext()->setContextProperty("QmlDir", m_dir);
rootContext()->setContextProperty("fileSystemModel", &m_qmlFiles);
rootContext()->setContextProperty("rootPathIndex", m_qmlFiles.index(m_qmlFiles.rootPath()));
load(mainQml);
m_window = rootObjects().first();
}
void QmlLiveEngine::onFileSystemChanged()
{
if (!m_timer) return;
if (!m_timer->isActive()) m_timer->start();
}
void QmlLiveEngine::onReloadRequested()
{
unwatchAll();
watchFileSystemRecursively(m_dir);
if (m_window) {
QMetaObject::invokeMethod(m_window, "_reload");
}
}
void QmlLiveEngine::watchFileSystemRecursively(const QString &dir)
{
QDir d(dir);
QStringList files = d.entryList(QStringList() << "*.qml", QDir::Files);
QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
m_watcher->addPath(dir);
for (QString& file : files) {
m_watcher->addPath(dir + '/' + file);
}
for (QString& subdir : dirs) {
watchFileSystemRecursively(dir + '/' + subdir);
}
}
void QmlLiveEngine::unwatchAll()
{
QStringList dirs = m_watcher->directories();
QStringList files = m_watcher->files();
QStringList fails;
for (QString &dir: dirs) {
if (!m_watcher->removePath(dir)) {
fails << dir;
}
}
for (QString &file: files) {
if (!m_watcher->removePath(file)) {
fails << file;
}
}
if (!fails.empty()) {
qWarning() << tr("The following directories or files "
"cannot be removed from file system watcher:");
for (QString &fail: fails) {
qWarning() << "\t" << fail;
}
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import Qt.labs.folderlistmodel 2.15
import QtQuick.Controls 1.5
import QtQml.Models 2.12
import io.qt.examples.quick.controls.filesystembrowser 1.0
ApplicationWindow {
id:root
width: 800
height: 600
visible: true
Rectangle{
id:fileList
width: 200
height: root.height
color: "lightgrey"
Button{
id: clearBtn
text: "clear"
width: 100
height: 40
anchors.bottom: parent.bottom
anchors.right: parent.right
onClicked: {
clearQml()
}
}
ItemSelectionModel {
id: sel
model: fileSystemModel
}
TreeView{
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: clearBtn.top
model: fileSystemModel
rootIndex: rootPathIndex
selection: sel
TableViewColumn {
role: "fileName"
resizable: true
}
onActivated: {
var url = fileSystemModel.data(index, FileSystemModel.UrlStringRole)
loadQml(url)
}
}
}
property string currentQml
Rectangle{
id:title
anchors.left: fileList.right
anchors.leftMargin: 2
anchors.right: parent.right
anchors.top: parent.top
height: 20
Text {
text: currentQml
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
color: "mediumaquamarine"
}
Rectangle{
id: showView
anchors.left: fileList.right
anchors.leftMargin: 2
anchors.right: parent.right
anchors.top: title.bottom
anchors.bottom: parent.bottom
Canvas{
anchors.fill: parent
visible: true
property int img_w: 16
property int img_h: 16
property string imageData: "qrc:/image/lb_transparent.png"
onPaint: {
var ctx = getContext("2d");
for(let i = 0; i <= Math.floor(width/img_w);i++)
{
for (let j = 0; j <= Math.floor(height/img_h); j++)
{
ctx.drawImage(imageData,i*img_w,j*img_h);
}
}
}
Component.onCompleted: {
loadImage(imageData)
}
}
Loader{
anchors.centerIn: parent
id: pageLoader
asynchronous: true
focus: true //这里需要开启,不然对于加载的qml,如果需要焦点事件,则无法接收
visible: true
}
}
function loadQml(name)
{
if(name.length <= 0)
return
pageLoader.source = "";
//需要先清空,不然会
QmlLiveEngine.clearCache();
pageLoader.setSource(name)
pageLoader.visible = true;
if(currentQml !== name)
currentQml = name;
console.log(pageLoader.source)
}
function clearQml()
{
pageLoader.source = "";
pageLoader.visible = false;
}
function _reload(){
if(currentQml.length <= 0)
return;
loadQml(currentQml)
}
}