Виджет таблицы в Qt представляет собой довольный гибкий и удобный компонент для вывода структурированных данных. Используя QTableView вы можете полностью контролировать способ отображения данных модели. Одной из типичных задач является отрисовка в ячейке таблицы индикатора прогресса QProgressBar. Этим мы сейчас и займемся.
Подготовительный этап
Не будем усложнять демонстрационный пример и определим следующий простой виджет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#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. После нажатия на кнопку в таблицу будет добавлена строка и запустится таймер. Этим таймером мы будем моделировать некую активность и наращивать значение прогресса от нуля до сотни. Соответствующая реализация не намного сложнее, чем звучит ее описание:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#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.
Приложение уже можно запустить, и оно будет отображать возрастающий прогресс, но никакого специального индикатора вы не увидите. Это будет обычное текстовое поле с цифрами:
Но это не проблема. Сейчас мы легко все поправим.
Пишем ProgressBarDelegate
А для этого нам понадобится не так уж много. Достаточно реализовать объект-делегат, который мы установим для всего столбца нашей таблицы с помощью setItemDelegateForColumn(). Таким образом, отображение ячеек этого столбца будет осуществляться не стандартными методами, а так, как хотим мы, то есть в виде индикатора прогресса. Для определения делегата унаследуем класс QStyledItemDelegate:
1 2 3 4 5 6 7 |
class ProgressBarDelegate : public QStyledItemDelegate { public: ProgressBarDelegate( QObject* parent = 0 ); void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; }; |
В функцию paint() нашему делегату будут переданы:
- Объект класса QPainter, которым мы и будем рисовать индикатор;
- Набор опций для отображения ячейки QStyleOptionViewItem;
- Индекс модели QModelIndex, с помощью которого мы можем получить всю необходимую информацию о ячейке таблицы, которую нам предстоит нарисовать.
А вот и реализация делегата:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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. В результате должен получиться примерно такой фрагмент:
1 2 3 4 5 6 |
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 общие, то одни и те же делегаты будут работать в любом из них.