За последний год пришлось довольно много работать с Model/View фреймворком Qt. Приходилось как писать собственные модели, так и переделывать существующие. И вот, после созерцания очередного творения, решил представить общественности некоторые наработки.
Начнем с очевидного:
Не используйте виджеты
Все эти QTableWidget
, QListWidget
и QTreeWidget
— не для вас.
Использование MVC фреймворка не составляет проблемы. В простейшем случае, можно использовать готовые модели, которые предоставляет Qt. Количество кода и его сложность при этом не растет, зато удается избежать всевозможных проблем роста. Разделение модели и отображения с самого начала позволяет с легкостью добавлять такие вещи как сортировка или фильтрация, не переписывая при этом половину кода.
Одни плюсы, а минусов, при этом, не замечено.
Используйте стандартный интерфейс работы с моделями
В общем, второй пункт вытекает напрямую из первого.
Посмотрите, какие функции предоставляет вам QAbstractItemModel. Только их и должен использовать ваш код, работающий с моделью. Этого интерфейса более чем достаточно. Никакой самодеятельноти. QStandardItemModel, например, содержит такую замечательную функцию, как
QStandardItem * item ( int row, int column = 0 ) const
Не пользуйтесь ей. Все операции, в которых фигурируют Item
-ы, должны быть только операциями заполнения модели данными.
Если вы собираетесь узнать текст элемента 5
й строки 3
го столбца, не надо писать
QString text = model->item(5, 3)->text();
Используйте для этого стандартный интерфейс моделей:
QModelIndex index = model->index(5, 3);
QString text = index.data(Qt::DisplayRole).toString();
Это сэкономит кучу усилий в дальнейшем, когда вы захотите поставить между моделью и компонентом отображения прокси-модель, выполняющую сортировку, и внезапно обнаружите, что она не содержит такой замечательной функции item()
.
Избегайте QStandardItemModel
Пожалуй, это самый сложный и неочевидный пункт для объяснения.
Модель QStandardItemModel
предоставляет универсальный интерфейс, так что с его помощью можно достаточно быстро сделать какую угодно структуру представления данных.
Однако есть в ее использовании и существенные недостатки.
Во-первых, Qt предоставляет несколько готовых простых моделей для некоторых случаев: QStringListModel, QDirModel, QFileSystemModel, QSqlQueryModel с ее родственниками — к вашим услугам!
К сожалению, в большинстве случаев они не очень подходят. Что же, разрабатывать собственные модели для каждого случая?
Действительно, на первый взгляд эта задача выглядит довольно сложной. На самом деле все не так страшно. Но перед тем, как перейти к практической части написания модели, давайте рассмотрим несколько концептуальных вопросов.
Как выглядит работа с данными при использовании QStandardItemModel
?
- Получаем данные из внешнего источника.
- Заполняем модель полученными данными.
- После того, как этот процесс завершен, можно отобразить модель.
- Чтобы синхронизировать изменения, внесенные пользователем, подписываемся на сигналы (например,
itemChanged()
)
Выглядит не так уж плохо? Не тут-то было!
Во-первых, если объем данных большой, то построение модели займет достаточно много времени. Более того, поскольку модель должна быть готова полностью до того, как мы сможем показать ее пользователю, придется прочитать все данные.
Во-вторых, мы получаем, фактически, две модели данных – одна – источник данных и вторая – стандартная модель Qt. И эти модели нужно синхронизировать. Эта задача не такая простая, как кажется на первый взгляд.
К примеру, пользователь изменил некоторое значение в ячейке. Мы пробуем записать это значение в источник, но ничего не выходит (например, с источником данных потеряна связь). Теперь необходимо вернуть старое значение, чтобы не нарушить согласованность данных. Но его еще надо откуда-то взять! То есть, приходится хранить еще один, резервный, набор данных.
Фактически, все многообразие возникающих проблем проистекает из одного печального факта: как только вы начинаете использовать стандартную модель, вы больше не контролируете ваши данные. Вы встречаетесь уже со случившимися фактами, вы вынуждены исправлять ошибки, внесенные в структуру ваших данных, но вы не можете избежать их. Вы не можете навесить какие либо ограничения целостности.
Поэтому я предлагаю отказаться от стандартной модели для большинства случаев. На самом деле, вам не нужно даже разрабатывать свою модель. Потому что ваши данные – это и есть ваша модель. Она уже готова. Вам нужно только написать тонкую обертку, которая согласовывала бы интерфейс вашей модели с интерфейсом моделей Qt MVC.
В этом вам помогут стандартные заготовки Qt: QAbstractListModel, QAbstractTableModel. Адаптация их для конкретных случаев, как правило, очень проста. Вам нужно реализовать всего несколько функций, и их реализация, обычно, тривиальна.
При этом вы разом избавляетесь от множетва проблем.
- Не нужно «строить» модель. Модель уже готова после того, как создана. Для того, чтобы начать с ней работать, вам даже не нужны данные. Вам необходима только метаинформация, которую вы внесли в модель еще на этапе разработки. Все остальное вам понадобится только тогда, когда об этом спросят.
При этом, если количество данных в модели велико, то большую часть их не спросят, вероятно, никогда. - Вы можете использовать все возможность технологии Qt MVC, вроде динамической подгрузки данных или быстрого перемещения строк.
- Вы можете наложить ограничения целостности на ваши данные и не дать пользователю их разрушить.
- Вы можете использовать автоматически вычислимые данные и значения по умолчанию.
Просто попробуйте сделать с использованиемQStandardItemModel
так, чтобы ячейки с нечетными значениями отображались с зеленым цветом фона, а ячейки с четными значениями – с красным. Данные ячеек, естественно, могут быть изменены пользователем.
Совет напоследок
Как вы думаете, какая функция модели будет вызываться чаще всего? Правильно, это функция index()
.Она вызывается действительно часто. Нет, не так часто, как вы подумали. На порядок чаще. А функция index()
, скорее всего, будет вызывать функции rowCount()
и columnCount()
. Эта тройка должна работать быстро, особенно если данных в модели будет много.
А вот запрос на актуализацию данных, вызов data()
, будет происходить только тогда, когда эти данные действительно нужны. Например, для того, чтобы показать их пользователю. Если часть данных не видна в окне, то и запрашиваться она не будет.
P.S. Если интересно, могу рассказать о чем угодно подробнее по теме работы с моделями. Предложения принимаются.
Изображена девушка Abbey Lee, снятая в Diane von Furstenberg весной 2009го. Автор: Ed Kavishe, Fashion Wire Press. Лицензировано под CC-BY.
Автор: Lol4t0