Сталкивались ли вы с ситуацией, когда один и тот же набор данных приходится «запихивать» в несколько виджетов — и внезапно приложение начинает пухнуть по памяти, а синхронизация превращается в головную боль? Особенно это чувствуется с файловой системой и любыми «тяжёлыми» источниками: дублирование растёт, баги множатся, а изменения в логике хранения данных тянут за собой каскад правок.
Эта глава раскроет, почему Qt-подход «интервью» (model/view) — не просто шаблон из учебников, а практический инструмент, которым профессиональные разработчики спасают производительность и архитектуру. Здесь обнаружите неочевидный секрет: как отделить данные от отображения так, чтобы менять хранилище почти без боли — и при этом получать чистый код, удобный для тестирования.
Будут разобраны 4 ключевых компонента (модель, представление, выделение, делегат), работа с QModelIndex и ролями (DisplayRole/ToolTipRole/DecorationRole), а также сортировка/фильтрация через QSortFilterProxyModel. Плюс — практические примеры: разделение выделения между 3 представлениями, кастомный делегат с подсветкой и мини-обозреватель файловой системы на QFileSystemModel.
Если модель-представление до сих пор кажется «сложной магией» — пропустить эту главу значит продолжать платить временем и памятью в каждом втором проекте.
И чтобы сразу перейти от чтения к практике: доступна ссылка на архив со всеми исходниками примеров (готовы к компиляции) и 16 бесплатными главами.
Самопроверка по главе
Почему технология «интервью» эффективнее элементно-ориентированного подхода при работе с базами данных и большими объемами информации?Ответ
Правильный ответ: Данные не дублируются при использовании нескольких представлений — все представления работают с одной моделью данных через интерфейс. Это экономит память и автоматически решает проблему синхронизации.
Какие два метода обязательно нужно реализовать при создании собственной модели на основе QAbstractListModel?Ответ
Правильный ответ: Методы rowCount() (возвращает количество строк) и data() (возвращает данные для заданного индекса и роли). Без них модель не сможет предоставлять данные представлениям.
Что такое промежуточная модель (Proxy model) и зачем она нужна?Ответ
Правильный ответ: Это модель между исходной моделью и представлением, позволяющая выполнять сортировку и фильтрацию данных без изменения оригинальной модели. Класс QSortFilterProxyModel — готовая реализация для этих задач.
Почему при изменении данных через метод setData() необходимо отправлять сигнал dataChanged()?Ответ
Правильный ответ: Сигнал уведомляет все подключенные представления о том, что данные изменились, и они должны обновить свое содержимое. Без этого сигнала изменения не отобразятся в интерфейсе.
Из каких трех частей состоит индекс модели (QModelIndex)?Ответ
Правильный ответ: Из строки, столбца и внутреннего идентификатора. Это позволяет адресовать ячейки в иерархических таблицах, где внутренний идентификатор указывает на родительский элемент.
Зачем нужно вызывать beginInsertRows() и endInsertRows() при вставке строк в модель?Ответ
Правильный ответ: Эти методы уведомляют все связанные представления и другие объекты о начале и завершении изменений в модели, что позволяет им корректно обновить свое состояние и избежать ошибок отображения.
Какую роль играет делегат в архитектуре «модель-представление» и какой метод нужно перегрузить для изменения внешнего вида элементов?Ответ
Правильный ответ: Делегат отвечает за рисование каждого элемента и его редактирование. Для изменения внешнего вида нужно перегрузить метод paint(), получающий QPainter и параметры отображения.
Как сделать элементы модели редактируемыми пользователем?Ответ
Правильный ответ: Реализовать метод setData() для сохранения изменений и переопределить метод flags() так, чтобы он возвращал флаг Qt::ItemIsEditable для валидных индексов.
Почему в методе rowCount() для модели списка нужно возвращать 0, если parent.isValid() равно true?Ответ
Правильный ответ: Модель списка (QAbstractListModel) по определению одномерна и не должна иметь дочерних элементов. Валидный индекс родителя указывает на попытку получить дочерние элементы, которых быть не должно.
Какие преимущества дает централизованное управление выделением через QItemSelectionModel?Ответ
Правильный ответ: Позволяет разделять механизм выделения между несколькими представлениями одной модели — выделение элемента в одном представлении автоматически отразится во всех остальных. Код управления выделением находится в одном месте.
Для чего используются роли элементов (например, Qt::DisplayRole, Qt::EditRole)?Ответ
Правильный ответ: Роли позволяют каждому элементу модели хранить разные типы данных для разных целей: текст для отображения, значение для редактирования, иконку, цвет фона, подсказку и т.д.
В каком случае стоит использовать QStandardItemModel вместо создания собственной модели?Ответ
Правильный ответ: Когда нужно работать с небольшим количеством данных и удобнее хранить данные напрямую в модели. Это компромиссное решение, противоречащее идее модель-представление, но практичное для простых приложений.
Как можно получить доступ к встроенной модели элементно-ориентированного виджета QListWidget и зачем это может понадобиться?Ответ
Правильный ответ: Вызвать метод model(), который возвращает указатель на встроенную модель. Это позволяет разделить данные QListWidget с другими представлениями без дублирования.
Практические задания
Простой уровень
Список задач с разделением представлений
Создайте приложение для списка задач, используя QStringListModel. Отобразите эту модель одновременно в QListView и QTableView. Добавьте 5 начальных задач. Убедитесь, что выделение элемента в одном представлении автоматически выделяет его и в другом.
Подсказки: Используйте QStringListModel и метод setStringList() для инициализации данных. Создайте QItemSelectionModel и установите её в оба представления методом setSelectionModel(). Не забудьте установить модель в оба представления через setModel().
Средний уровень
Модель контактов с фильтрацией
Создайте приложение с моделью контактов (имена людей). Используйте QStringListModel с 10-15 именами. Добавьте QLineEdit для ввода фильтра и два представления QListView: одно показывает все контакты, другое — только отфильтрованные. При вводе текста в QLineEdit второе представление должно показывать только имена, содержащие введенный текст.
Подсказки: Используйте QSortFilterProxyModel для фильтрации. Создайте оригинальную модель QStringListModel и промежуточную модель, связанную с ней через setSourceModel(). Соедините сигнал textChanged() из QLineEdit со слотом setFilterWildcard() или setFilterRegularExpression() промежуточной модели.
Сложный уровень
Собственная модель студентов с оценками
Создайте собственную табличную модель на основе QAbstractTableModel для хранения информации о студентах: имя, возраст и средний балл. Модель должна поддерживать редактирование всех полей. Реализуйте методы data(), setData(), rowCount(), columnCount(), headerData() и flags(). Добавьте возможность вставки новых строк через кнопку. Отобразите модель в QTableView с кастомным делегатом, который выделяет строки студентов с баллом выше 4.5 зелёным фоном.
Подсказки: Храните данные в QVector> или создайте структуру Student. В методе data() обрабатывайте роли Qt::DisplayRole и Qt::EditRole. Для редактирования верните Qt::ItemIsEditable в flags(). Реализуйте insertRows() с вызовами beginInsertRows()/endInsertRows(). Для делегата унаследуйте QStyledItemDelegate и перегрузите метод paint(), проверяя значение балла через index.data().
💬 Присоединяйтесь к обсуждению!
Разобрались с архитектурой «модель-представление»? Удалось создать свою первую модель на основе QAbstractListModel или QAbstractTableModel?
Возникли вопросы о том, когда использовать промежуточные модели, как правильно работать с ролями элементов или как реализовать кастомный делегат?
Поделитесь своим опытом: Какие задачи вы решали с помощью модель-представление? Сталкивались ли вы с проблемами при создании собственных моделей? Какие паттерны оказались наиболее полезными в вашей практике?
Ваши вопросы и находки помогут другим читателям лучше освоить эту мощную технологию Qt!