Как обновить TableView с данными о прогрессе для нескольких ProgressBars?

Я начал расширять qGetDownloadManager излучать прогресс TransferItemтак, чтобы я мог подключиться к нему. Я вставляю данные о прогрессе в ячейку TableView модель для отображения с DelegateНаконец, делегат рисует индикатор выполнения. Это работает в теории, но я сталкиваюсь со следующим

Проблема: когда параллельно происходит несколько загрузок, я получаю обновления прогресса от обоих сигналов в обе ячейки!

Оба индикатора выполнения показывают данные о ходе выполнения, но сигнал является смешанным и не уникальным для текущего индекса (QModelIndex index / index.row()).

(Пожалуйста, не обращайте внимания на небольшую проблему перехода между UserRoles (после нажатия кнопки загрузки отображается "ActionCell", а затем "Установить", прежде чем появится "ProgressBar".) Это не главная проблема здесь. Мой вопрос касается индекса проблема.) Текст "112" и "113" index.row,

Вопросы:

  • Как обновить TableView с данными о прогрессе для нескольких ProgressBars?
  • Что я должен изменить, чтобы отображать индикатор выполнения для каждой загрузки?

Источник

Извлечь прогресс загрузки

Я добавил следующие вещи, чтобы повторно излучать сигнал через классы, пока он не поднимется до самого верха, где он станет подключаемым из графического интерфейса.

  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. функция СЛОТ 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) в слот 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*)));
    }
    
  4. Наконец, я переиздаю процесс загрузки еще раз:

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

Теперь TableView с делегатом, doDownload(index) и ProgressBarUpdater

  1. QTableView
  2. с добавлением QSortFilterProxyModel (для нечувствительности к регистру)
  3. с добавлением ColumnDelegate, который отображает DownloadButton и ProgressBar на основе пользовательских UserRoles. Делегат обрабатывает нажатие кнопки: СИГНАЛ 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);
    }
    

я добавил QString::number(index.row() к тексту индикатора выполнения, чтобы каждый ProgressBar получал свой номер строки. Другими словами: рендеринг уникален для строки, но входящие данные о прогрессе как-то смешаны.

Я застрял в проблеме индекса на некоторое время. Спасибо заранее за вашу помощь.

Обновление: проблема решена!

Спасибо большое ddriver!! Я последовал вашим предложениям и исправил это:

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

Это приводит к одному и разному прогрессу для всех индикаторов выполнения, что соответствует последней загрузке, которая сообщает о прогрессе перед обновлением пользовательского интерфейса. Вот почему вы не получаете больше вариаций после завершения первой загрузки, так как менеджер обновляет только прогресс для второй.

Наконец, я переиздаю процесс загрузки еще раз:

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-адрес запроса элемента переноса с URL-адресом в модели на currentIndexRow, и только model-setData() если это то же самое. Это решение не очень эффективно, так как оно отправляет всем обновлениям прогресса только один из них.

  • вырезать посредника, то, что я предлагал с самого начала, сделать DownloadManager ::get() вернуть указатель на DownloadItem/TransferItem создан в своем теле, затем в UpdaterDialog::doDownload() вы можете подключить элемент переноса непосредственно к соответствующему средству обновления, так что вам больше не понадобится DownloadManager::downloadProgress() и signalProgress сигнал, вам нужно только изменить сигнатуру сигнала в TransferItem в void downloadProgress(QMap<QString, QVariant>); и испускать прогресс, а не пункт. На самом деле это наиболее эффективное решение, так как оно не требует ничего лишнего, а только удаляет ненужные вещи.

Другие вопросы по тегам