如何使用多个ProgressBars的进度数据更新TableView?

时间:2016-04-19 14:41:42

标签: qt signals-slots qtableview qstyleditemdelegate

我已经开始扩展qGet DownloadManager以发出TransferItem的进度,以便我可以连接到它。我将进度数据插入到TableView模型的单元格中,以便显示Delegate,最后委托绘制进度条。这在理论上有效,但我遇到了以下

问题:当有多个并行下载时,我会从两个信号进入两个单元格进行更新!

enter image description here

两个进度条都显示进度数据,但信号有点混合,并非当前索引唯一(QModelIndex index / index.row())。

(请忽略UserRoles之间的小转换问题(单击下载按钮“ActionCell”后显示,然后在“ProgressBar”出现之前显示“Install”。)。这不是主要问题。我的问题是关于索引问题。)文本“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)作为接收者计算进度并将其存储在progressQMap<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)与插槽DownloadManager - downloadProgress(TransferItem*)相关联。 (dlDownloadItemTransferItem}。

    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);
    }
    
  5. 现在TableView with Delegate,doDownload(index)和ProgressBarUpdater

    1. QTableView
    2. 添加QSortFilterProxyModel(不区分大小写)
    3. 添加了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)));
      
    4. 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);
      }
      
    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);
      }
      
    7. 我已将QString::number(index.row()添加到进度条文本中,以便每个ProgressBar获取其行号。换句话说:渲染对于行是唯一的,但传入的进度数据在某种程度上是混合的。

      我现在停留在索引问题上一段时间了。提前感谢您的帮助。

      更新:问题已解决!

      非常感谢ddriver !!我按照你的建议修改了它:

      enter image description here

1 个答案:

答案 0 :(得分: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的信号更改为void signalProgress(TransferItem *),将downloadProgress的正文更改为emit signalProgress(item);,更改为void ProgressBarUpdater::updateProgress(TransferItem *),并在正文中比较转移项目的请求url指向currentIndexRow模型中的那个,如果相同则仅model-setData()。这个解决方案效率不高,因为它会向所有进度更新程序发出只是为了修改它。

  • 切断了中间人,我从一开始就建议,让DownloadManager ::get()返回指向其身体中创建的DownloadItem / TransferItem的指针,然后是UpdaterDialog::doDownload()您可以将转移项直接连接到相应的进度更新程序,这样您就不再需要DownloadManager::downloadProgress()signalProgress信号,只需更改TransferItem信号的签名即可{1}}到void downloadProgress(QMap<QString, QVariant>);并发出进度而不是项目。这实际上是最有效的解决方案,因为它不需要额外的任务,只需删除不必要的东西。