Создание модели
Создадим новый проект Qt Widgets и назовем его QListViewExplorer.
Добавим на форму QListView
Добавим класс для нашей модели – QListViewExplorerModel
Заголовок:
#ifndef QLISTVIEWEXPLORERMODEL_H
#define QLISTVIEWEXPLORERMODEL_H
#include <QDir>
#include <QModelIndex>
class QListViewExplorerModel: public QAbstractListModel
{
public:
QListViewExplorerModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
QVariant data(const QModelIndex &index, int role) const;
void getFolderList(QString folderPath, QFileInfoList *dirList);
private:
QFileInfoList *aDirList;
};
#endif // QLISTVIEWEXPLORERMODEL_H
Реализация:
#include "qlistviewexplorermodel.h"
QListViewExplorerModel::QListViewExplorerModel(QObject *parent)
:QAbstractListModel(parent)
{
}
void QListViewExplorerModel::getFolderList(QString folderPath, QFileInfoList *dirList)
{
QDir dir = QDir(folderPath);
*dirList = dir.entryInfoList();
this->aDirList = dirList;
}
int QListViewExplorerModel::rowCount(const QModelIndex &) const
{
return this->aDirList->count();
}
QVariant QListViewExplorerModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->aDirList->at(index.row()).fileName();
}
break;
case Qt::UserRole: //data
{
value = this->aDirList->at(index.row()).fileName();
}
break;
default:
break;
}
return value;
}
Добавим код в конструктор формы MainWindow::MainWindow(QWidget *parent)
this->aDirList = new QFileInfoList();
this->model = new QListViewExplorerModel();
this->model->getFolderList(".",this->aDirList);
this->ui->listView->setModel(model);
Добавим поля в заголовок:
private:
Ui::MainWindow *ui;
QFileInfoList *aDirList;
QListViewExplorerModel *model;
Запустим
Мы и получили список файлов папки, из которой производится запуск нашей программы.
Рассмотрим код модели:
В методе getFolderList() мы создаем экземпляр класса QDir. Данный класс позволяет получать доступ к файлам и папкам файловой системы, а также виртуально перемещаться по ним.
После этого мы получаем список файлов и папок, указанной папки и через указатель присваиваем значение этого списка полю класса, объявленному в нашей главной форме.
Как мы уже говорили в предыдущих статьях – модель не должна хранить данные, мы просто передаем ей указатель на переменную, в которой должна храниться информация. После этого мы обновляем указатель на эти данные, объявленный как поле самого класса.
Метод data() в нашем случае для всех ролей возвращает имя файла или папки, которые мы выбрали. В данный момент это несущественно.
Сортируем список папок и файлов
У нас есть список файлов и папок, но проблема в том, что содержимое списка отсортировано непонятно как - неясно где папка, а где файл.
Применим сортировку к списку. В методе модели getFolderList() изменим строку
*dirList = dir.entryInfoList( QDir::NoFilter, QDir::DirsFirst);
QDir::NoFilter – указывает, что нам не нужна фильтрация (подробнее в документации Qt5 - https://doc.qt.io/qt-5/qdir.html#Filter-enum)
QDir::DirsFirst – задаёт метод сортировки – папки первыми, потом файлы (подробнее в документации Qt5 - https://doc.qt.io/qt-5/qdir.html#SortFlag-enum).
Запустим
Уже лучше, но всё равно непонятно, где папка, а где файл. Давайте добавим иконки для каждой строки.
Для начала нам понадобятся сами иконки, скачать вы их можете с Яндекс.Диска - https://yadi.sk/d/_N4v7pySleb07A или с Github.
Создадим в корне проекта папку img и скопируем в нее файлы с иконками.
Создадим файл ресурса resource.qrc со следующим содержимым:
<RCC>
<qresource prefix="/">
<file>img/file.png</file>
<file>img/folder.png</file>
<file>img/zip.png</file>
<file>img/folderup.png</file>
<file>img/iconlist.png</file>
<file>img/list.png</file>
</qresource>
</RCC>
Добавим его к проекту.
Изменим метод модели data():
QVariant QListViewExplorerModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->aDirList->at(index.row()).fileName();
}
break;
case Qt::DecorationRole: //icon
{
if (this->aDirList->at(index.row()).isDir()) {
QPixmap icon = QPixmap(":/img/folder.png");
QPixmap tmp = icon.scaled(30, 30, Qt::KeepAspectRatio);
value = tmp;
break;
}
if (this->aDirList->at(index.row()).isFile()) {
QPixmap icon = QPixmap(":/img/file.png");
QPixmap tmp = icon.scaled(30, 30, Qt::KeepAspectRatio);
value = tmp;
break;
}
value = this->aDirList->at(index.row()).fileName();
}
break;
case Qt::UserRole: //data
{
value = this->aDirList->at(index.row()).fileName();
}
break;
default:
break;
}
return value;
}
Запустим:
Здесь все просто, для роли Qt::DecorationRole мы возвращаем экземпляр класса QPixmap, в зависимости от типа элемента мы используем разные иконки и каждую, дополнительно, масштабируем.
Обратите внимание, мы используем временную переменную, для того, чтобы создать саму ионку. Если вы попробуете написать вот так:
value = QPixmap(":/img/file.png").scaled(30, 30, Qt::KeepAspectRatio);
То всё будет работать, но вы можете получить множество ошибок:
QPixmap::scaled: Pixmap is a null pixmap
У меня на одном ПК они появлялись, а на другом нет, неясно с чем это связано, поэтому я и использую временную переменную.
Фильтруем элементы списка
Осталось убрать папку с именем «.»
Для этого мы можем использовать фильтры метода entryInfoList()
*dirList = dir.entryInfoList(QDir::NoDot | QDir::Files | QDir::Dirs, QDir::DirsFirst);
В данном случае мы использовали фильтр:
QDir::NoDot | QDir::Files | QDir::Dirs
Что означает: не показывать пункт с одной точкой, показывать файлы и папки.
Вы можете избавиться и от пункта с двумя точками, для этого достаточно добавить к фильтру еще одно значение:
QDir::NoDot | QDir::Files | QDir::Dirs | QDir::NoDotDot
Или переписать так:
QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs
Что даст тот же результат.
Добавляем иконку для zip архивов
Давайте добавим еще кое-что, у всех файлов независимо от расширения, одна и та же иконка, давайте сделаем для отдельную zip-файлов.
Для таких файлов у нас уже есть иконка, осталось добавить код. Модернизируем метод data() нашей модели:
QVariant QListViewExplorerModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->aDirList->at(index.row()).fileName();
}
break;
case Qt::DecorationRole: //icon
{
if (this->aDirList->at(index.row()).isDir()) {
QPixmap icon = QPixmap(":/img/folder.png");
QPixmap tmp = icon.scaled(30, 30, Qt::KeepAspectRatio);
value = tmp;
break;
}
if (this->aDirList->at(index.row()).isFile()) {
QString fileExt = this->aDirList->at(index.row()).completeSuffix();
if (fileExt == "zip") {
QPixmap icon = QPixmap(":/img/zip.png");
QPixmap tmp = icon.scaled(30, 30, Qt::KeepAspectRatio);
value = tmp;
break;
}
QPixmap icon = QPixmap(":/img/file.png");
QPixmap tmp = icon.scaled(30, 30, Qt::KeepAspectRatio);
value = tmp;
break;
}
value = this->aDirList->at(index.row()).fileName();
}
break;
case Qt::UserRole: //data
{
value = this->aDirList->at(index.row()).fileName();
}
break;
default:
break;
}
return value;
}
Теперь вам нужно создать архив в папке, где осуществляется сборка проекта, у меня это папка на уровень выше, чем папка с проектом и называется она:
build-QListViewExplorer-Desktop_Qt_MinGW_w64_64bit_MSYS2-Debug
Запускаем:
У архива своя иконка.
Обратите внимание, если у вас имя архива начинается с точки, он не будет нормально обработан, я случайно столкнулся с этим, создавая архив. В принципе с точки начинаются только системные или служебные файлы, так что это не важно.
Таким же образом вы можете добавить иконки для других расширений файлов.
Открываем файлы, как в Проводнике Windows
Попробуем реализовать схожий функционал проводника – запуск/открытие файла при двойном щелчке.
Добавим слот для нашего QListView:
void MainWindow::on_listView_doubleClicked(const QModelIndex &index)
{
QDesktopServices::openUrl(QUrl(this->aDirList->at(index.row()).absoluteFilePath()));
}
При двойном щелчке на архиве запускается 7-Zip, как и ожидалось.
Добавляем адресную строку
Добавим адресную строку сверху от нашего QListView, чтобы пользователь мог видеть текущий каталог.
На форму добавим компонент LineEdit и разместим его выше QListView.
Добавим в конец конструктора формы строку:
this->ui->lineEdit->setText(QDir::currentPath());
Запустим:
Путь появился в строке.
Меняем текущую папки при двойном щелчке
Давайте добавим функционал перехода в папку, при двойном щелчке на нее.
Изменим метод слота on_listView_doubleClicked()
void MainWindow::on_listView_doubleClicked(const QModelIndex &index)
{
if (this->aDirList->at(index.row()).isDir())
{
QString tmp = this->aDirList->at(index.row()).absoluteFilePath();
model->getFolderList(this->aDirList->at(index.row()).absoluteFilePath(),this->aDirList);
this->ui->lineEdit->setText(tmp);
} else {
QDesktopServices::openUrl(QUrl(this->aDirList->at(index.row()).absoluteFilePath()));
}
}
Внесем изменения в метод модели getFolderList()
void QListViewExplorerModel::getFolderList(QString folderPath, QFileInfoList *dirList)
{
QDir dir = QDir(folderPath);
*dirList = dir.entryInfoList(QDir::NoDot | QDir::Files | QDir::Dirs, QDir::DirsFirst);
this->beginResetModel();
this->aDirList = dirList;
this->endResetModel();
}
Запустим.
Теперь вы можете сменить текущую папку, с помощью двойного щелчка по пункту «..» или имени папки.
Рассмотрим получившийся код:
В слоте по двойному щечку мы проверяем является ли выбранный элемент папкой, если является, то мы вызываем метод модели и заново загружаем элементов список элементов с помощью getFolderList(), но уже для новой папки. Не забываем обновить имя текущей папки в строке адреса.
В методе модели getFolderList(), после того как мы получили список элементов указанной папки мы используем методы beginResetModel() и endResetModel() чтобы уведомить модель о том, что произошел сброс модели, так как данные у нас целиком поменялись.
Переходим на уровень выше с помощью кнопки
Добавим кнопку, чтобы перейти в папку на уровень выше, чем текущая.
На форму добавим кнопку QPushButton.
Назначим ей иконку folderup.png из ресурса
Удалим в редакторе текст для кнопки, чтобы она использовала иконку.
Добавим к кнопке слот на одиночный щелчок:
void MainWindow::on_pushButton_clicked()
{
currentFolder.cdUp();
this->ui->lineEdit->setText(currentFolder.absolutePath());
model->getFolderList(currentFolder.absolutePath(),this->aDirList);
}
Изменим конструктор формы:
заменим
this->model->getFolderList(".",this->aDirList);
на
this->currentFolder = QDir(QDir::currentPath());
this->model->getFolderList(this->currentFolder.absolutePath(),this->aDirList);
Добавим в заголовок формы приватное поле
private:
QDir currentFolder;
Оно нам понадобится, чтобы хранить информацию о текущей папке.
Запускаем. Теперь по щелчку по этой кнопке, мы можем перейти на каталог выше.
Переключаемся между списком и иконками
Доработаем нашу программу еще немного – добавим две кнопки для переключения между отображением списком и иконками.
Добавим две кнопки на форму
Назначим одной иконку из ресурса list.png, а другой iconlist.png
Добавим слоты для кнопок
Для перехода в режим списка:
void MainWindow::on_pushButton_3_clicked()
{
this->ui->listView->setViewMode(QListView::ListMode);
}
Для перехода в режим иконок:
void MainWindow::on_pushButton_2_clicked()
{
this->ui->listView->setViewMode(QListView::IconMode);
}
Запускаем:
При переходе в режим иконок всё съезжает и выглядит очень некрасиво. Доработает методы:
void MainWindow::on_pushButton_3_clicked()
{
this->ui->listView->setUniformItemSizes(false);
this->ui->listView->setViewMode(QListView::ListMode);
}
void MainWindow::on_pushButton_2_clicked()
{
this->ui->listView->setUniformItemSizes(true);
this->ui->listView->setViewMode(QListView::IconMode);
}
Запускаем:
Уже лучше.
Управляем отображением иконок в окне:
Добавим в конструктор окна строку
this->ui->listView->setWrapping(true);
Теперь у нас папки отображаются в несколько колонок.
Заменим эту строку на
this->ui->listView->setWordWrap(true);
Имена файлов будут переноситься на новую строку, чтобы вместиться по ширине.
Заключение
Сегодня мы начали создание собственного файлового менеджера, с файлами и папками…
Был создан новый проект, на форму добавлен QListView
Для виджета была создана модель, загружающая элементы в список, используя в качестве источника данных файловую систему.
Настроили сортировку элементов списка таким образом, чтобы папки шли перед файлами.
Добавили иконки в ресурсы проекта.
Доработали модель таким образом, чтобы папки и файлы имели разные иконки.
Реализовали механизм, который при двойном щелчке на файл, открывает так же, как это делает Windows Explorer.
Добавили отдельную иконку для zip-файлов
Добавили кнопку для перехода на папку выше уровнем.
Добавили две кнопки для смены режима отображения между обычным списком и иконками.