IT Notes

QProgressBar в таблице QTableView

Введение

Виджет таблицы в Qt представляет собой довольный гибкий и удобный компонент для вывода структурированных данных. Используя QTableView вы можете полностью контролировать способ отображения данных модели. Одной из типичных задач является отрисовка в ячейке таблицы индикатора прогресса QProgressBar. Этим мы сейчас и займемся.

Подготовительный этап

Не будем усложнять демонстрационный пример и определим следующий простой виджет:

#include <QWidget>
#include <QTimer>

class QTableWidget;
class QPushButton;

class ProgressBarDemo : public QWidget {
    Q_OBJECT

public:
    ProgressBarDemo( QWidget* parent = 0 );
    ~ProgressBarDemo();

private slots:
    void addRow();
    void onProgress();

private:
    QTableWidget* m_table;
    QPushButton* m_btnAdd;

    QTimer m_timer;

    int m_currentRow;
    int m_currentProgress;

};

За основу мы взяли не сам QTableView, а его наследника QTableWidget, для которого не требуется создавать дополнительный класс модели. Однако все, о чем мы будем говорить, будет работать и для QTableView. Более того, описанный подход также будет работать для QTreeView и QListView.

На форме виджета мы разместим таблицу с единственным столбцом Progress. А внизу прикрепим кнопку Add. После нажатия на кнопку в таблицу будет добавлена строка и запустится таймер. Этим таймером мы будем моделировать некую активность и наращивать значение прогресса от нуля до сотни. Соответствующая реализация не намного сложнее, чем звучит ее описание:

#include <QTableWidget>
#include <QHeaderView>
#include <QVBoxLayout>
#include <QPushButton>

#include <QApplication>

static const QStringList TABLE_COLUMN_LABELS = QStringList() << "Progress";
static const int PROGRESS_STEP = 5;
static const int PROGRESS_DELAY_MSEC = 100;
static const int MAX_PROGRESS_VALUE = 100;

ProgressBarDemo::ProgressBarDemo( QWidget* parent ) :
    QWidget( parent ),
    m_currentRow( 0 ),
    m_currentProgress( 0 ) {

    QVBoxLayout* mainLayout = new QVBoxLayout;
    setLayout( mainLayout );

    m_table = new QTableWidget;
    m_table->setColumnCount( TABLE_COLUMN_LABELS.count() );
    m_table->setHorizontalHeaderLabels( TABLE_COLUMN_LABELS );
    m_table->horizontalHeader()->setResizeMode( QHeaderView::Stretch );
    mainLayout->addWidget( m_table );

    QHBoxLayout* panelLayout = new QHBoxLayout;
    mainLayout->addLayout( panelLayout );

    panelLayout->addStretch( 1 );

    m_btnAdd = new QPushButton( "Add" );
    connect( m_btnAdd, SIGNAL( clicked() ), SLOT( addRow() ) );
    panelLayout->addWidget( m_btnAdd );

    resize( 640, 480 );

    m_timer.setInterval( PROGRESS_DELAY_MSEC );
    connect( &m_timer, SIGNAL( timeout() ), SLOT( onProgress() ) );
}

ProgressBarDemo::~ProgressBarDemo() {
}

void ProgressBarDemo::addRow() {
    m_currentProgress = 0;
    m_currentRow = m_table->rowCount();

    m_table->insertRow( m_currentRow );
    if( QTableWidgetItem* item = new QTableWidgetItem( "0" ) ) {
        item->setFlags( item->flags() ^ Qt::ItemIsEditable );
        m_table->setItem( m_currentRow, 0, item );
    }

    m_btnAdd->setDisabled( true );
    m_timer.start();
}

void ProgressBarDemo::onProgress() {
    m_currentProgress += PROGRESS_STEP;

    if( QTableWidgetItem* item = m_table->item( m_currentRow, 0 ) ) {
        item->setData( Qt::DisplayRole, m_currentProgress );
    }

    if( m_currentProgress == MAX_PROGRESS_VALUE ) {
        m_timer.stop();
        m_btnAdd->setEnabled( true );
    }
}

Здесь лишь обратим внимание на то, что новая строка в таблицу добавляется с помощью insertRow(). После чего создается элемент QTableWidgetItem, который мы прикрепим к новой строке. Для него мы убираем возможность редактирования исключая флаг Qt::ItemIsEditable. Установка нового значения происходит в слоте onProgress() с помощью функции-члена setData() для роли Qt::DisplayRole.

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

