QTreeView и QFileSystemModel для отображения только USB-накопителей

Я пытаюсь написать диалоговое окно, которое позволяет пользователю выбирать из любого подключенного USB-накопителя, обнаруженного в системе. В Windows я определенно могу получить эту информацию вручную, используя вызовы как GetLogicalDriveStrings, так и GetDriveType, так что я мог бы таким образом создать простой список. Но пользователь также должен иметь возможность перейти к любому из USB-накопителей, чтобы выбрать правильную папку для записи файла. Я взглянул на QFileSystemModel, но не вижу, как ограничить (фильтровать) его только отображением подключенных USB-накопителей и их дочерних папок / файлов. Кто-нибудь знает, как это лучше всего сделать с помощью фреймворка Qt?


Обновлено - 03.12.24:

docsteer, спасибо за предложение. Похоже, правильный путь к этому. Я реализовал предложенное изменение, и в большинстве случаев при запуске приложения у меня происходит сбой. Он показывает C: и ждет некоторое время, а затем вылетает с кодом ошибки 255. Я предполагаю, что есть что-то, что я здесь неправильно подключил, но пока не смог это выяснить. В тех случаях, когда он не падает, я все еще вижу полный список доступных дисков в системе, включая два USB-накопителя, которые я подключил, а не просто вижу USB-накопители. Если я изменю строку 42 в filesystemmodeldialog.cpp так, чтобы передать «dir» вместо «usbModel», сбоя не произойдет. Можете ли вы или кто-либо увидеть здесь что-либо, что может вызвать сбой, и какие-либо причины, по которым созданный мной 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

Обновлено - 12/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 для представления - это все, что мне действительно нужно, и безуспешно.

Есть предположения?


person bmahf    schedule 02.12.2014    source источник


Ответы (2)


Я бы предложил сделать это с помощью 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.
person docsteer    schedule 02.12.2014

Итак, 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);
}

Спасибо за помощь.

person bmahf    schedule 10.12.2014