QTreeView и QFileSystemModel для отображения только USB-накопителей
Я пытаюсь написать диалоговое окно, которое позволяет пользователю выбирать из любого подключенного USB-накопителя, найденного в системе. В Windows я определенно могу получить эту информацию вручную, используя вызовы GetLogicalDriveStrings и GetDriveType, так что я мог создать простой список таким образом. Но пользователь также должен иметь возможность перейти на любой из USB-накопителей, чтобы выбрать правильную папку для записи файла. Я взглянул на QFileSystemModel, но не вижу, как ограничить (отфильтровать) его показ только подключенными USB-накопителями и их дочерними папками / файлами. У кого-нибудь есть представление о том, как это лучше всего сделать с помощью фреймворка Qt?
Обновлено - 03.12.24:
docsteer, спасибо за предложение. Похоже, правильный путь с этим. Я применил предложенное изменение, и в большинстве случаев при запуске приложения происходил сбой. Он показывает C: и ждет некоторое время, а затем вылетает с кодом ошибки 255. Я предполагаю, что есть кое-что, что я не правильно подключил, но пока не смог выяснить это. В те времена, когда он не падает, я все еще вижу полный список доступных дисков в системе, включая два подключенных USB, а не просто вижу USB. Если я изменю строку 42 в filesystemmodeldialog.cpp так, чтобы вместо "usbModel" я передал "dir", то сбоя не будет. Можете ли вы или кто-либо увидеть здесь что-нибудь, что может вызвать сбой, и какую-либо причину, по которой созданная мной USBDriveFilterProxyModel, которая правильно выбирает два USB-устройства из всех подключенных дисков, не работает для фильтрации данных в представлении? Я дал все файлы из моего небольшого тестового приложения, включая заголовок, сгенерированный из файла.ui, так что, если кто-то захочет запустить его, чтобы увидеть, что происходит, он может.
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class FileSystemModelDialog;
class QFileSystemModel;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
void detectUsb();
private:
Ui::MainWindow *ui;
FileSystemModelDialog *treeView;
QFileSystemModel *fileSystemModel;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "filesystemmodeldialog.h"
#include <QFileSystemModel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
treeView = new FileSystemModelDialog(this);
setCentralWidget(treeView);
fileSystemModel = new QFileSystemModel;
}
MainWindow::~MainWindow()
{
delete ui;
}
filesystemmodeldialog.h:
#ifndef FILESYSTEMMODELDIALOG_H
#define FILESYSTEMMODELDIALOG_H
#include "ui_filesystemmodelwidget.h"
#include <QWidget>
#include <QFileSystemModel>
#include <QItemDelegate>
class USBDriveFilterProxyModel;
class IconItemDelegate : public QItemDelegate
{
public:
IconItemDelegate();
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
class IconFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;
};
class FileSystemModelDialog : public QWidget
{
Q_OBJECT
public:
explicit FileSystemModelDialog(QWidget *parent);
~FileSystemModelDialog();
private:
Ui::FileSystemModelWidget *ui;
IconFileSystemModel *dir;
USBDriveFilterProxyModel *usbModel;
Q_SIGNALS:
void signalFileSelected(QString);
};
#endif // FILESYSTEMMODELDIALOG_H
filesystemmodeldialog.cpp:
#include "filesystemmodeldialog.h"
#include "usbdrivefilter.h"
IconItemDelegate::IconItemDelegate(){}
QSize IconItemDelegate::sizeHint ( const QStyleOptionViewItem & /*option*/, const QModelIndex & index ) const
{
const QFileSystemModel *model = reinterpret_cast<const QFileSystemModel *>(index.model());
QFileInfo info = model->fileInfo(index);
if(info.isDir())
{
return QSize(40,40);
}
else
{
return QSize(64,64);
}
}
QVariant IconFileSystemModel::data ( const QModelIndex & index, int role ) const
{
// will do more, but for now, just paints to view
return QFileSystemModel::data(index, role);
}
FileSystemModelDialog::FileSystemModelDialog(QWidget *parent) :
QWidget(parent),
ui(new Ui::FileSystemModelWidget)
{
ui->setupUi(this);
dir = new IconFileSystemModel();
dir->setRootPath("\\");
dir->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
usbModel = new USBDriveFilterProxyModel(this);
usbModel->setSourceModel(dir);
usbModel->setDynamicSortFilter(true);
IconItemDelegate *iconItemDelegate = new IconItemDelegate();
ui->treeView->setModel(usbModel);
// hide unneeded hierachy info columns
ui->treeView->hideColumn(1);
ui->treeView->hideColumn(2);
ui->treeView->hideColumn(3);
ui->treeView->setItemDelegate(iconItemDelegate);
}
FileSystemModelDialog::~FileSystemModelDialog()
{
delete dir;
}
usbdrivefilter.h:
#ifndef USBDRIVEFILTER_H
#define USBDRIVEFILTER_H
#include <QSortFilterProxyModel>
#include <QModelIndex>
#include <QString>
class USBDriveFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit USBDriveFilterProxyModel(QObject *parent = 0);
protected:
virtual bool filterAcceptsRow(
int source_row, const QModelIndex &source_parent) const;
private:
void getMountedRemovables();
private:
QList<QString> removables;
};
#endif // USBDRIVEFILTER_H
usbdrivefilter.cpp:
#include "usbdrivefilter.h"
#include <windows.h>
USBDriveFilterProxyModel::USBDriveFilterProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
getMountedRemovables();
// will eventually also register for changes to mounted removables
// but need to get passed my current issue of not displaying only USBs.
}
bool USBDriveFilterProxyModel::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
// Since drive string can have more than just "<DriveLetter>:", need
// to check each entry in the usb list for whether it is contained in
// the current drive string.
for (int i = 0; i < removables.size(); i++)
{
if (sourceModel()->data(index0).toString().contains(removables[i]))
{
return true;
}
}
return false;
}
void USBDriveFilterProxyModel::getMountedRemovables()
{
DWORD test = GetLogicalDrives();
DWORD mask = 1;
UINT type = 0;
WCHAR wdrive[] = L"C:\\"; // use as a drive letter template
for (int i = 0; i < 32; i++)
{
if (test & mask)
{
wdrive[0] = (char)('A' + i); // change letter in template
type = GetDriveType(wdrive);
switch (type) {
case DRIVE_REMOVABLE:
{
QString qdrive = QString((char)('A' + i)) + ":";
removables.append(qdrive);
break;
}
default: break;
}
}
mask = mask << 1;
}
}
ui_filesystemmodelwidget.h:
#ifndef UI_FILESYSTEMMODELWIDGET_H
#define UI_FILESYSTEMMODELWIDGET_H
#include <QtWidgets/QApplication>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QTreeView>
QT_BEGIN_NAMESPACE
class Ui_FileSystemModelWidget
{
public:
QTreeView *treeView;
void setupUi(QWidget *FileSystemModelWidget)
{
if (FileSystemModelWidget->objectName().isEmpty())
FileSystemModelWidget->setObjectName(QStringLiteral("FileSystemModelWidget"));
FileSystemModelWidget->resize(670, 755);
treeView = new QTreeView(FileSystemModelWidget);
treeView->setGeometry(QRect(0, 0, 670, 531));
treeView->setObjectName(QStringLiteral("treeView"));
treeView->setAutoFillBackground(true);
treeView->setStyleSheet(QLatin1String(" QScrollBar:vertical {\n"
" width: 61px;\n"
" background-color: rgb(227, 227, 227);\n"
" }\n"
" QScrollBar::handle:vertical {\n"
" min-height: 50px;\n"
" }\n"
"\n"
"QTreeView, QListView {\n"
" alternate-background-color: rgb(226, 226, 226);\n"
" font-size: 16px;\n"
" show-decoration-selected: 1;\n"
" }\n"
"\n"
" QTreeView::item, QListView::item {\n"
" height: 22px;\n"
" border: 1px solid transparent;\n"
" border-top-color: transparent;\n"
" border-bottom-color: transparent;\n"
" }\n"
"\n"
" QTreeView::item:selected, QListView::item::selected {\n"
" border: 1px solid #567dbc;\n"
" background-color: rgb(85, 170, 255);\n"
" }\n"
"\n"
"\n"
" QTreeView::branch:has-siblings:!adjoins-item {\n"
" border-image: url(:/new/prefix1/images/vline.png) 0;\n"
" }\n"
"\n"
" QTreeView::branch:has-siblings:adjoins-item {\n"
" border-image: url(:/new/prefix1/images/branch-more.png) 0;\n"
" }\n"
"\n"
" QTreeView::branch:!has-children:!has-siblings:adjoins-item {\n"
" border-image: url"
"(:/new/prefix1/images/branch-end.png) 0;\n"
" }\n"
"\n"
" QTreeView::branch:has-children:!has-siblings:closed,\n"
" QTreeView::branch:closed:has-children:has-siblings {\n"
" border-image: none;\n"
" image: url(:/new/prefix1/images/branch-closed.png);\n"
" }\n"
"\n"
" QTreeView::branch:open:has-children:!has-siblings,\n"
" QTreeView::branch:open:has-children:has-siblings {\n"
" border-image: none;\n"
" image: url(:/new/prefix1/images/branch-open.png);\n"
" }\n"
""));
treeView->setFrameShape(QFrame::Box);
treeView->setFrameShadow(QFrame::Plain);
treeView->setHorizontalScrollMode(QAbstractItemView::ScrollPerItem);
treeView->setExpandsOnDoubleClick(true);
treeView->header()->setVisible(false);
treeView->header()->setStretchLastSection(true);
retranslateUi(FileSystemModelWidget);
QMetaObject::connectSlotsByName(FileSystemModelWidget);
} // setupUi
void retranslateUi(QWidget *FileSystemModelWidget)
{
FileSystemModelWidget->setWindowTitle(QApplication::translate("FileSystemModelWidget", "Form", 0));
} // retranslateUi
};
namespace Ui {
class FileSystemModelWidget: public Ui_FileSystemModelWidget {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_FILESYSTEMMODELWIDGET_H
Обновлено - 4/4/24:
Итак, я обнаружил, что сбой происходит в IconItemDelegate::sizeHint(), в файле filesystemusbmodeldialog.cpp. Прогон доходит до линии 9:
QFileInfo info = model->fileInfo(index);
и переход в этот момент дает нарушение доступа. Я предполагаю, что это потому, что я заменил объект IconFileSystemUsbModel на USBDriveFilterProxyModel в качестве модели QTreeView в конструкторе FileSystemUsbModelDialog. Поэтому я предполагаю, что приведение index.model() в IconItemDelegate:: sizeHint () является неправильным приведением, и что теперь мне нужно получить исходную исходную модель перед вызовом fileInfo(). Поэтому я изменил перегрузку sizeHint () следующим образом:
QSize IconItemUsbDelegate::sizeHint ( const QStyleOptionViewItem & /*option*/, const QModelIndex & index ) const
{
const USBDriveFilterProxyModel *model = reinterpret_cast<const USBDriveFilterProxyModel *>(index.model());
const QFileSystemModel *fsmodel = reinterpret_cast<const QFileSystemModel *>(model->sourceModel());
QFileInfo info = fsmodel->fileInfo(index);
if(info.isDir())
{
return QSize(40,40);
}
else
{
return QSize(64,64);
}
}
Но это не сработало. Затем я нашел ссылку, которая, казалось, говорила, что мне нужно вызвать setRootIndex() на мой взгляд, теперь, когда прокси находится на месте модели, поэтому я добавил, что в моем конструкторе FileSystemUsbModelDialog, который теперь выглядит так:
FileSystemUsbModelDialog::FileSystemUsbModelDialog(QWidget *parent) :
QWidget(parent),
ui(new Ui::FileSystemUsbModelWidget)
{
ui->setupUi(this);
dir = new IconFileSystemUsbModel();
dir->setRootPath("\\");
dir->setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
usbModel = new USBDriveFilterProxyModel(this);
usbModel->setSourceModel(dir);
usbModel->setDynamicSortFilter(false);
IconItemUsbDelegate *iconItemDelegate = new IconItemUsbDelegate();
ui->treeView->setModel(usbModel);
ui->treeView->setRootIndex(usbModel->mapFromSource(dir->setRootPath("\\")));
// hide unneeded hierachy info columns
ui->treeView->hideColumn(1);
ui->treeView->hideColumn(2);
ui->treeView->hideColumn(3);
ui->treeView->setItemDelegate(iconItemDelegate);
}
Это не сработало. Я вернулся к своему IconItemUsbDelegate::sizeHint() и изменил его обратно, думая, что, возможно, установка root в представлении - это все, что мне действительно нужно, и не повезло.
Какие-нибудь мысли?
2 ответа
Таким образом, docsteer заставил меня пойти по правильному пути, как я уже говорил в своих обновлениях, но, как я уже сказал, у меня произошел сбой при входе в метод sizeHint() моего делегата. По чьему-то предложению я добавил несколько отладочных операторов, чтобы выяснить, что индекс показал следующим образом:
qDebug() << index.isValid();
qDebug() << "text = " << index.data();
qDebug() << "Row = " << index.row() << "Column = " << index.column();
и я обнаружил, что содержание индекса было определенным для того, что я ожидал бы содержать модель прокси, а не модель файловой системы. При более внимательном рассмотрении я понял, что передавал индекс, связанный с моделью прокси, в метод fileInfo() для приведения к модели файловой системы. Я изменил его, как показано ниже, чтобы сначала привести модель индекса к указателю типа прокси-модели, а затем получить указатель модели источника (файловой системы) и вызвать его метод fileInfo(). Но теперь я сначала сопоставляю индекс с исходным индексом и передаю результат этого сопоставления в метод fileInfo() модели файловой системы, который теперь работает как шарм:
const USBDriveFilterProxyModel *model = reinterpret_cast<const USBDriveFilterProxyModel *>(index.model());
const QFileSystemModel *fsmodel = reinterpret_cast<const QFileSystemModel *>(model->sourceModel());
QFileInfo info = fsmodel->fileInfo(index);
if(info.isDir())
{
return QSize(40,40);
}
else
{
return QSize(64,64);
}
Спасибо за помощь.
Я бы предложил сделать это с помощью QSortFilterProxyModel. Пример может выглядеть так
.header
class USBDriveFilter : public QSortFilterProxyModel
{
Q_OBJECT;
public:
USBDriveFilter(QObject *parent = 0);
protected:
// Reimplemented from QSortFilterProxyModel
virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const;
};
.cpp
bool USBDriveFilter::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
// This is a naive example and just doesn't accept the drive if the name
// of the root node contains C: - you should extend it to check the letter
// against your known list of USB drives derived from the windows API
return (!sourceModel()->data(index0).toString().contains("C:"));
}
Чтобы использовать это, вы бы сделали что-то вроде
QFileSystemModel *m = new QFileSystemModel(this);
USBDriveFilter *filter = new USBDriveFilter(this);
filter->setSourceModel(m);
// Now use filter as your model to pass into your tree view.