如何使用多个 ProgressBar 的进度数据更新 TableView?
How to update a TableView with progress data for multiple ProgressBars?
我已经开始扩展 qGet DownloadManager
以发出 TransferItem
的进度,以便我可以连接到它。我将进度数据插入到 TableView
模型的单元格中,以便使用 Delegate
显示,最后委托绘制进度条。这在理论上可行,但我 运行 进入以下
问题: 当有多个并行下载时,我从 both signals 获得进度更新到 both细胞!
两个进度条都显示进度数据,但信号是混合的并且不是当前索引所独有的 (QModelIndex index
/ index.row()
)。
(请忽略 UserRoles 之间的小转换问题(点击下载按钮后显示 "ActionCell",然后显示 "Install",然后才显示 "ProgressBar"。)。那不是这里的主要问题。我的问题是关于索引问题。)文本“112”和“113”是int index.row
.
问题:
- 如何使用多个 ProgressBar 的进度数据更新 TableView?
- 我必须更改什么才能为每次下载呈现进度条?
来源
发出下载进度
我添加了以下内容以通过 类 重新发射信号,直到它冒泡到顶部,在那里它可以从 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。委托处理按钮单击:SIGNAL 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!!我听从了你的建议并修复了它:
DownloadManager
跟踪所有传输的进度,并且您将每个传输项目的数据保存在各自的 TransferItem
。
IMO 的合乎逻辑的事情是从每个 TransferItem
到相应的 ProgressBarUpdater
建立连接,并从传输项目中发出。
但是,在您的情况下,您报告的进度不是来自每个单独的传输项目,而是来自下载管理器。因此,每次发出进度时,您都会将特定传输项目的进度发出到 all 个进度条。
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
所以不是
TransferItem --progress--> CorrespondingUI
你有一个:
TransferItem --transferItem--> DownloadManager --progress--> AllUIs
这导致所有进度条都有一个单一且不同的进度,这对应于 UI 更新之前碰巧报告进度的最后一次下载。这就是为什么在第一次下载完成后您不会再得到变化,因为管理器只更新第二次的进度。
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
谁真正需要匿名进程,不包含任何信息适用于哪个传输?当然除了bug。
Would you be so nice to explain, how to simplify it?
昨天我发表评论时,我已经筋疲力尽了,在头脑清醒的情况下,它看起来并不过分,但我仍然可能会选择更精简的东西,只涉及 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 转移到 currentIndexRow
模型中的请求,如果相同则仅 model-setData()
。这个解决方案不是很有效,因为它会发送给所有进度更新器,只是为了修改一个。
去掉中间人,我从一开始就建议,让 DownloadManager ::get()
return 指向 DownloadItem
/TransferItem
在其主体中创建,然后在 UpdaterDialog::doDownload()
中您可以将传输项目直接连接到适当的进度更新器,因此您将不再需要 DownloadManager::downloadProgress()
和 signalProgress
信号,您只需要将 TransferItem
中的信号签名更改为 void downloadProgress(QMap<QString, QVariant>);
并发出进度而不是项目。这实际上是最有效的解决方案,因为它不涉及任何额外的东西,只是删除了不必要的东西。
我已经开始扩展 qGet DownloadManager
以发出 TransferItem
的进度,以便我可以连接到它。我将进度数据插入到 TableView
模型的单元格中,以便使用 Delegate
显示,最后委托绘制进度条。这在理论上可行,但我 运行 进入以下
问题: 当有多个并行下载时,我从 both signals 获得进度更新到 both细胞!
两个进度条都显示进度数据,但信号是混合的并且不是当前索引所独有的 (QModelIndex index
/ index.row()
)。
(请忽略 UserRoles 之间的小转换问题(点击下载按钮后显示 "ActionCell",然后显示 "Install",然后才显示 "ProgressBar"。)。那不是这里的主要问题。我的问题是关于索引问题。)文本“112”和“113”是int index.row
.
问题:
- 如何使用多个 ProgressBar 的进度数据更新 TableView?
- 我必须更改什么才能为每次下载呈现进度条?
来源
发出下载进度
我添加了以下内容以通过 类 重新发射信号,直到它冒泡到顶部,在那里它可以从 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。委托处理按钮单击:SIGNALdownloadButtonClicked(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!!我听从了你的建议并修复了它:
DownloadManager
跟踪所有传输的进度,并且您将每个传输项目的数据保存在各自的 TransferItem
。
IMO 的合乎逻辑的事情是从每个 TransferItem
到相应的 ProgressBarUpdater
建立连接,并从传输项目中发出。
但是,在您的情况下,您报告的进度不是来自每个单独的传输项目,而是来自下载管理器。因此,每次发出进度时,您都会将特定传输项目的进度发出到 all 个进度条。
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
所以不是
TransferItem --progress--> CorrespondingUI
你有一个:
TransferItem --transferItem--> DownloadManager --progress--> AllUIs
这导致所有进度条都有一个单一且不同的进度,这对应于 UI 更新之前碰巧报告进度的最后一次下载。这就是为什么在第一次下载完成后您不会再得到变化,因为管理器只更新第二次的进度。
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
谁真正需要匿名进程,不包含任何信息适用于哪个传输?当然除了bug。
Would you be so nice to explain, how to simplify it?
昨天我发表评论时,我已经筋疲力尽了,在头脑清醒的情况下,它看起来并不过分,但我仍然可能会选择更精简的东西,只涉及 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 转移到currentIndexRow
模型中的请求,如果相同则仅model-setData()
。这个解决方案不是很有效,因为它会发送给所有进度更新器,只是为了修改一个。去掉中间人,我从一开始就建议,让
DownloadManager ::get()
return 指向DownloadItem
/TransferItem
在其主体中创建,然后在UpdaterDialog::doDownload()
中您可以将传输项目直接连接到适当的进度更新器,因此您将不再需要DownloadManager::downloadProgress()
和signalProgress
信号,您只需要将TransferItem
中的信号签名更改为void downloadProgress(QMap<QString, QVariant>);
并发出进度而不是项目。这实际上是最有效的解决方案,因为它不涉及任何额外的东西,只是删除了不必要的东西。