如何在后台线程中创建用于QFileSystemModel的自定义图标

7
我正在Qt中制作一个文件浏览器,用于一些自定义设计文件。我想将它们的预览加载为缩略图,因此我使用QIconProvider返回Icon到我的QFileSystemModel。
问题是创建QIcon的算法需要一些资源,导致我的应用程序在完成加载所有缩略图之前不响应。
我想知道是否有任何方法可以将我的QIconProvider放在后台线程中,以使我的应用程序响应。

1
如果您已经将预览生成代码封装为函数,则可以考虑将其传递给QtConcurrent::run进行后台执行,然后使用排队信号进行通知。 - G.M.
2个回答

11

很遗憾,QFileIconProvider API 和模型 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);
    }
};

谢谢您的回复。我尝试了您的解决方案,似乎可以工作。但是现在我在访问我的QFileSystemModel时遇到了问题。
  1. 我通过调用setSourceModel将我的QFileSytemModel设置为我的QIdentityProxyModel。
  2. 我通过调用setModel将我的QFileIdentityModel设置为我的QListView。
当我尝试访问我的QFileSystemModel中所选QModelIndex的filePath时,运行时出现了崩溃。您有什么想法吗?
- willy
我正在回答自己的问题: 这是我的错误,我试图通过从我的QListView使用QModelIndexes来访问QFileSystemModel的项目,而我的QListView已设置为我的QIdentityProxyModel。 正确的方法是通过使用您的QListView返回的QModelIndex直接从QIdentityProxyModel访问项目(因为QIdentityProxyModel设置为您的QListView)。 - willy
你说得对。索引属于特定的模型。当你使用代理时,你应该忘记源模型的存在,基本上。代理是你使用的东西 - 存在源模型是一种实现细节 :) - Kuba hasn't forgotten Monica
@KubaOrder:有没有办法返回QFileSystemModel的默认图标(例如文件夹)? 我试图让getIcon在文件夹的情况下返回“QIdentityProxyModel :: data(index,QFileSystemModel :: FileIconRole).value <QIcon>();”,但它不起作用。 - willy
1
我已经更新了答案。getIcon 不知道任何模型的信息。如果它不知道如何获取图标,它可以返回一个默认构造的图标,代理将会将请求转发到源模型以获取默认图标。 - Kuba hasn't forgotten Monica
@KubaOrder:是的!就是这个技巧。在 it->isNull() 的情况下,返回 QIdentityProxyModel::data(index, role);。 非常感谢。 - willy

6

这个被接受的答案非常棒 - 让我了解了一些更高级的Qt概念。

对于未来尝试此操作的任何人,以下是我必须进行的一些更改以使其平稳运行:

  • 限制线程:QThreadPool传递给QConcurrent::run,并将最大线程数设置为1或2。使用默认值会导致应用程序崩溃,因为所有线程都会在构建图像预览时被占用。瓶颈将是磁盘,因此在此任务上不需要超过1或2个线程。
  • 避免重新进入:需要处理在生成图标完成之前多次查询相同路径的图标的情况。当前代码将生成相同图标的多个线程。简单的解决方案是在调用QConcurrent::run之前向m_icons映射添加占位符条目。我只是调用了默认的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);

你有什么想法可以将options.rect从ItemDelegate :: paint()传递到数据模型,以便模型可以处理缩略图生成?我需要缩放+裁剪缩略图,使其适合ItemDelegate中的矩形。 - cytrinox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接