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)
}