I've started to extend the qGet DownloadManager
to emit the progress of a TransferItem
, so that i can connect to it. I'm inserting the progress data into a cell of a TableView
model for display with an Delegate
, finally the delegate paints the progress bar. That works in theory, but i'm running into the following
Problem: when there are multiple downloads in parallel, then i get progress updates from both signals into both cells!
Both progress bars show progress data, but the signal is kind of mixed and not unique to the current index (QModelIndex index
/ index.row()
).
(Please ignore the small transitioning problem between UserRoles (after clicking the download button "ActionCell" is displayed and then "Install", before the "ProgressBar" shows up.). That is not the main problem here. My question is about the index problem.) The text "112" and "113" is the int index.row
.
Questions:
- How to update a TableView with progress data for multiple ProgressBars?
- What must i change to render a progress bar for each download?
Source
Emit progress of a download
I've added the following things to re-emit the signal through the classes, until it bubbles up to the top, where it becomes connectable from the GUI.
a connection from
QNetworkReply
-downloadProgress(qint64,qint64)
toTransferItem
-updateDownloadProgress(qint64,qint64)
void TransferItem::startRequest() { reply = nam.get(request); connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead())); connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64))); connect(reply, SIGNAL(finished()), this, SLOT(finished())); timer.start(); }
the SLOT function
TransferItem
-updateDownloadProgress(qint64,qint64)
as receiver calculates the progress and stores it inprogress
(QMap<QString, QVariant>
). After the calculation thedownloadProgress(this)
signal is emitted.// SLOT void TransferItem::updateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { progress["bytesReceived"] = QString::number(bytesReceived); progress["bytesTotal"] = QString::number(bytesTotal); progress["size"] = getSizeHumanReadable(outputFile->size()); progress["speed"] = QString::number((double)outputFile->size()/timer.elapsed(),'f',0).append(" KB/s"); progress["time"] = QString::number((double)timer.elapsed()/1000,'f',2).append("s"); progress["percentage"] = (bytesTotal > 0) ? QString::number(bytesReceived*100/bytesTotal).append("%") : "0 %"; emit downloadProgress(this); } QString TransferItem::getSizeHumanReadable(qint64 bytes) { float num = bytes; QStringList list; list << "KB" << "MB" << "GB" << "TB"; QStringListIterator i(list); QString unit("bytes"); while(num >= 1024.0 && i.hasNext()) { unit = i.next(); num /= 1024.0; } return QString::fromLatin1("%1 %2").arg(num, 3, 'f', 1).arg(unit); }
When a new download is enqueued, i'm connecting the emitted
downloadProgress(this)
to the SlotDownloadManager
-downloadProgress(TransferItem*)
. (dl
isDownloadItem
which extendsTransferItem
).void DownloadManager::get(const QNetworkRequest &request) { DownloadItem *dl = new DownloadItem(request, nam); transfers.append(dl); FilesToDownloadCounter = transfers.count(); connect(dl, SIGNAL(downloadProgress(TransferItem*)), SLOT(downloadProgress(TransferItem*))); connect(dl, SIGNAL(downloadFinished(TransferItem*)), SLOT(downloadFinished(TransferItem*))); }
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item) { emit signalProgress(item->progress); }
Now the TableView with Delegate, doDownload(index) and ProgressBarUpdater
QTableView
- with added
QSortFilterProxyModel
(for case-insensitivity) with added
ColumnDelegate
, which renders DownloadButton and ProgressBar based on custom UserRoles. The delegate handles the button click: the SIGNALdownloadButtonClicked(index)
is emited from theeditorEvent(event, model, option, index)
method.actionDelegate = new Updater::ActionColumnItemDelegate; ui->tableView->setItemDelegateForColumn(Columns::Action, actionDelegate); connect(actionDelegate, SIGNAL(downloadButtonClicked(QModelIndex)), this, SLOT(doDownload(QModelIndex)));
The
doDownload
method receives theindex
and fetches the download URL from the model. Then the URL is added to the DownloadManager and i'm setting up a ProgressBarUpdater object to set the progress data to the model at the given index. Finally i'm, connectingdownloadManager::signalProgress
toprogressBar::updateProgress
and invoke thedownloadManager::checkForAllDone
to start the download processing.void UpdaterDialog::doDownload(const QModelIndex &index) { QUrl downloadURL = getDownloadUrl(index); if (!validateURL(downloadURL)) return; QNetworkRequest request(downloadURL); downloadManager.get(request); // QueueMode is Parallel by default ProgressBarUpdater *progressBar = new ProgressBarUpdater(this, index.row()); progressBar->setObjectName("ProgressBar_in_Row_" + QString::number(index.row()) ); connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)), progressBar, SLOT(updateProgress(QMap<QString, QVariant>))); QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection); }
The model update part: the ProgressBarUpdater takes the index and the progress and should update the model at the given index.
ProgressBarUpdater::ProgressBarUpdater(UpdaterDialog *parent, int currentIndexRow) : QObject(parent), currentIndexRow(currentIndexRow) { model = parent->ui->tableView_1->model(); } void ProgressBarUpdater::updateProgress(QMap<QString, QVariant> progress) { QModelIndex actionIndex = model->index(currentIndexRow, UpdaterDialog::Columns::Action); // set progress to model model->setData(actionIndex, progress, ActionColumnItemDelegate::DownloadProgressBarRole); model->dataChanged(actionIndex, actionIndex); }
The rendering part: i'm rendering the fake ProgressBar from the delegate; fetching the progress data with
index.model()->data(index, DownloadProgressBarRole)
.void ActionColumnItemDelegate::drawDownloadProgressBar(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionProgressBarV2 opt; opt.initFrom(bar); opt.rect = option.rect; opt.rect.adjust(3,3,-3,-3); opt.textVisible = true; opt.textAlignment = Qt::AlignCenter; opt.state = QStyle::State_Enabled | QStyle::State_Active; // get progress from model QMap<QString, QVariant> progress = index.model()->data(index, DownloadProgressBarRole).toMap(); QString text = QString::fromLatin1(" %1 %2 %3 %4 %5 ") .arg(QString::number(index.row())) .arg(progress["percentage"].toString()) .arg(progress["size"].toString()) .arg(progress["speed"].toString()) .arg(progress["time"].toString()); opt.minimum = 0; opt.maximum = progress["bytesTotal"].toFloat(); opt.progress = progress["bytesReceived"].toFloat(); opt.text = text; bar->style()->drawControl(QStyle::CE_ProgressBar,&opt,painter,bar); }
I've added QString::number(index.row()
to the progress bar text, so that each ProgressBar gets its row number rendered. In other words: the rendering is unique to the row, but the incoming progress data is somehow mixed.
I'm stuck on the index problem for a while now. Thank you in advance for your help.
Update: The issue is resolved!
Thank you very much ddriver!! I followed your suggestions and fixed it:
qDebug()
s to pinpoint what and where goes wrong? – dtechQStandardItemModel
or did you sub classedQAbstractTableModel
? Progress information should update the data model nothing else.QTableView
should react on changes in data model. If this part is done properly thanQSortFilterProxyModel
should work out of the box. – Marek R