视频效果如下:
qml实现指定文件目录的显示功能
图片效果如下:
实现方法也是研究了许久,从一开始继承QFileSystemModel来实现文件目录的显示,但是使用QFileSystemModel只有和QTreeView才能实现指定目录显示的效果,因为QFileSystemModel的SetRootPath()方法只能起到监控指定目录的方法,设置了SetRootPath()方法后,其他盘符依然会显示出来,而QTreeView中的setRootIndex()方法配合SetRootPath()方法能实现只显示指定的目录的效果,但是这是在Qt Widgets项目中才能这么实现,在Qt Quick项目中想要使用QFileSystemModel模型来实现指定目录的显示,我也是做了许久依旧无法实现出来,如果有实现出来的小伙伴欢迎私信交流,后面又试过使用qml端官方写的FolderListModel模型来实现指定目录的显示,虽然这个FolderListModel模型,确实可以将指定目录下的所有子目录和文件都显示出来,但是它也紧紧只能显示一层,意思就是,它所显示出来的子目录无法打开,如果想要通过这个模型实现文件系统目录,可能要做的工作需要更多,于是后面研究后,决定继承QStandardItemModel类和使用qml的TreeView来实现指定目录的显示效果。效果如上,笔者还给文件目录加上了每行可以选中的效果,和目录前面+号变成-号的效果。目前还未实现可以右键鼠标进行相关目录的操作行为。实现代码如下:
displayfilesystemmodel.h头文件内容如下:
#ifndef DISPLAYFILESYSTEMMODEL_H
#define DISPLAYFILESYSTEMMODEL_H
#include <QStandardItemModel>
#include <QStandardItem>
#include <QDir>
#include <QFileInfoList>
class DisplayFileSystemModel : public QStandardItemModel
{
Q_OBJECT
public:
enum Roles {
FileNameRole = Qt::UserRole + 100,
FilePathRole ,
IsDirRole,
DepthRole,
CheckedRole,
SelectedRole,
EmbellishRole
};
explicit DisplayFileSystemModel(QObject *parent = nullptr);
~DisplayFileSystemModel();
Q_INVOKABLE void setRootPath(const QString &path);
int getDepth(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Q_INVOKABLE void updateChecked(const QString &path, bool checked);
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void updateSelected(const QString &path, bool selected);
signals:
void dirSet();
private:
void loadDirectory(const QDir &dir);
void loadDirectory(const QDir &dir, QStandardItem *parentItem);
QStandardItem *root;
QHash<QString, QStandardItem*> itemMap;
QHash<QString, bool> checkedItems;
QString selectedItem;
};
#endif // DISPLAYFILESYSTEMMODEL_H
displayfilesystemmodel.cpp文件内容如下:
#include "displayFileSystemModel.h"
DisplayFileSystemModel::DisplayFileSystemModel(QObject *parent) : QStandardItemModel(parent)
{
}
DisplayFileSystemModel::~DisplayFileSystemModel()
{
}
void DisplayFileSystemModel::setRootPath(const QString &path)
{
QDir dir(path);
if (!dir.exists()) {
return;
}
clear();
itemMap.clear(); // 清空itemMap
loadDirectory(dir);
}
int DisplayFileSystemModel::getDepth(const QModelIndex &index) const
{
int depth = 0;
QModelIndex parentIndex = index.parent();
while (parentIndex.isValid()) {
depth++;
parentIndex = parentIndex.parent();
}
return depth;
}
QVariant DisplayFileSystemModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
QStandardItem *item = itemFromIndex(index);
switch (role) {
case FileNameRole:
return item->data(FileNameRole);
case EmbellishRole:
// 返回设置的图标
return item->data(EmbellishRole);
case DepthRole:
return getDepth(index);
case CheckedRole: {
QString path = item->data(FilePathRole).toString(); // 获取路径数据
return checkedItems.value(path, false);
}
case SelectedRole: {
// 返回这一行是否被选中
QString path = item->data(FilePathRole).toString(); // 获取路径数据
return path == selectedItem;
}
default:
return QStandardItemModel::data(index, role);
}
}
void DisplayFileSystemModel::loadDirectory(const QDir &dir)
{
QStandardItem *parentItem = invisibleRootItem();
loadDirectory(dir, parentItem);
}
void DisplayFileSystemModel::loadDirectory(const QDir &dir, QStandardItem *parentItem)
{
QFileInfoList list = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &info : list) {
// 如果文件的扩展名是.gpkg或.qgz,就跳过这个文件
if (info.isFile() && (info.suffix() == "gpkg" || info.suffix() == "qgz")) {
continue;
}
QStandardItem *item = new QStandardItem();
item->setData(info.fileName(), FileNameRole);
item->setData(info.absoluteFilePath(), FilePathRole); // 设置路径数据
// 根据是否是目录设置不同的图标
if (info.isDir()) {
item->setData("qrc:/2.png", EmbellishRole);
} else {
item->setData("qrc:/1.png", EmbellishRole);
}
parentItem->appendRow(item);
itemMap[info.absoluteFilePath()] = item; // 更新itemMap
if (info.isDir()) {
loadDirectory(QDir(info.absoluteFilePath()), item);
}
}
}
void DisplayFileSystemModel::updateChecked(const QString &path, bool checked)
{
if (!itemMap.contains(path)) { // 处理不存在的项
return;
}
if (checkedItems[path] != checked) {
checkedItems[path] = checked;
QStandardItem *item = itemMap.value(path);
QModelIndex parentIndex = item->parent() ? item->parent()->index() : QModelIndex();
QModelIndex firstIndex = this->index(item->row(), 0, parentIndex);
QModelIndex lastIndex = this->index(item->row(), this->columnCount(parentIndex) - 1, parentIndex);
emit dataChanged(firstIndex, lastIndex, { CheckedRole });
}
}
QHash<int, QByteArray> DisplayFileSystemModel::roleNames() const
{
QHash<int, QByteArray> roles = QStandardItemModel::roleNames();
roles[FileNameRole] = "filename";
roles[FilePathRole] = "filepath";
roles[IsDirRole] = "isdir";
roles[DepthRole] = "depth";
roles[CheckedRole] = "checked";
roles[SelectedRole] = "selected";
roles[EmbellishRole] = "embellish";
return roles;
}
void DisplayFileSystemModel::updateSelected(const QString &path, bool selected)
{
if (!itemMap.contains(path)) { // 处理不存在的项
return;
}
if (selected) {
if (!selectedItem.isEmpty()) {
// 取消之前选中的行的选中状态
QString oldSelectedItem = selectedItem;
selectedItem.clear();
QStandardItem *oldItem = itemMap.value(oldSelectedItem);
QModelIndex parentIndex = oldItem->parent() ? oldItem->parent()->index() : QModelIndex();
QModelIndex firstIndex = this->index(oldItem->row(), 0, parentIndex);
QModelIndex lastIndex = this->index(oldItem->row(), this->columnCount(parentIndex) - 1, parentIndex);
emit dataChanged(firstIndex, lastIndex, { SelectedRole });
}
// 选中新的行
selectedItem = path;
} else {
selectedItem.clear();
}
QStandardItem *item = itemMap.value(path);
QModelIndex parentIndex = item->parent() ? item->parent()->index() : QModelIndex();
QModelIndex firstIndex = this->index(item->row(), 0, parentIndex);
QModelIndex lastIndex = this->index(item->row(), this->columnCount(parentIndex) - 1, parentIndex);
emit dataChanged(firstIndex, lastIndex, { SelectedRole });
}
main.cpp文件内容如下:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <displayfilesystemmodel.h>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
DisplayFileSystemModel * fsm = new DisplayFileSystemModel(&engine);
fsm->setRootPath("D:/QT");
engine.rootContext()->setContextProperty("fileSystemModel", fsm);
qmlRegisterType<DisplayFileSystemModel>("org.qgis", 1, 0, "DisplayFileSystemModel");
const QUrl url(u"qrc:/qml_test4/main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
MyRectangle.qml文件内容如下:
import QtQuick
import QtQml.Models 2.2
import Qt.labs.folderlistmodel 2.2
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import Qt.labs.qmlmodels
Item {
id: fileExplorer
visible: false
width: parent.width
height: parent.height
ScrollView {
id: id_scro
anchors.fill: parent
width: parent.width
height: parent.height
clip: true
TreeView {
id: fileTreeView
anchors.fill: parent
anchors.bottomMargin: 10
// The model needs to be a QAbstractItemModel
model: fileSystemModel
selectionModel: ItemSelectionModel {id: fileItemSelectionModel}
delegate: Item {
id: treeDelegate
implicitWidth: parent.width
implicitHeight: label.implicitHeight * 1.5
readonly property real indent: 20
readonly property real padding: 5
// Assigned to by TreeView:
required property TreeView treeView
required property bool expanded
required property bool isTreeNode
required property int hasChildren
property bool isPressed: false
TapHandler {
onTapped: {
treeView.toggleExpanded(row);
treeView.model.updateChecked(model.filepath, !checked);
treeView.model.updateSelected(model.filepath, !model.selected);
}
}
Rectangle {
id: dirRectangle
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
color: model.selected ? "#DDE8FB" : "white"
clip: true
Row {
width: parent.width
height: parent.height
x: padding + (model.depth * indent)
spacing: 10 // 设置图标和文字之间的间距为10个单位
Rectangle {
id: expandCollapseIcon
width: 13
height: 13
color: "#447DD1"
anchors.verticalCenter: parent.verticalCenter
visible: treeDelegate.hasChildren
Text {
id: expandCollapseText
text: treeDelegate.expanded ? "-" : "+"
anchors.centerIn: parent
color: "white"
}
MouseArea {
anchors.fill: parent
onClicked: {
treeDelegate.expanded = !treeDelegate.expanded;
treeView.toggleExpanded(index);
treeView.model.updateSelected(model.filepath, !model.selected);
}
}
}
Rectangle {
width: 13
height: 13
visible: !treeDelegate.hasChildren
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
CheckBox {
id: fileCheckBox
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
// scale: 0.7
onPressed: {
isPressed = true;
}
onReleased: {
isPressed = false;
}
checked: model.checked
onCheckedChanged: {
if (isPressed) {
// 更新模型中的勾选状态
treeView.model.updateChecked(model.filepath, checked);
treeView.model.updateSelected(model.filepath, !model.selected);
}
}
}
}
Image {
source: model.embellish
width: 15
height: 15
anchors.verticalCenter: parent.verticalCenter
}
Label {
id: label
text: model.filename
font.pixelSize: 12
anchors.verticalCenter: parent.verticalCenter
color: "black"
}
}
}
}
}
}
}
main.qml文件内容如下:
import QtQuick 2.9
import QtQuick.Controls 2.5
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Rectangle {
anchors.fill: parent
MyRectangle {
id: fileExp
anchors.fill: parent
visible: true
}
}
}