qt qml listview加载日志目录菜单及显示目录下日志文件
*在qml中直接访问本地电脑的文件比较麻烦,使用qml listview配合c++,通过后台QList生成listview所需ListModel折腾很久,还要实现目录加载和文件清单加载,内存管理也是个麻烦事。后面考虑在后台生成json内容,提供给前台qml使用,比较轻巧的方式,实现了目录菜单展开和关闭,目录下日志文件的显示。然后在qml中加载日志文件显示也比较麻烦,遇到一些问题,比如要通过XMLHttpRequest方式加载文件,日志文件内容如何自动换行,汉字乱码等,最终还算得到了解决。希望能对需要的人有所帮助。
下面展示一些代码`。
C++头文件
#ifndef FOLDERLISTDATAPROVIDER_H
#define FOLDERLISTDATAPROVIDER_H
#include <QObject>
#include <QMap>
#include <QVariant>
class FolderlistDataProvider : public QObject
{
Q_OBJECT
public:
explicit FolderlistDataProvider(QObject *parent = nullptr);
//通过Q_INVOKABLE方式提供前端qml调用
Q_INVOKABLE QVariantMap getCategoryData(QString path);
signals:
};
#endif // FOLDERLISTDATAPROVIDER_H
C++代码文件
#include "folderlistdataprovider.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QDir>
FolderlistDataProvider::FolderlistDataProvider(QObject *parent) : QObject(parent)
{
}
QVariantMap FolderlistDataProvider::getCategoryData(QString path){
QDir directory(path);
// 检查目录是否存在
if (!directory.exists()) {
qDebug() << "日志目录不存在: " << path;
return QVariantMap();
}
// 设置过滤器仅包含目录
directory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
// 设置按名称排序
directory.setSorting(QDir::Name | QDir::Reversed);
// 获取目录列表
QFileInfoList dirList = directory.entryInfoList();
// 创建一个 QVariantList 并将类别添加到其中
QVariantList categoriesList;
// 遍历目录列表并输出目录路径
foreach (const QFileInfo& dirInfo, dirList) {
QJsonObject categoryObject;
categoryObject["category"] = dirInfo.fileName();
categoryObject["expand"] = false;
QDir subDirectory(dirInfo.absoluteFilePath());
if (!subDirectory.exists()){
qDebug() << "子目录不存在: " << dirInfo.absoluteFilePath();
return QVariantMap();
}
// 设置过滤器仅包含目录
subDirectory.setFilter(QDir::Files);
// 设置按名称排序
subDirectory.setSorting(QDir::Name | QDir::Reversed);
// 获取文件列表
QFileInfoList subFileList = subDirectory.entryInfoList();
QJsonArray itemsArray;
foreach (const QFileInfo& fileInfo, subFileList) {
QJsonObject item;
item["name"] = fileInfo.fileName();
item["path"] = "file:///" + fileInfo.absoluteFilePath();
itemsArray.append(item);
}
categoryObject["itemsModel"] = itemsArray;
categoriesList.append(categoryObject);
}
// 创建一个 QVariantMap,将 QVariantList 添加到其中
QVariantMap data;
data["categoryList"] = QVariant(categoriesList);
return data;
}
然后在main.cpp中注册导出FolderlistDataProvider类及定义日志文件所在路径
#include <QApplication>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "folderlistdataprovider.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
//QGuiApplication app(argc, argv);
QApplication app(argc, argv);
//注册C++类
qmlRegisterType<FolderlistDataProvider>("mylib.folderlistdataprovider", 1, 0, "FolderlistDataProvider");
QQmlApplicationEngine engine;
// 获取应用程序目录路径
QString appDirPath = QCoreApplication::applicationDirPath();
//注册路径
engine.rootContext()->setContextProperty("appDirPath", appDirPath);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
qml生成前端组件和调用C++类函数
import QtQuick 2.12
import mylib.folderlistdataprovider 1.0
Item {
//引入C++后台对象
FolderlistDataProvider {
id: provider
}
property variant categoryData: ({})
property ListModel mymodel:ListModel {
id: tempModel
}
Component {
id: categoryDelegate
Column {
width: parent.width
Rectangle {
color:"#1c1c24"
width: parent.width
height: 80
Row {
spacing: 10
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
Item {
width: 48
height: 48
MouseArea {
anchors.fill: parent
onClicked: expand = !expand
Image {
anchors.fill: parent
source: expand ? "qrc:/imgs/folder-open.png" : "qrc:/imgs/folder.png"
smooth: true;
fillMode: Image.PreserveAspectFit;
}
}
}
Text {
text: category
font.pixelSize: 24
anchors.verticalCenter: parent.verticalCenter
color:"white"
}
}
}
//目录及日志文件显示
ListView {
width: parent.width
//菜单不展开时,高度设置为-1
height: expand ? contentHeight : -1
model: itemsModel
clip: false
delegate: Item {
width: parent.width
height: 50
Row {
spacing: 10
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
Rectangle {
width: 24
height: 1
color: "transparent"
}
Item {
width: 32
height: 32
MouseArea {
anchors.fill: parent
onClicked:{
doDisplayLogs(txtDisplay,path);
}
Image {
anchors.fill: parent
source: "qrc:/imgs/file.png"
smooth: true;
fillMode: Image.PreserveAspectFit;
}
}
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: name
color: "white"
font.pixelSize: 20
}
}
}
}
}
}
ListView {
width: parent.width
height: parent.height
model: mymodel
clip:true
delegate: categoryDelegate
}
Component.onCompleted: {
//appDirPath为定义的日志文件所在路径
categoryData = provider.getCategoryData(appDirPath + "/logs");
for (var item of categoryData.categoryList){
tempModel.append({category: item.category,expand:item.expand,itemsModel: item.itemsModel})
}
}
}
main.qml中调用
function doDisplayLogs(dis,path){
dis.text = "";
path = path.replace(/\\/g, "/");
var file = new XMLHttpRequest();
file.open("GET", path, true); // 将第三个参数设置为 true
file.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
file.onreadystatechange = function() {
if (file.readyState === 4 && file.status === 200) {
var texts = decodeURIComponent(file.responseText);
var lines = texts.split('\n');
for (var i = 0; i < lines.length; i++) {
dis.append(lines[i]);
}
} else {
//console.error('Error:', file.status);
}
};
file.send(null);
}
Rectangle{
id : rectLogTitle
anchors.top: parent.top
anchors.topMargin: 20
anchors.left: parent.left
anchors.leftMargin: 20
width:400
height: parent.height - 30
color:"#1c1c24"
border.color: "#1f75fc"
border.width: 1
MyFolderListView {
anchors.fill: parent
anchors.margins: 10
}
}
Rectangle{
id: rectLogMain
anchors.top: parent.top
anchors.topMargin: 20
anchors.left: rectLogTitle.right
anchors.leftMargin: 20
width:parent.width * 0.7
height: parent.height - 30
color:"#1c1c24"
border.color: "#1f75fc"
border.width: 1
ScrollView {
id: scrollLogView
anchors.leftMargin: 10
clip: true
anchors.fill: parent
TextArea {
id: txtDisplay
width: parent.width
height: parent.height
color: "#FAFAFB"
readOnly:true
textFormat:TextEdit.RichText
text:""
wrapMode: TextArea.Wrap
function append(strContent)
{
txtDisplay.text = txtDisplay.text + '\n' + strContent
}
}
}
}
最终实现效果为:
需要自己去找几个文件夹和文件的图片资源