Применение удобных классов отображения элементов
Применение удобных классов отображения элементов
Удобные Qt—подклассы отображения элементов обычно использовать проще, чем определять пользовательскую модель, и они особенно удобны, когда разделение модели и представления нам не дает преимущества. Мы использовали этот подход в главе 4, когда создавали подклассы QTableWidget и QTableWidgetItem для реализации функциональности электронной таблицы.
В данном разделе мы покажем, как можно применять удобные классы отображения элементов для вывода на экран элементов. В первом примере приводится используемый только для чтения виджет QListWidget, во втором примере — редактируемый QTableWidget и в третьем примере — используемый только для чтения QTreeWidget.
Мы начинаем с простого диалогового окна, которое позволяет пользователю выбрать из списка символ, используемый в блок-схемах программ. Каждый элемент состоит из пиктограммы, текста и уникального идентификатора.
Сначала покажем фрагмент заголовочного файла диалогового окна:
01 class FlowChartSymbolPicker : public QDialog {
02 Q_OBJECT
03 public:
04 FlowChartSymbolPicker(const QMap<int, QString> &symbolMap,
05 QWidget *parent = 0);
06 int selectedId() const { return id; }
07 void done(int result);
08 …
09 }
![](https://storage.yandexcloud.net/wr4img/412139_65_c10p1003.png)
Рис. 10.3. Приложение Выбор символа блок—схемы (Flowchart Symbol Picker).
При создании диалогового окна мы должны передать его конструктору ассоциативный массив QMap<int, QString>, и после выполнения конструктора мы можем получить идентификатор выбранного элемента (или —1, если пользователь ничего не выбрал), вызывая selectedId().
01 FlowChartSymbolPicker::FlowChartSymbolPicker(
02 const QMap<int, QString> &symbolMap, QWidget *parent)
03 : QDialog(parent)
04 {
05 id = -1;
06 listWidget = new QListWidget;
07 listWidget->setIconSize(QSize(60, 60));
08 QMapIterator<int, QString> i(symbolMap);
09 while (i.hasNext()) {
10 i.next();
11 QListWidgetItem *item = new QListWidgetItem(i.value(),
12 listWidget);
13 item->setIcon(iconForSymbol(i.value()));
14 item->setData(Qt::UserRole, i.key());
15 …
16 }
17 }
Мы инициализируем id (идентификатор последнего выбранного элемента) значением —1. Затем мы конструируем QListWidget — удобный виджет отображения элементов. Мы проходим в цикле по всем элементам ассоциативного массива символов блок—схемы symbolMap и для каждого создаем объект QListWidgetItem. Конструктор QListWidgetItem принимает выводимую на экран строку QString и родительский виджет QListWidget.
Затем задаем пиктограмму элемента и вызываем setData() для сохранения в QListWidgetItem идентификатора элемента. Закрытая функция iconForSymbol() возвращает QIcon для заданного имени символа.
QListWidgetItem может выступать в разных ролях, каждой из которых соответствует определенный объект QVariant. Самыми распространенными ролями являются Qt::DisplayRole, Qt::EditRole и Qt::IconRole, и для них предусмотрены удобные функции по установке и получению их значений (setText(), setIcon()), но имеется также несколько других ролей. Кроме того, мы можем определить пользовательские роли, задавая числовое значение, равное или большее, чем Qt::UserRole. В нашем примере мы используем Qt::UserRole при сохранении идентификатора каждого элемента.
В непоказанной части конструктора создаются кнопки, выполняется компоновка виджетов и задается заголовок окна.
01 void FlowChartSymbolPicker::done(int result)
02 {
03 id = -1;
04 if (result == QDialog::Accepted) {
05 QListWidgetItem *item = listWidget->currentItem();
06 if (item)
07 id = item->data(Qt::UserRole).toInt();
08 }
09 QDialog::done(result);
10 }
Функция done() класса QDialog переопределяется. Она вызывается при нажатии пользователем кнопок ОК или Cancel. Если пользователь нажимает кнопку OK, мы получаем доступ к соответствующему элементу и извлекаем идентификатор, используя функцию data(). Если бы нас интересовал текст элемента, мы могли бы его получить с помощью вызова item->data(Qt::DisplayRole).toString() или более простого вызова item->text().
По умолчанию QListWidget используется только для чтения. Если бы мы хотели разрешить пользователю редактировать элементы, мы могли бы соответствующим образом установить переключатели редактирования представления, используя QAbstractItemView::setEditTriggers(), например QAbstractItemView::AnyKeyPressed означает, что пользователь может инициировать редактирование элемента, просто начав вводить символы с клавиатуры. Можно было бы поступить по-другому и предусмотреть кнопку редактирования Edit (и, возможно, кнопки добавления и удаления Add и Delete) и связать их со слотами, чтобы можно было программно управлять операциями редактирования.
Теперь, когда мы увидели, как можно использовать удобный класс отображения элементов для просмотра и выбора данных, мы рассмотрим пример, в котором можно редактировать данные. Мы снова используем диалоговое окно, представляющее на этот раз набор точек с координатами (x, у), которые может редактировать пользователь.
![](https://storage.yandexcloud.net/wr4img/412139_65_c10p1004.png)
Рис. 10.4. Приложение Редактор координат.
Как и в предыдущем примере, мы основное внимание уделим программному коду, относящемуся к представлению элементов, и начнем с конструктора.
01 CoordinateSetter::CoordinateSetter(QList<QPointF> *coords,
02 QWidget *parent)
03 : QDialog(parent)
04 {
05 coordinates = coords;
06 tableWidget = new QTableWidget(0, 2);
07 tableWidget->setHorizontalHeaderLabels(
08 QStringList() << tr("X") << tr("Y"));
09 for (int row = 0; row < coordinates->count(); ++row) {
10 QPointF point = coordinates->at(row);
11 addRow();
12 tableWidget->item(row, 0)->setText(
13 QString::number(point.x()));
14 tableWidget->item(row, 1)->setText(
15 QString::number(point.y()));
16 }
17 …
18 }
Конструктор QTableWidget принимает начальное число строк и столбцов таблицы, выводимой на экран. Каждый элемент в QTableWidget представлен объектом QTableWidgetltem, включая элементы заголовков строк и столбцов. Функция setHorizontalHeaderLabels() задает заголовки всем столбцам, используя соответствующий текст из переданного списка строк. По умолчанию QTableWidget обеспечивает заголовки строк числовыми метками, начиная с 1; именно это нам и нужно, поэтому нам не приходится задавать вручную заголовки строк.
После создания и центровки заголовков столбцов мы в цикле просматриваем все переданные нам данные с координатами. Для каждой пары (x, у) мы создаем два элемента QTableWidgetItem, соответствующие координатам x и у. Эти элементы добавляются в таблицу, используя функцию QTableWidget::setItem(), в аргументах которой кроме самого элемента задаются его строка и столбец.
По умолчанию виджет QTableWidget разрешает редактирование. Пользователь может редактировать любую ячейку таблицы, установив на нее курсор и нажав F2 или просто вводя текст с клавиатуры. Все сделанные пользователем изменения автоматически сохранятся в элементах QTableWidgetltem. Запретить редактирование мы можем с помощью вызова setEditTriggers(QAbstractItemView::NoEditTriggers).
01 void CoordinateSetter::addRow()
02 {
03 int row = tableWidget->rowCount();
04 tableWidget->insertRow(row);
05 QTableWidgetltem *item0 = new QTableWidgetltem;
06 item0->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
07 tableWidget->setItem(row, 0, item0);
08 QTableWidgetltem *item1 = new QTableWidgetltem;
09 item1->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
10 tableWidget->setItem(row, 1, item1);
11 tableWidget->setCurrentItem(item0);
12 }
Слот addRow() вызывается, когда пользователь щелкает по кнопке Add Row (добавить строку). Мы добавляем в конец таблицы новую строку, используя insertRow(). Если пользователь попытается отредактировать какую-нибудь ячейку новой строки, QTableWidget автоматически создаст новый объект QTableWidgetItem.
01 void CoordinateSetter::done(int result)
02 {
03 if (result == QDialog::Accepted) {
04 coordinates->clear();
05 for (int row = 0; row < tableWidget->rowCount(); ++row) {
06 double x = tableWidget->item(row, 0)->text().toDouble();
07 double у = tableWidget->item(row, 1)->text().toDouble();
08 coordinates->append(QPointF(x, y));
09 }
10 }
11 QDialog::done(result);
12 }
Наконец, когда пользователь нажимает кнопку OK, мы очищаем координаты, переданные ранее в диалоговое окно, и создаем новый набор на основе координат в элементах виджета QTableWidget.
В качестве нашего третьего и последнего примера применения в Qt удобных виджетов отображения элементов мы рассмотрим некоторые фрагменты приложения, которое показывает параметры настройки Qt—приложения, используя QTreeWidget. Данный виджет по умолчанию используется только для чтения.
![](https://storage.yandexcloud.net/wr4img/412139_65_c10p1005.png)
Рис. 10.5. Приложение Просмотр параметров настройки (Settings Viewer).
Ниже приводится фрагмент конструктора:
01 SettingsViewer::SettingsViewer(QWidget *parent)
02 : QDialog(parent)
03 {
04 organization = "Trolltech";
05 application = "Designer";
06 treeWidget = new QTreeWidget;
08 treeWidget->setColumnCount(2);
09 treeWidget->setHeaderLabels(
10 QStringList() << tr("Key") << tr("Value"));
11 treeWidget->header()->setResizeMode(0, QHeaderView::Stretch);
12 treeWidget->header()->setResizeMode(1, QHeaderView::Stretch);
13 …
14 setWindowTitle(tr("Settings Viewer"));
15 readSettings();
16 }
Для получения доступа к параметрам настройки приложения необходимо создать объект QSettings с указанием в параметрах названия организации и имени приложения. Мы устанавливаем значения по умолчанию (приложение «Designer» компании «Trolltech») и затем создаем новый объект QTreeWidget. В конце мы вызываем фyнкцию readSettings().
01 void SettingsViewer::readSettings()
02 {
03 QSettings settings(organization, application);
04 treeWidget->clear();
05 addChildSettings(settings, 0, "");
06 treeWidget->sortByColurnn(0);
07 treeWidget->setFocus();
08 setWindowTitle(tr("Settings Viewer - %1 by %2")
09 .arg(application).arg(organization));
10 }
Параметры настройки приложения хранятся в виде набора ключей и значений, имеющих иерархическую структуру. Закрытая функция addChildSettings() принимает объект параметров настройки, родительский элемент QTreeWidgetItem и текущую «группу». Группа в QSettings аналогична каталогу файловой системы. Функция addChildSettings() может вызывать себя рекурсивно для прохода по произвольной структуре типа дерева. При первом ее вызове из функции readSettings() передается 0, задавая корень в качестве родительского объекта.
01 void SettingsViewer::addChildSettings(QSettings &settings,
02 QTreeWidgetItem *parent, const QString &group)
03 {
04 QTreeWidgetItem *item;
05 settings.beginGroup(group);
06 foreach (QString key, settings.childKeys()) {
07 if (parent) {
08 item = new QTreeWidgetItem(parent);
09 } else {
10 item = new QTreeWidgetItem(treeWidget);
11 }
12 item->setText(0, key);
13 item->setText(1, settings.value(key).toString());
14 }
15 foreach (QString group, settings.childGroups()) {
16 if (parent) {
17 item = new QTreeWidgetItem(parent);
18 } else {
19 item = new QTreeWidgetItem(treeWidget);
20 }
21 item->setText(0, group);
21 addChildSettings(settings, item, group);
22 }
23 settings.endGroup();
24 }
Функция addChildSettings() используется для создания всех элементов QTreeWidgetItem. Она проходит по всем ключам текущего уровня в иерархии параметров настройки и для каждого ключа создает один объект QTableWidgetItem. Если в качестве родительского элемента задан 0, мы создаем дочерний элемент собственно виджета QTreeWidget (т.е. создается элемент верхнего уровня); в противном случае мы создаем дочерний элемент для объекта parent. В первый столбец записывается имя ключа, а во второй столбец — соответствующее ему значение.
Затем эта функция выполняется для каждой группы текущего уровня. Для каждой группы создается новый объект QTreeWidgetItem, причем в первый столбец записывается имя группы. Затем эта функция рекурсивно вызывает саму себя с указанием группового элемента в качестве родительского для заполнения виджета QTreeWidget дочерними элементами группы.
Показанные в данном разделе виджеты отображения элементов позволяют нам использовать стиль программирования, который очень похож на тот, который применялся в ранних версиях Qt: чтение всего набора данных в виджет отображения элементов с использованием объектов, представляющих отдельные элементы данных, и (если элементы допускают редактирование) их запись обратно в источник данных. В последующих разделах мы выйдем за рамки этого простого подхода и воспользуемся всеми преимуществами, которые дает архитектура Qt модель/представление.