如何使用多个 ProgressBars 的进度数据更新 TableView?
Posted
技术标签:
【中文标题】如何使用多个 ProgressBars 的进度数据更新 TableView?【英文标题】:How to update a TableView with progress data for multiple ProgressBars? 【发布时间】:2016-04-19 14:41:41 【问题描述】:我已经开始扩展qGet DownloadManager
以发出TransferItem
的进度,以便我可以连接到它。我将进度数据插入到TableView
模型的单元格中,以使用Delegate
显示,最后代表绘制进度条。这在理论上可行,但我遇到了以下问题
问题:当有多个并行下载时,我会从两个信号获取进度更新到两个单元!
两个进度条都显示进度数据,但信号有点混杂,不是当前索引独有的 (QModelIndex index
/ index.row()
)。
(请忽略UserRoles之间的小转换问题(单击下载按钮后显示“ActionCell”然后“安装”,在“ProgressBar”出现之前。)。这不是这里的主要问题。我的问题是关于索引问题。)文本“112”和“113”是int index.row
。
问题:
我必须更改哪些内容才能为每次下载呈现进度条?来源
发出下载进度
我添加了以下内容以通过类重新发出信号,直到它冒泡到顶部,在那里它可以从 GUI 连接。
从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();
SLOT 函数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);
现在是带有 Delegate、doDownload(index) 和 ProgressBarUpdater 的 TableView
QTableView
添加了QSortFilterProxyModel
(不区分大小写)
添加了 ColumnDelegate
,它基于自定义 UserRoles 呈现 DownloadButton 和 ProgressBar。委托处理按钮单击:信号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!!我听从了你的建议并修复了它:
【问题讨论】:
您的实现不必要地过于复杂,难怪事情会变得一团糟。您是否尝试过实际调试,或者至少放几个qDebug()
s 来查明哪里出了问题?
我故意删除了调试行。 “您的实现不必要地过于复杂”。你能解释一下,如何简化它?
我基本同意 ddriver。通过查看这段不完整的大代码,不可能找出问题所在。您应该提供一个最小的完整示例或只是简化它。移除委托,直接从更新器对象中设置项目文本,添加一些调试以查看是否更新了正确的项目等等......
您可以将您的代码发布到 github 或其他地方吗?
IMO 整个问题都被错误的设计所诅咒。我没有看到有关您正在使用的数据模型的任何信息。它是QStandardItemModel
还是您将QAbstractTableModel
子分类?进度信息应该不会更新数据模型。 QTableView
应该对数据模型的变化做出反应。如果这部分完成得当,QSortFilterProxyModel
应该可以开箱即用。
【参考方案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
这导致所有进度条都有一个单一且不同的进度,这对应于在 UI 更新之前发生报告进度的最后一次下载。这就是为什么在第一次下载完成后您不会再有任何变化,因为管理器只会更新第二次的进度。
最后,我再次发送下载进度:
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的signal改为void signalProgress(TransferItem *)
,将downloadProgress
的body改为emit signalProgress(item);
,改为void ProgressBarUpdater::updateProgress(TransferItem *)
,在body中将传输项的请求url与模型中的url进行比较currentIndexRow
,只有 model-setData()
相同。此解决方案效率不高,因为它会向所有进度更新器发出只是为了修改一个。
去掉中间人,我从一开始就建议,让DownloadManager ::get()
返回一个指向在它的主体中创建的DownloadItem
/TransferItem
的指针,然后在UpdaterDialog::doDownload()
你可以连接将传输项直接发送到相应的进度更新器,因此您将不再需要DownloadManager::downloadProgress()
和signalProgress
信号,您只需将TransferItem
中的信号签名更改为void downloadProgress(QMap<QString, QVariant>);
并发出进度而不是该项目。这实际上是最有效的解决方案,因为它不涉及任何额外内容,只需要删除不必要的东西。
【讨论】:
抱歉耽搁了。是的,就是这样 - 问题是从 downloadManager 到 progressBar 的连接。但我仍然不确定如何实现它。 --“匿名进度,不包含任何适用于哪个传输的信息”我应该将索引 id 传递给 downloadManager 以将其设置为 TransferItem 并让它与进度数据一起冒泡吗?还是发射 TransferItem 本身?如果我不传递 id,我可以使用 URL 将其与行相关联。嗯…… 关于DownloadItem
+TransferItem
的冗余:是的,可以去掉TransferItem
,只使用DownloadItem
对象。它的 Qt 代码:github.com/qtproject/qtbase/blob/dev/tests/manual/… 我猜他们使用这种方法根据请求方法区分 Upload 和 Download 对象。
你不需要管理器和进度条之间的连接,进度条是逐项的,管理器不是,但一般来说,你不想要“一对多”而是“一对一连接”——当然你可以传递一个索引,并且除了正确的索引之外的所有信号都被忽略,但这是不必要的复杂性和开销。
Qt 人——他们喜欢臃肿和混乱,也许他们是按 LOC 付费的,也许他们喜欢他们的代码难以维护以使自己变得不可或缺,我不知道,但这反映在私人他们还编写了 API 和以上琐碎的示例,以防示例产生复杂的学习和笨拙的扩展和构建代码。
@JensA.Koch - 欢迎您。这个问题其实很简单,只是因为设计错综复杂,没有立即弄清楚,IMO 并不真正值得如此慷慨的赏金,但我知道被卡住的感觉。这里真正的教训是:从最低限度开始,只在必要时扩展,较小的设计在概念上更加清晰,随着您逐渐扩展它,您会更好地了解它,相比之下,从一开始就过于复杂的设计可视化和正确组合起来很复杂。以上是关于如何使用多个 ProgressBars 的进度数据更新 TableView?的主要内容,如果未能解决你的问题,请参考以下文章