如何使用多个 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*)。 (dlDownloadItem 扩展 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()-&gt;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--&gt; CorrespondingUI

你有一个:

TransferItem --transferItem--&gt; DownloadManager --progress--&gt; AllUIs

这导致所有进度条都有一个单一且不同的进度,这对应于在 UI 更新之前发生报告进度的最后一次下载。这就是为什么在第一次下载完成后您不会再有任何变化,因为管理器只会更新第二次的进度。

最后,我再次发送下载进度:

void DownloadManager::downloadProgress(TransferItem *item)

    emit signalProgress(item->progress);

谁真正需要匿名进度,不包含任何信息,它适用于哪个转移?当然,除了错误。

请您解释一下,如何简化它?

昨天,当我发表评论时,我的精神已经走到了尽头,头脑清醒,看起来并没有那么夸张,但我仍然可能会选择更精简的东西,只涉及 3 个关键组件:

DownloadsManager -> DownloadController -> UI
                 -> DownloadController -> UI

考虑到下载就是传输,拥有DownloadItemTransferItem 似乎是多余的。

模型和视图也完全没有必要,将进度存储在模型中而不是仅仅将其作为进度条的成员也是如此。您可以为每次下载只使用一个常规小部件,并将它们放置在垂直布局中。

更新:

过度的、不必要的划分导致了一定程度的碎片化,这使得很难获取数据,一旦你把所有东西放在一起就需要让它工作。主要问题是您无法将传输项目绑定到正确的进度条更新程序,并且由于您仍然尚未发布所有相关代码,因此我可以提供的最简单的解决方案涉及以下小改动:

// 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&lt;QString, QVariant&gt;); 并发出进度而不是该项目。这实际上是最有效的解决方案,因为它不涉及任何额外内容,只需要删除不必要的东西。

【讨论】:

抱歉耽搁了。是的,就是这样 - 问题是从 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?的主要内容,如果未能解决你的问题,请参考以下文章

保存多个精灵的游戏进度

如何根据嵌套循环中的多个变量报告进度(对于进度条)?

使用 AFNetworking 计算下载多个文件的总进度

如何在 React 中分离多个进度百分比

PyQt5如何多个QRunnable报告进度和最终结果

多个时如何以编程方式访问相对布局中的文本视图或进度条?