1

Context:

For an application, I am creating a minified 'file browser' that shows the file icon, file name, relative location (w.r.t. a specific directory), date & size including folders.

Problem:

I am using the "convinience" QTableWidget for displaying of the file entries, however while testing on a few files (<10 folders / files), it takes ~3s to display the content.

After calling QTableWidget::setItem(int, int, QTableWidgetItem*) for each column, I would expect the UI to update and show the item.

What actually happens is all items are processed (setItem(int, int, QTableWidgetItem*) called then after some arbitrary time ~3s (depending on the number of items) it finally displays. This is a problem!

What have I tried / Suggestions:

  • According to numerous posts (e.g. here), resizing the QHeaderView on each insert is a big performance killer. I do not do this, instead I predefine the column widths on launch in the QDialog::showEvent(QShowEvent*)

  • Another suggestion (here and here) is to predefine the number of rows you will use using QTableWidget::setRowCount(int). I have done this by default.

Using a Custom Table Model

Finally, it is suggested (and here) to rather use a custom table model with a QTableView, with an example(s) found here

I am hesitant to take this approach (which appears to be the ideal one) as the fact that waiting ~3s for a few items to be shown in a QTableWidget is absolutely absurd.

Any suggestions to improve performance would be greatly appreciated!

I noticed that using a QFuture dramatically slows down performance vs a simple foreach loop, the latter being ~2-5 times faster, however the delay on the QTableWidget is still the main problem

Implementation details:

UI Settings

QTableWidget Settings (UI)

    QObject
     - mouseTracking enabled
     - contextMenuPolicy: Custom
     - custom stylesheet
    
    QAbstractScrollArea:
     - horizontalScrollBar: AlwaysOff
    
    QAbstractItemView:
     - No triggers
     - No drag drop overwrite
     - Alternating row colours
     - selection mode: single
     - selection behaviour: select rows
     - icon size: 64x64
    
    QTableWidget:
     - show grid: no
     - gridStyle: nopen
     - sortingEnabled: true
    
    QTableWidget:
     - columnCount: 5
    
    Header:
     - horizontalHeaderShowSortIndicator: true
     - verticalHeaderVisible: false
     - verticalHeaderSectionSize: 64

Custom Model Struct

struct Model {
     int index = -1;
     QIcon* icon;
     QString* name;
     QString* location;
     QString* dateModified;
     QString* size;
     bool isDir = false;
}; Q_DECLARE_METATYPE(Model);

Preparing to add entries

      //QFileInfoList list;  - all QFileInfo entries in the current directory
      ui->customTableWidget->setSortingEnabled(false);
      ui->customTableWidget->setRowCount(list.size());

      connect(&futureWatcherLoadDirectory, &QFutureWatcher<QPair<QFileInfo, Model>>::finished, this, [this]() {
           loadDirectoryWaitCondition.wakeAll();
           futureWatcherLoadDirectory.disconnect();
           ui->customTableWidget->setEnabled(true);
           ui->customTableWidget->setSortingEnabled(true);

      });
      connect(&futureWatcherLoadDirectory, &QFutureWatcher<QPair<QFileInfo, Model>>::resultReadyAt, this, [&](int index) {
           if (ui->customTableWidget->rowCount() == 0) {
                Global::clearAndDeleteTableListWidget(ui->customTableWidget);
           }
           QPair<QFileInfo, Model> result = futureWatcherLoadDirectory.resultAt(index);
           currentDirectoryModel.append(&result.second);
           addTableEntry(&result.second);
           //publish to table
      });

      std::function<QPair<QFileInfo, Model>(const QFileInfo)> processFileInfoItem = [this](const QFileInfo fi) {
           return processFileInfoEntry(fi);
      };
      futureLoadDirectory = QtConcurrent::mapped(list, processFileInfoItem);

      futureWatcherLoadDirectory.setFuture(futureLoadDirectory);

Adding entry item

void ExplorerDialog::addTableEntry(Model* model)
{
     bool hasIcon = (model->icon != nullptr);
     QTableWidgetItem* iconItem = new QTableWidgetItem(hasIcon ? *model->icon : QIcon(), QString());
     QTableWidgetItem* nameItem = new QTableWidgetItem(*model->name);
     QTableWidgetItem* locationItem = new QTableWidgetItem(*model->location);
     QTableWidgetItem* dateModifiedItem = new QTableWidgetItem(*model->dateModified);
     QTableWidgetItem* sizeItem = new QTableWidgetItem(*model->size);

     if (hasIcon) {
          iconItem->setTextAlignment(Qt::AlignVCenter);
     }
     nameItem->setTextAlignment(Qt::AlignVCenter);
     locationItem->setTextAlignment(Qt::AlignVCenter);
     dateModifiedItem->setTextAlignment(Qt::AlignCenter);
     sizeItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);

     if (!model->isDir && hasIcon) {
          QString base64 = ImageHelper::getIconBase64(*model->icon);
          QString html = QString("<img src='data:image/%1;base64, %2'>").arg(ImageHelper::getCacheImageFormat().toUpper(), base64);
          if (hasIcon) {
               iconItem->setToolTip(html);
          }
          nameItem->setToolTip(html);
          locationItem->setToolTip(html);
          dateModifiedItem->setToolTip(html);
          sizeItem->setToolTip(html);
     }

     int index = model->index;
     ui->customTableWidget->setItem(index, 0, iconItem);
     ui->customTableWidget->setItem(index, 1, nameItem);
     ui->customTableWidget->setItem(index, 2, locationItem);
     ui->customTableWidget->setItem(index, 3, dateModifiedItem);
     ui->customTableWidget->setItem(index, 4, sizeItem);

     // with or without QTableWidget::update being called, it has no effect. The item is not added to the table immediately (or almost immediately)
}
CybeX
  • 2,060
  • 3
  • 48
  • 115
  • 1
    Why not just use a [`QFileSystemModel`](https://doc.qt.io/qt-5/qfilesystemmodel.html) and either a [`QTableView`](https://doc.qt.io/qt-5/qtableview.html) or [`QTreeView`](https://doc.qt.io/qt-5/qtreeview.html)? Any extra display/interaction functionality you require can probably(?) be implemented using a custom item delegate. – G.M. Aug 10 '20 at 08:18
  • @G.M. thanks for the suggestion - I have been meaning to use a better format, however I am more curious about the 'delay' that QTableWidget causes. – CybeX Aug 10 '20 at 08:26
  • 1
    I would suggest to simplify your example. Remove everything that is irrelevant (`QFuture`, `QAbstractItemView`, `QAbstractScrollArea`, ...). You could also use fixed dummy data, which makes your example better reproducible. see [mcve]. You could also try if the icon is the culprit. – m7913d Aug 10 '20 at 10:41
  • It's a known fact that `Widget` classes are more convenient to use but they take a performance hit when they have to deal with complex widgets as their item widgets, so the recommendation is to use `View` classes. With that being said, I don't see how <10 items should cause a problem. Try to do the same with a `QTreeWidget` and see if you're having the same issue. From my experience `QTreeWidget` has been the easiest to deal with. – santahopar Aug 23 '20 at 22:49

0 Answers0