Как обновить TableView с данными о прогрессе для нескольких ProgressBars?
Я начал расширять qGetDownloadManager
излучать прогресс TransferItem
так, чтобы я мог подключиться к нему. Я вставляю данные о прогрессе в ячейку TableView
модель для отображения с Delegate
Наконец, делегат рисует индикатор выполнения. Это работает в теории, но я сталкиваюсь со следующим
Проблема: когда параллельно происходит несколько загрузок, я получаю обновления прогресса от обоих сигналов в обе ячейки!
Оба индикатора выполнения показывают данные о ходе выполнения, но сигнал является смешанным и не уникальным для текущего индекса (QModelIndex index
/ index.row()
).
(Пожалуйста, не обращайте внимания на небольшую проблему перехода между UserRoles (после нажатия кнопки загрузки отображается "ActionCell", а затем "Установить", прежде чем появится "ProgressBar".) Это не главная проблема здесь. Мой вопрос касается индекса проблема.) Текст "112" и "113" index.row
,
Вопросы:
- Как обновить TableView с данными о прогрессе для нескольких ProgressBars?
- Что я должен изменить, чтобы отображать индикатор выполнения для каждой загрузки?
Источник
Извлечь прогресс загрузки
Я добавил следующие вещи, чтобы повторно излучать сигнал через классы, пока он не поднимется до самого верха, где он станет подключаемым из графического интерфейса.
соединение от
QNetworkReply
-downloadProgress(qint64,qint64)
вTransferItem
-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(); }
функция СЛОТ
TransferItem
-updateDownloadProgress(qint64,qint64)
как получатель рассчитывает прогресс и сохраняет его вprogress
(QMap<QString, QVariant>
). После расчетаdownloadProgress(this)
сигнал испускается.// 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); }
Когда новая загрузка ставится в очередь, я подключаю
downloadProgress(this)
в слотDownloadManager
-downloadProgress(TransferItem*)
, (dl
являетсяDownloadItem
который расширяетсяTransferItem
).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*))); }
Наконец, я переиздаю процесс загрузки еще раз:
void DownloadManager::downloadProgress(TransferItem *item) { emit signalProgress(item->progress); }
Теперь TableView с делегатом, doDownload(index) и ProgressBarUpdater
QTableView
- с добавлением
QSortFilterProxyModel
(для нечувствительности к регистру) с добавлением
ColumnDelegate
, который отображает DownloadButton и ProgressBar на основе пользовательских UserRoles. Делегат обрабатывает нажатие кнопки: СИГНАЛdownloadButtonClicked(index)
испускается изeditorEvent(event, model, option, index)
метод.actionDelegate = new Updater::ActionColumnItemDelegate; ui->tableView->setItemDelegateForColumn(Columns::Action, actionDelegate); connect(actionDelegate, SIGNAL(downloadButtonClicked(QModelIndex)), this, SLOT(doDownload(QModelIndex)));
doDownload
метод получаетindex
и получает URL-адрес загрузки из модели. Затем URL-адрес добавляется в DownloadManager, и я настраиваю объект ProgressBarUpdater, чтобы установить данные о ходе выполнения для модели с заданным индексом. Наконец я подключаюсьdownloadManager::signalProgress
вprogressBar::updateProgress
и вызватьdownloadManager::checkForAllDone
начать обработку загрузки.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); }
Часть обновления модели: ProgressBarUpdater принимает индекс и прогресс и должен обновлять модель по заданному индексу.
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); }
Часть рендеринга: я рендерил фальшивый ProgressBar от делегата; получение данных о прогрессе с
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); }
я добавил QString::number(index.row()
к тексту индикатора выполнения, чтобы каждый ProgressBar получал свой номер строки. Другими словами: рендеринг уникален для строки, но входящие данные о прогрессе как-то смешаны.
Я застрял в проблеме индекса на некоторое время. Спасибо заранее за вашу помощь.
Обновление: проблема решена!
Спасибо большое ddriver!! Я последовал вашим предложениям и исправил это:
1 ответ
DownloadManager
отслеживает ход выполнения всех передач, и вы сохраняете данные каждого элемента передачи в соответствующих TransferItem
,
Логично, что IMO будет иметь связь с каждым TransferItem
к соответствующему ProgressBarUpdater
и испускать из пункта передачи.
Однако в вашем случае вы сообщаете о прогрессе не по каждому отдельному элементу передачи, а по диспетчеру загрузки. Таким образом, каждый раз, когда вы отправляете прогресс, вы отправляете прогресс для определенного элемента переноса на все индикаторы выполнения.
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
Так что вместо
TransferItem --progress--> CorrespondingUI
у тебя есть:
TransferItem --transferItem--> DownloadManager --progress--> AllUIs
Это приводит к одному и разному прогрессу для всех индикаторов выполнения, что соответствует последней загрузке, которая сообщает о прогрессе перед обновлением пользовательского интерфейса. Вот почему вы не получаете больше вариаций после завершения первой загрузки, так как менеджер обновляет только прогресс для второй.
Наконец, я переиздаю процесс загрузки еще раз:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
И кому именно нужен анонимный прогресс, не содержащий никакой информации, к какой передаче он относится? Помимо ошибки, конечно.
Не могли бы вы объяснить, как это упростить?
Вчера я был в самом конце своей мыслительной веревки, когда комментировал, с ясной головой это не выглядит слишком преувеличенным, но, тем не менее, я бы, вероятно, пошел на что-то более обтекаемое, включающее только 3 ключевых компонента:
DownloadsManager -> DownloadController -> UI
-> DownloadController -> UI
Просто кажется излишним иметь DownloadItem
а затем также TransferItem
, считая, что загрузка - это передача.
Модель и представление также совершенно не нужны, так как они хранят прогресс в модели, а не просто содержат его в качестве индикатора выполнения. Вы можете использовать обычный виджет для каждой загрузки и размещать их в вертикальной компоновке.
Обновить:
Чрезмерное ненужное разделение привело к некоторому уровню фрагментации, который затрудняет доступ к данным, необходимым для того, чтобы они работали после того, как все сложено. Основная проблема в том, что у вас нет возможности привязать элемент переноса к правильному средству обновления индикатора выполнения, и, поскольку вы все еще не опубликовали весь соответствующий код, самое простое возможное решение, которое я могу предложить, включает в себя следующие незначительные изменения:
// in DownloadManager
void signalProgress(QMap<QString, QVariant>); // this signal is unnecessary, remove
void DownloadManager::downloadProgress(TransferItem *item) // change this
{
registry[item->request.url()]->updateProgress(item->progress);
}
QMap<QUrl, ProgressBarUpdater *> registry; // add this
// in UpdaterDialog
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()) );
// remove the connection - source of the bug, instead register the updater
downloadManager.registry[downloadURL] = progressBar;
QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection);
}
Вот и все, обновление прогресса связано с URL, а в DownloadManager::downloadProgress
вместо того, чтобы отправлять прогресс всем обновлениям прогресса, вы просто ищете тот, который на самом деле соответствует конкретной загрузке, и обновляете только его прогресс. Это несколько неуклюже, но, как я уже сказал, если ваш дизайн будет правильным, он не понадобится, и у вас не возникнет проблем с самого начала.
Есть и другие решения:
изменить сигнал DownloadManager на
void signalProgress(TransferItem *)
и телоdownloadProgress
вemit signalProgress(item);
изменить наvoid ProgressBarUpdater::updateProgress(TransferItem *)
и в теле сравните URL-адрес запроса элемента переноса с URL-адресом в модели наcurrentIndexRow
, и толькоmodel-setData()
если это то же самое. Это решение не очень эффективно, так как оно отправляет всем обновлениям прогресса только один из них.вырезать посредника, то, что я предлагал с самого начала, сделать
DownloadManager ::get()
вернуть указатель наDownloadItem
/TransferItem
создан в своем теле, затем вUpdaterDialog::doDownload()
вы можете подключить элемент переноса непосредственно к соответствующему средству обновления, так что вам больше не понадобитсяDownloadManager::downloadProgress()
иsignalProgress
сигнал, вам нужно только изменить сигнатуру сигнала вTransferItem
вvoid downloadProgress(QMap<QString, QVariant>);
и испускать прогресс, а не пункт. На самом деле это наиболее эффективное решение, так как оно не требует ничего лишнего, а только удаляет ненужные вещи.