2016-08-25 41 views
4

我在qt中爲某些自定義設計文件製作文件瀏覽器。我想將其預覽作爲縮略圖加載,因此我使用QIconProvider將圖標返回到我的QFileSystemModel如何在後臺線程中爲QFileSystemModel創建自定義圖標

問題是,創建QIcon的算法需要一些資源,因此我的應用程序在加載完所有縮略圖之前無法響應。

我想知道是否有任何方法可以將我的QIconProvider放在後臺線程中,以便我的應用程序響應。

+1

如果您預覽生成的代碼打包到那麼功能看看它傳遞給[ 'QtConcurrent :: run'](http://doc.qt.io/qt-5/qtconcurrentrun。html)進行後臺執行,然後使用排隊的信號進行通知。 –

回答

5

不幸的是,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); 
    } 
}; 
+0

比你的迴應。我試過你的解決方案,它似乎工作。但是現在我在訪問我的QFileSystemModel時遇到了問題。 1.我通過調用setSourceModel設置我的QFileSytemModel我QIdentityProxyModel 2.蔭通過調用則setModel 設置我的QFileIdentityModel我而QListView和我有運行時暗戀,而我試圖訪問的選擇QModelIndex的文件路徑我的QFileSystemModel。 關於我在做什麼錯的任何想法? – michalis

+0

我回答了我自己的問題: 這是我的錯誤,我試圖通過使用QModelIndexes從我的QListView,它已被設置爲我的QIdentityProxyModel訪問QFileSystemModel的項目。 正確的方法是使用QListView返回的QModelIndex直接從QIdentityProxyModel訪問項目(因爲QIdentityProxyModel設置爲您的QList視圖)。 – michalis

+0

你說得對。索引屬於特定的模型。當你使用代理時,你應該幾乎忘記了源模型的存在。代理是你使用的 - 有一個源模型是一個實現細節:) –

3

接受的答案是太棒了 - 向我介紹了一些更高級的Qt的概念。

對於任何人在將來嘗試這一點,這裏的一些變化,我不得不讓得到這個工作順利進行:

  • 限制線程:傳遞一個QThreadPoolQConcurrent::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); 
相關問題