4

我已经开始扩展qGetDownloadManager以发出 a 的进度TransferItem,以便我可以连接到它。我将进度数据插入TableView模型的单元格中以显示Delegate,最后代表绘制进度条。这在理论上可行,但我遇到了以下问题

问题:当有多个并行下载时,我会从两个信号中获取进度更新到两个单元格中

在此处输入图像描述

两个进度条都显示进度数据,但信号有点混合,不是当前索引 ( QModelIndex index/ index.row()) 独有的。

(请忽略 UserRoles 之间的小转换问题(单击下载按钮后显示“ActionCell”然后“安装”,在“ProgressBar”出现之前。)。这不是这里的主要问题。我的问题是关于索引问题。)文本“112”和“113”是 int index.row

问题:

  • 如何使用多个 ProgressBars 的进度数据更新 TableView?
  • 我必须更改哪些内容才能为每次下载呈现进度条?

来源

发出下载进度

我添加了以下内容以通过类重新发出信号,直到它冒泡到顶部,在那里它可以从 GUI 连接。

  1. 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();
    }
    
  2. 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);
    }
    
  3. 当新的下载入队时,我将发射连接 downloadProgress(this)到 Slot 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*)));
    }
    
  4. 最后,我再次发送下载进度:

    void DownloadManager::downloadProgress(TransferItem *item)
    {
        emit signalProgress(item->progress);
    }
    

现在带有 Delegate、doDownload(index) 和 ProgressBarUpdater 的 TableView

  1. QTableView
  2. 添加QSortFilterProxyModel(不区分大小写)
  3. 添加了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)));
    
  4. doDownload方法接收index并从模型中获取下载 URL。然后将 URL 添加到 DownloadManager 中,我正在设置 ProgressBarUpdater 对象以将进度数据设置为给定索引处的模型。最后,我连接downloadManager::signalProgressprogressBar::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);
    }
    
  5. 模型更新部分: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);
    }
    
  6. 渲染部分:我正在渲染来自委托的假 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!!我遵循了您的建议并修复了它:

在此处输入图像描述

4

1 回答 1

2

跟踪所有传输的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);
}

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

你会很高兴解释一下,如何简化它?

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

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

考虑到下载是一种传输,因此拥有 aDownloadItem和a 似乎是多余的。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 *),将主体更改downloadProgressemit signalProgress(item);,更改为void ProgressBarUpdater::updateProgress(TransferItem *),并在主体中将传输项的请求 url 与模型中的 url 进行比较currentIndexRow,并且仅model-setData()当它相同时。该解决方案效率不高,因为它会向所有进度更新器发出只是为了修改一个。

  • 剪掉中间人,我从一开始就建议,DownloadManager ::get()返回一个指向在其主体中创建的DownloadItem/的指针,然后您可以将传输项直接连接到适当的进度更新器,这样您就不再需要和信号,您只需将信号的签名更改为并发出进度而不是项目。这实际上是最有效的解决方案,因为它不涉及任何额外内容,只需要删除不必要的东西。TransferItemUpdaterDialog::doDownload()DownloadManager::downloadProgress()signalProgressTransferItemvoid downloadProgress(QMap<QString, QVariant>);

于 2016-04-22T09:50:37.977 回答