Как создать пользовательские значки для QFileSystemModel в фоновом потоке

Я делаю файловый браузер в qt для некоторых пользовательских файлов дизайна. Я хочу загрузить их предварительный просмотр в качестве миниатюры, и по этой причине я использую QIconProvider, чтобы вернуть значок в мой QFileSystemModel.

Проблема в том, что алгоритму, который создает QIcon, нужны некоторые ресурсы, и в результате мое приложение не отвечает, пока не завершит загрузку всех эскизов.

Мне интересно, есть ли способ поместить мой QIconProvider в фоновый поток, чтобы мое приложение реагировало.


person michalis    schedule 25.08.2016    source источник
comment
Если у вас есть код генерации предварительного просмотра, упакованный как функция, попробуйте передать его QtConcurrent::run для выполнения в фоновом режиме, а затем с использованием сигналов из очереди для уведомления.   -  person G.M.    schedule 25.08.2016


Ответы (2)


К сожалению, существует несоответствие импеданса между API QFileIconProvider и API модели: QFileSystemModel предоставляет асинхронные уведомления представлению, когда что-то меняется, но поставщик значков не может асинхронно уведомлять модель, когда значки изменяются или становятся известными.

Вы можете установить прокси-сервер идентификации между моделью файловой системы и представлением. Затем метод data этого прокси будет запрашивать значки асинхронно. В этом случае поставщик синхронных значков модели не используется и не нужен.

// https://github.com/KubaO/stackoverflown/tree/master/questions/icon-proxy-39144638
#include <QtWidgets>
#include <QtConcurrent>

/// A thread-safe function that returns an icon for an item with a given path.
/// If the icon is not known, a null icon is returned.
QIcon getIcon(const QString & path);

class IconProxy : public QIdentityProxyModel {
    Q_OBJECT
    QMap<QString, QIcon> m_icons;
    Q_SIGNAL void hasIcon(const QString&, const QIcon&, const QPersistentModelIndex& index) const;
    void onIcon(const QString& path, const QIcon& icon, const QPersistentModelIndex& index) {
        m_icons.insert(path, icon);
        emit dataChanged(index, index, QVector<int>{QFileSystemModel::FileIconRole});
    }
public:
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
        if (role == QFileSystemModel::FileIconRole) {
            auto path = index.data(QFileSystemModel::FilePathRole).toString();
            auto it = m_icons.find(path);
            if (it != m_icons.end()) {
                if (! it->isNull()) return *it;
                return QIdentityProxyModel::data(index, role);
            }
            QPersistentModelIndex pIndex{index};
            QtConcurrent::run([this,path,pIndex]{
                emit hasIcon(path, getIcon(path), pIndex);
            });
            return QVariant{};
        }
        return QIdentityProxyModel::data(index, role);
    }
    IconProxy(QObject * parent = nullptr) : QIdentityProxyModel{parent} {
        connect(this, &IconProxy::hasIcon, this, &IconProxy::onIcon);
    }
};
person Kuba hasn't forgotten Monica    schedule 25.08.2016
comment
Чем вам за ваш ответ. Я попробовал ваше решение, и оно, кажется, работает. Но теперь у меня проблемы с доступом к моему QFileSystemModel. 1. Я устанавливаю свою QFileSytemModel в свою QIdentityProxyModel, вызывая setSourceModel 2. Я устанавливаю свою QFileIdentityModel в свою QListView, вызывая setModel, и я влюблен во время выполнения, когда пытаюсь получить доступ к filePath выбранного QModelIndex моей QFileSystemModel. Любые идеи о том, что я делаю неправильно? - person michalis; 30.08.2016
comment
Я отвечаю на свой вопрос: это была моя ошибка, я пытался получить доступ к элементам QFileSystemModel, используя QModelIndexes из моего QListView, для которого было установлено значение QIdentityProxyModel. Правильный способ - получить доступ к элементам непосредственно из QIdentityProxyModel, используя QModelIndex, который возвращает вам ваш QListView (поскольку QIdentityProxyModel установлен для вашего представления QList). - person michalis; 31.08.2016
comment
Ты прав. Индексы относятся к конкретной модели. Когда вы используете прокси, вы должны забыть о существовании исходной модели. Прокси - это то, что вы используете, а наличие исходной модели - это деталь реализации :) - person Kuba hasn't forgotten Monica; 31.08.2016
comment
@KubaOrder: есть ли способ вернуть значок QFileSystemModel по умолчанию (например, в случае папки)? Я пытаюсь заставить getIcon возвращать 'QIdentityProxyModel::data(index, QFileSystemModel::FileIconRole).value‹QIcon›();' в случае папки, но это не работает. - person michalis; 31.08.2016
comment
Я обновил ответ. getIcon ничего не знает ни о каких моделях. Если он не знает, как получить значок, он может вернуть созданный по умолчанию, а затем прокси-сервер перенаправит запрос к исходной модели для значка по умолчанию. - person Kuba hasn't forgotten Monica; 31.08.2016
comment
@KubaOrder: Да! в этом была хитрость. return QIdentityProxyModel::data(index, role); в случае it->isNull() . Чем вам очень. - person michalis; 31.08.2016

Принятый ответ фантастический - познакомил меня с некоторыми из более продвинутых концепций Qt.

Для тех, кто попробует это в будущем, вот некоторые изменения, которые я должен был внести, чтобы это работало гладко:

  • Ограничить потоки. Передайте QThreadPool в QConcurrent::run с максимальным числом потоков, равным 1 или 2. Использование значения по умолчанию убило приложение, так как все потоки сжигаются при создании превью изображений. Узким местом будет диск, поэтому нет смысла использовать для этой задачи более 1 или 2 потоков.
  • Избегайте повторного ввода. Необходимо обработать случай, когда значок для одного и того же пути запрашивается несколько раз, прежде чем создание значка будет завершено. Текущий код порождает несколько потоков, генерирующих один и тот же значок. Простое решение — добавить запись-заполнитель на карту m_icons перед вызовом QConcurrent::run. Я только что вызвал значение по умолчанию QIdentityProxyModel::data(index, QFileSystemModel::FileIconRole), так что иконка получает достойное значение по умолчанию до завершения загрузки.
  • Отмена задачи. Если вы уничтожите свою модель (или захотите переключить папки просмотра и т. д.), вам понадобится способ отменить активные задачи. К сожалению, нет встроенного способа отменить отложенную QConcurrent::run задачу. Я использовал std::atomic_bool для сигнализации об отмене, которую задачи проверяют перед выполнением. И std::condition_variable для ожидания отмены/завершения всех задач.

Совет. Мой вариант использования для этого состоял в том, чтобы загружать миниатюры для предварительного просмотра изображений на диске (вероятно, общий вариант использования). После некоторых экспериментов я обнаружил, что самый быстрый способ создать превью — это использовать QImageReader, передав размер миниатюры setScaledSize. Обратите внимание, что если у вас есть неквадратные изображения, вам нужно передать размер с соответствующим соотношением сторон, например:

    const QSize originalSize = reader.size(); // Note: Doesn't load the file contents
    QSize scaledSize = originalSize;
    scaledSize.scale(MaximumIconSize, Qt::KeepAspectRatio);
    reader.setScaledSize(scaledSize);
person Alex Goldberg    schedule 30.03.2017
comment
У вас есть идея, как я могу передать options.rect из ItemDelegate::paint() в модель данных, чтобы модель могла обрабатывать генерацию миниатюр? Мне нужны масштабированные + обрезанные миниатюры, соответствующие прямоугольнику внутри ItemDelegate. - person cytrinox; 07.10.2019