progress_before

Но это не проблема. Сейчас мы легко все поправим.

Пишем ProgressBarDelegate

А для этого нам понадобится не так уж много. Достаточно реализовать объект-делегат, который мы установим для всего столбца нашей таблицы с помощью setItemDelegateForColumn(). Таким образом, отображение ячеек этого столбца будет осуществляться не стандартными методами, а так, как хотим мы, то есть в виде индикатора прогресса. Для определения делегата унаследуем класс QStyledItemDelegate:

class ProgressBarDelegate : public QStyledItemDelegate {
public:
    ProgressBarDelegate( QObject* parent = 0 );
    void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
};

В функцию paint() нашему делегату будут переданы:

  1. Объект класса QPainter, которым мы и будем рисовать индикатор;
  2. Набор опций для отображения ячейки QStyleOptionViewItem;
  3. Индекс модели QModelIndex, с помощью которого мы можем получить всю необходимую информацию о ячейке таблицы, которую нам предстоит нарисовать.

А вот и реализация делегата:

static const int PROGRESS_BAR_HEIGHT_PX = 20;

ProgressBarDelegate::ProgressBarDelegate( QObject* parent ) : QStyledItemDelegate( parent ) {
}

void ProgressBarDelegate::paint(
    QPainter* painter,
    const QStyleOptionViewItem& option,
    const QModelIndex& index
) const {
    int progress = index.data().toInt();

    QStyleOptionProgressBar progressBarOption;
    QRect r = option.rect;
    r.setHeight( PROGRESS_BAR_HEIGHT_PX );
    r.moveCenter( option.rect.center() );
    progressBarOption.rect = r;
    progressBarOption.textAlignment = Qt::AlignCenter;
    progressBarOption.minimum = 0;
    progressBarOption.maximum = MAX_PROGRESS_VALUE;
    progressBarOption.progress = progress;
    progressBarOption.text = QString::number( progress ) + "%";
    progressBarOption.textVisible = true;

    QStyledItemDelegate::paint( painter, option, QModelIndex() );
    QApplication::style()->drawControl( QStyle::CE_ProgressBar, &progressBarOption, painter );
}

В представленной реализации мы создаем набор опций QStyleOptionProgressBar, в котором определяем стандартные параметры отображения индикатора прогресса. Обратите внимание, что размер индикатора мы определяем с помощью QRect. Причем за основу взят размер из переданных нам опций option. Но этот размер будет соответствовать всей области ячейки, а она может оказаться слишком высокой, из-за чего индикатор окажется слишком растянутым, поэтому для высоты мы выбрали свое значение.

В конце функции paint() мы сначала вызываем реализацию, предусмотренную в базовом классе, но передали в последнем аргументе пустой QModelIndex, чтобы не выводить никаких данных модели. Это нужно, чтобы наша ячейка могла отображать некоторые базовые состояния, типа рамки фокуса и подсветки в случае выбора (можете убрать эту строку и убедиться, что ничего не происходит, когда вы щелкаете по ячейкам). А сам индикатор прогресса с заготовленными параметрами рисуется вызовом QApplication::style()->drawControl().

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

m_table = new QTableWidget;
m_table->setColumnCount( TABLE_COLUMN_LABELS.count() );
m_table->setHorizontalHeaderLabels( TABLE_COLUMN_LABELS );
m_table->horizontalHeader()->setResizeMode( QHeaderView::Stretch );
m_table->setItemDelegateForColumn( 0, new ProgressBarDelegate ); // устанавливаем наш делегат
mainLayout->addWidget( m_table );

После запуска и пары нажатий на кнопку Add я получил следующее:

Все работает!

Заключение

Вот мы и познакомились со способом отображения индикатора прогресса в виджете таблицы. Таким же образом вы можете рисовать в своем делегате вообще все, что угодно. Кроме того, поскольку базовые классы у QTableView, QTreeView и QListView общие, то одни и те же делегаты будут работать в любом из них.

Исходники

 Скачать пример использования QProgressBar в таблице QTableView

Похожие публикации

Комментарии

Как это правильно создать?

Как это правильно сделать, через QT Creator?

Здравствуйте, Илья. Добавил в конец статьи ссылку на загрузку исходных кодов проекта.