Создание новых моделей
Разделение функциональности между компонентами model/view позволяет создавать модели, которые могут использовать преимущества существующих представлений. Этот подход позволяет нам представлять данные из различных источников, используя стандартные компоненты графического интерфейса пользователя, такие как QListView , QTableView и QTreeView.
Класс QAbstractItemModel предоставляет достаточно гибкий интерфейс для поддержки источников данных, которые упорядочивают информацию в иерархических структурах, что дает возможность добавления, удаления, изменения данных или их сортировку каким-либо образом. Он также обеспечивает поддержку операций перетаскивания.
Классы QAbstractListModel и QAbstractTableModel классов обеспечивают поддержку интерфейсов для более простых неиерархических структур данных, и проще использовать в качестве отправной точки для простых списков и таблиц моделей.
В этом разделе мы создадим простую read-only модель только для чтения, чтобы изучить основные принципы архитектуры model/view. Далее модель будет изменена так, чтобы данные стало можно редактировать.
На более сложную модель можно посмотреть в примере Simple Tree Model.
Требования к созданию. потомков класса QAbstractItemModel описаны в документе Model Subclassing Reference.
Разработка модели
При создании новой модели для существующей структуры данных важно учитывать, какой тип модели следует использовать для обеспечения интерфейса с данными. Если структура данных может быть представлена в виде списка или таблицы элементов, можно создать наследника класса QAbstractListModel или QAbstractTableModel, поскольку эти классы предоставляют подходящие реализации по умолчанию для многих функций.
Однако, если базовая структура данных может быть представлена только иерархической древовидной структурой, необходимо создать подкласс QAbstractItemModel . Этот подход взят из примера Simple Tree Model.
В этом разделе мы реализуем простую модель, основанную на списке строк, поэтому идеальным классам для построения представляется класс QAbstractListModel.
Независимо от формы базовой структуры данных, обычно хорошей идеей является добавление стандартного API QAbstractItemModel в специализированных моделях к той, которая обеспечивает более естественный доступ к базовой структуре данных. Это облегчает заполнение модели данными, но в то же время позволяет другим общим компонентам Model/View взаимодействовать с ней с помощью стандартного API. Модель, описанная ниже, предоставляет собственный конструктор именно для этой цели.Пример read-only модели
Реализованная здесь модель представляет собой простую неиерархическую модель данных, доступную только для чтения, основанную на стандартном классе QStringListModel . В качестве внутреннего источника данных использован QStringList, он реализует только то, что необходимо для создания работающей модели. Чтобы упростить реализацию, мы наследуемся от класса QAbstractListModel, поскольку он определяет разумное поведение по умолчанию для моделей списков и предоставляет более простой интерфейс, чем класс QAbstractItemModel .
При реализации модели важно помнить, что QAbstractItemModel не хранит никаких данных сам по себе, он просто представляет интерфейс, который представления используют для доступа к данным. Для минимальной модели только для чтения необходимо реализовать только несколько функций, поскольку для большей части интерфейса существуют реализации по умолчанию. Объявление класса выглядит следующим образом:class StringListModel : public QAbstractListModel { Q_OBJECT public: StringListModel(const QStringList &strings, QObject *parent = nullptr) : QAbstractListModel(parent), stringList(strings) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; private: QStringList stringList; };
Помимо конструктора модели нам нужно реализовать только две функции: rowCount() возвращает количество строк в модели, а data() возвращает элемент данных, соответствующий указанному модельному индексу.
Хорошим поведением для моделей также является реализация headerData(), чтобы предоставить представлениям дерева и таблицы что-то для отображения в своих заголовках.
Еще раз отметим, что это неиерархическая модель, поэтому нам не нужно беспокоиться об отношениях родитель-потомок. Если бы наша модель была иерархической, мы также должны были бы реализовать функции index() и parent() .
Список строк хранится внутри в закрытой переменной-члене stringList.
Размер модели
Мы хотим, чтобы количество строк в модели было таким же, как количество строк в списке строк. С учетом этого мы реализуем rowCount():
int StringListModel::rowCount(const QModelIndex &parent) const { return stringList.count(); }
Поскольку модель неиерархическая, мы можем смело игнорировать модельный индекс, соответствующий родительскому элементу. По умолчанию, модели, унаследованные от QAbstractListModel, содержат только один столбец, поэтому нам не нужно переопределять функцию columnCount().
Заголовки модели и данные
Для элементов в представлении мы хотим вернуть строки в списке строк. Функция data() отвечает за возврат элемента данных, который соответствует аргументу index:
QVariant StringListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= stringList.size()) return QVariant(); if (role == Qt::DisplayRole) return stringList.at(index.row()); else return QVariant(); }
Мы возвращаем валидный QVariant только если предоставленный индекс является валидным и номер строки находится в диапазоне элементов списка строк, и запрошенная роль является одной из поддерживаемых.
Некоторые представления, такие как QTreeView и QTableView, умеют отображать заголовки элементов данных данных. Если модель отображается с представлением, содержащем заголовки, мы, например, можем захотеть показать в заголовке номера строк и столбцов. Данные о заголовках мы можем предоставить в функции производного класса headerData():
QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) return QStringLiteral("Column %1").arg(section); else return QStringLiteral("Row %1").arg(section); }
И снова, мы возвращаем валидный QVariant только если роль - одна из нами поддерживаемых.Также учитывается ориентация заголовка также.
Не все представления отображают заголовки с данными элемента, а те, которые эт оделают, могут быть настроены для их скрытия. Тем не менее, все же рекомендуется реализовать функцию headerData() для предоставления соответствующей информации о данных, предоставляемых моделью.
Элемент может иметь несколько ролей, выдавая разные данные в зависимости от указанной роли. Элементы в нашей модели имеют только одну роль, DisplayRole , поэтому мы возвращаем данные для элементов независимо от указанной роли. Однако мы можем повторно использовать данные, которые мы предоставляем для DisplayRole, в других ролях, таких как ToolTipRole, которые представления могут использовать для отображения информации об элементах во всплывающей подсказке.
Редактируемая модель
Модель только для чтения показывает, как пользователю могут быть представлены простые варианты выбора, но для многих приложений модель редактируемого списка гораздо полезнее. Мы можем изменить модель только для чтения, чтобы сделать элементы редактируемыми, изменив функцию data(), которую мы реализовали только для чтения, и реализовав две дополнительные функции: flags() и setData() . Следующие объявления функций добавляются в определение класса:
Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Делаем модель редактируемой
A delegate checks whether an item is editable before creating an editor. The model must let the delegate know that its items are editable. We do this by returning the correct flags for each item in the model; in this case, we enable all items and make them both selectable and editable:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; }
Обратите внимание, что нам не нужно знать, как делегат выполняет фактический процесс редактирования. Мы только должны предоставить делегату возможность установить данные в модели. Это достигается с помощью функции setData():
bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && role == Qt::EditRole) { stringList.replace(index.row(), value.toString()); emit dataChanged(index, index, {role}); return true; } return false; }
В этой модели элемент в списке строк, который соответствует модельному индексу, заменяется предоставленным значением. Однако, прежде чем мы сможем изменить список строк, мы должны убедиться, что индекс действителен, элемент имеет правильный тип и что роль поддерживается. По соглашению мы настаиваем на том, что роль - это EditRole, так как это роль, используемая стандартным делегатом элемента. Однако для логических значений вы можете использовать Qt::CheckStateRole и установить флаг Qt::ItemIsUserCheckable ; флажок затем используется для редактирования значения. Базовые данные в этой модели одинаковы для всех ролей, поэтому эта деталь упрощает интеграцию модели со стандартными компонентами.
Когда данные были установлены, модель должна сообщить представлениям, что некоторые данные изменились. Это делается путем испускания сигнала dataChanged() . Поскольку изменился только один элемент данных, диапазон элементов, указанных в сигнале, ограничен только одним модельным индексом.
Также необходимо изменить функцию data(), чтобы добавить тест Qt::EditRole:QVariant StringListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= stringList.size()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) return stringList.at(index.row()); else return QVariant(); }
Вставка и удаление строк
Можно изменить количество строк и столбцов в модели. В модели списка строк имеет смысл только изменить количество строк, поэтому мы только переопределяем функции для вставки и удаления строк:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override; bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
Поскольку строки в этой модели соответствуют строкам в списке,
Родительский индекс обычно используется для определения того, где в модели должны быть добавлены строки. В этом случае у нас есть только один список строк верхнего уровня, поэтому мы просто вставляем пустые строки в этот список.insertRows()функция вставляет несколько пустых строк в список строк перед указанной позицией. Количество вставленных строк эквивалентно количеству указанных строк.bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent) { beginInsertRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.insert(position, ""); } endInsertRows(); return true; }
Сначала модель вызывает функцию beginInsertRows(), чтобы сообщить другим компонентам о том, что число строк собирается измениться. Функция задает номера строк первой и последней новых строк, которые нужно вставить, и модельный индекс для их родительского элемента. После изменения списка строк он вызывает endInsertRows(), чтобы завершить операцию и сообщить другим компонентам об изменении размеров модели, возвращая true, чтобы указать на успешность.
Функция удаления строк из модели также проста. Строки, которые будут удалены из модели, определяются позицией и количеством заданных строк. Для простоты мы игнорируем родительский индекс и просто удаляем соответствующие элементы из списка строк.
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent) { beginRemoveRows(QModelIndex(), position, position+rows-1); for (int row = 0; row < rows; ++row) { stringList.removeAt(position); } endRemoveRows(); return true; }
Функция beginRemoveRows() всегда вызывается перед удалением любых базовых данных и определяет первую и последнюю строки, которые должны быть удалены. Это позволяет другим компонентам получить доступ к данным, прежде чем они станут недоступными. После удаления строк модель вызывает endRemoveRows(), чтобы завершить операцию и сообщить другим компонентам, что размеры модели изменились.
Следующие шаги
Мы можем отобразить данные, предоставленные этой моделью или любой другой моделью, с помощью класса QListView для представления элементов модели в форме вертикального списка. Для модели списка строк это представление также предоставляет редактор по умолчанию, так что элементами можно манипулировать. Мы исследуем возможности, предоставляемые стандартными классами представлений в View Classes.
Документ Model Subclassing Reference более детально описывает требования к созданию наследников класса QAbstractItemModel, и описывает, какие виртуальные функции должны быть реализованы для разрешения различных возможностей моделей различных типов.
Комментариев нет:
Отправить комментарий