Жасмин Бланшет - QT 4: программирование GUI на С++
01 int RegExpModel::columnCount(const QModelIndex &
02 /* родительский элемент */) const
03 {
04 return 2;
05 }
Число столбцов фиксировано и равно 2. Первый столбец содержит типы вершин; второй столбец содержит значения вершин.
01 QModelIndex RegExpModel::parent(const QModelIndex
02 &child) const
03 {
04 Node*node = nodeFromIndex(child);
05 if (!node)
06 return QModelIndex();
07 Node *parentNode = node->parent;
08 if (!parentNode)
09 return QModelIndex();
10 Node *grandparentNode = parentNode->parent;
11 if (!grandparentNode)
12 return QModelIndex();
13 int row = grandparentNode->children.indexOf(parentNode);
14 return createIndex(row, child.column(), parentNode);
15 }
Получить QModelIndex родительского элемента из дочернего немного сложнее, чем найти дочерний элемент родителя. Можно легко получить родительскую вершину, применяя сначала функцию nodeFromIndex() и поднимаясь затем вверх с помощью указателя на родительский элемент, но для получения номера строки (позиции родительской верщины в соответствующем списке дочерних вершин) мы должны перейти к родителю родительского элемента и найти в его списке дочерних элементов значение индекса первого родителя (родителя исходной дочерней вершины).
01 QVariant RegExpModel::data(const QModelIndex
02 &index, int role) const
03 {
04 if (role != Qt::DisplayRole)
05 return QVariant();
06 Node *node = nodeFromIndex(index);
07 if (!node)
08 return QVariant();
09 if (index.column() == 0) {
10 switch (node->type) {
11 case Node::RegExp:
12 return tr("RegExp");
13 case Node::Expression:
14 return tr("Expression");
15 case Node::Term:
16 return tr("Term");
17 case Node::Factor:
18 return tr("Factor");
19 case Node::Atom:
20 return tr("Atom");
21 case Node::Terminal:
22 return tr("Terminal");
23 default:
24 return tr("Unknown");
25 }
26 } else if (index.column() == 1) {
27 return node->str;
28 }
29 return QVariant();
30 }
В функции data() получаем для запрошенного элемента указатель Node * и используем его для получения доступа к данным соответствующей вершины. Если вызывающая программа запрашивает какую-нибудь роль, отличную от Qt::DisplayRole, или если не удается получить вершину Node для заданного индекса модели, мы возвращаем недействительное значение типа QVariant. Если столбец равен 0, возвращаем название типа вершины; если столбец равен 1, вбзвращаем значение вершины (ее строку).
01 QVariant RegExpModel::headerData(int section,
02 Qt::Orientation orientation, int role) const
03 {
04 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
05 if (section == 0) {
06 return tr("Node");
07 else if (section == 1) {
08 return tr("Value");
09 }
10 }
11 return QVariant();
12 }
При переопределении функции headerData() мы возвращаем соответствующие метки горизонтального заголовка. Класс QTreeView, который используется для визуального представления иерархических моделей, не имеет заголовков строк, поэтому мы их игнорируем.
Теперь, когда рассмотрены классы Node и RegExpModel, давайте посмотрим, как создается корневая вершина, когда пользователь изменяет текст в строке редактирования.
01 void RegExpWindow::regExpChanged(const QString®Exp)
02 {
03 RegExpParser parser;
04 Node *rootNode = parser.parse(regExp);
05 regExpModel->setRootNode(rootNode);
06 }
При изменении пользователем текста в строке редактирования вызывается слот главного окна regExpChanged(). В этом слоте выполняется синтаксический анализ введенного пользователем текста, и парсер возвращает указатель на корневую вершину дерева грамматического разбора.
Мы не показываем класс RegExpParser, потому что он не имеет отношения к графическому интерфейсу или программированию модели/представления. Полный исходный код для этого примера находится на компакт-диске.
В данном разделе мы увидели, как можно создавать три различные пользовательские модели. Многие модели значительно проще приведенных выше и обеспечивают соответствие один к одному между элементами и индексами модели. В самой системе Qt находятся дополнительные примеры применения архитектуры модель/представление вместе с подробной документацией.
Реализация пользовательских делегатов
Воспроизведение и редактирование в представлениях отдельных элементов выполняются с помощью делегатов. В большинстве случаев возможности делегата, предоставляемого представлением по умолчанию, оказываются достаточными. Если нам требуется более тонкое управление воспроизведением элементов, мы сможем этого добиться, просто используя пользовательскую модель: при переопределении функции data() можем предусмотреть обработку ролей Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole и Qt::BackgroundColorRole, а также тех, которые используются делегатом по умолчанию. Например, в приведенных выше приложениях Города и Курсы валют мы применяли Qt::TextAlignmentRole для выравнивания чисел вправо.
Если нам требуется еще больший контроль, можем создать наш собственный класс делегата и связать его с нужными нам представлениями. В показанном ниже диалоговом окне Редактор фонограмм (Track Editor) используется пользовательский делегат. В этом окне отображаются названия музыкальных фонограмм и их длительность. Данные в модели будут представлены просто строками QString (названия) и значениями типа int (секунды), однако длительность будет разбита на минуты и секунды, а ее редактирование будет выполняться, используя QTimeEdit.
Рис. 10.15. Приложение Редактор фонограмм.
Диалоговое окно Редактор фонограмм использует QTableWidget — удобный подкласс отображения элементов, который работает с объектами QTableWidgetltem. Данные представлены в виде списка фонограмм Track:
01 class Track
02 {
03 public:
04 Track(const QString &title = "", int duration = 0);
05 QString title;
06 int duration;
07 };
Ниже приводится фрагмент конструктора, показывающий, как создается и пополняется табличный виджет:
01 TrackEditor::TrackEditor(QList<Track> *tracks, QWidget *parent)
02 : QDialog(parent)
03 {
04 this->tracks = tracks;
05 tableWidget = new QTableWidget(tracks->count(), 2);
06 tableWidget->setItemDelegate(new TrackDelegate(1));
07 tableWidget->setHorizontalHeaderLabels(
08 QStringList() << tr("Track") << tr("Duration"));
09 for (int row = 0; row < tracks->count(); ++row) {
10 Track track = tracks->at(row);
11 QTableWidgetltem *item0 = new QTableWidgetItem(track.titie);
12 tableWidget->setItem(row, 0, item0);
13 QTableWidgetltem *item1 = new QTableWidgetItem(
14 QString::number(track.duration));
15 item1->setTextAlignment(Qt::AlignRight);
16 tableWidget->setItem(row, 1, item1);
17 }
18 …
19 }
Конструктор создает табличный виджет и, вместо того чтобы просто использовать делегата по умолчанию, связывает виджет с нашим пользовательским делегатом TrackDelegate, передавая ему номер столбца, содержащего временные данные. Мы начинаем с установки заголовков столбцов и затем проходим в цикле по всем данным, устанавливая для каждой строки название фонограммы и ее длительность.
В остальной части конструктора и диалогового окна TrackEditor нет ничего необычного, поэтому теперь рассмотрим класс trackDelegate, который обеспечивает воспроизведение и редактирование данных фонограммы.
01 class TrackDelegate : public QItemDelegate
02 {
03 Q_OBJECT
04 public:
05 TrackDelegate(int durationColumn, QObject *parent = 0);
06 void paint(QPainter *painter, const
07 QStyleOptionViewItem &option,
08 const QModelIndex &index) const;
09 QWidget *createEditor(QWidget *parent,
10 const QStyleOptionViewItem &option,
11 const QModelIndex &index) const;
12 void setEditorData(QWidget *editor,
13 const QModelIndex &index) const;
14 void setModelData(QWidget *editor,
15 QAbstractItemModel *model,
16 const QModelIndex &index) const;
17 private slots:
18 void commitAndCloseEditor();
19 private:
20 int durationColumn;
21 };
Мы используем QItemDelegate в качестве нашего базового класса, чтобы можно было воспользоваться возможностями делегата по умолчанию. Так же мы могли бы использовать QAbstractItemDelegate, если бы хотели начать с чистого листа. Для обеспечения в делегате возможности редактирования данных мы должны реализовать функции createEditor(), setEditorData() и setModelData(). Кроме того, реализуем функцию paint() для изменения отображения столбца длительностей.