Модель-представление в Qt

модель представления qt

Мы уже затрагивали тему паттернов ООП. Более того, в заметке Паттерн MVC на примере Cапера мы разработали полноценную игру на основе одного из них. Но пришло время заняться чем-то более серьезным.

Табличные представления весьма распространены в приложениях, которые работают с данными. Например, в заметке QProgressBar в таблице QTableView мы коротко рассмотрели работу с делегатами. Но это была в большей степени работа на стороне представления. Сейчас же мы сосредоточимся на применении моделей.

Чем мы займемся?

Как обычно, не будем ничего усложнять. Qt, как и другие продуманные библиотеки, хорош тем, что изучив один пример на какую-то тему, вы можете применять эти знания многократно. Вот и сейчас мы рассмотрим работу с моделями и представлениями в Qt на примере таблицы. Однако вы сможете использовать те же приемы и для других комбинаций модели/представления (например, для списков и деревьев).

Давайте реализуем приложение, которое позволяет добавлять и удалять произвольные ФИО. Для удаления записей ФИО мы предусмотрим в таблице дополнительный столбец. Этот столбец будет принимать логические значения, то есть True или False. Будем считать, что запись отмечена для удаления, если в этом столбце стоит значение True. Само же удаление будет происходить после нажатия соответствующий кнопки подтверждения.

Ничего особо сложного, поэтому приступим.

Объявление виджета приложения

Начнем с объявления главного виджета:

Объявление Модели

Думаю, что вы обратили внимание на объявление класса PersonsModel. Это и будет наша модель, которую мы подключим к представлению QTableView. Посмотрим, что она из себя представляет:

Модель PersonsModel реализует класс QAbstractTableModel. При этом мы переопределяем некоторые виртуальные функции-члены, с которыми разберемся чуть позже. Мы же добавили только appendPerson() и removeSelected(). Их назначение понять не сложно. Первый предназначен для добавления новой строки ФИО в таблицу, а второй для удаления строк, которые были отмечены.

Кроме того, мы определили перечисление Column для индексации столбцов нашей таблицы. В первом столбце мы поместим фамилию, во втором — имя, в третьем — отчество, а последний оставим для отметок на удаление.

Данные модели мы будем хранить в списке хэш-карт. Ключом хэш-карты будет индекс столбца. И к нему мы будем привязывать данные произвольного типа QVariant.

Реализация главного виджета

Но сначала закончим с виджетом ModelViewDemoWidget:

Здесь все весьма предсказуемо. Однако обратите внимание на строку:

В этом месте мы создаем нашу модель и связываем ее с представлением.

Реализация Модели

Теперь переходим к реализации модели. А начнем мы с нескольких базовых функций, которые задают структуру таблицы:

В rowCount() мы просто возвращаем количество элементов в списке, а в columnCount() — заранее заготовленное значение из перечисления Columns (такой прием удобен тем, что если нам придется добавлять в таблицу столбцы, то оставив LAST на последнем месте перечисления, его значение все равно будет равно количеству столбцов, но уже новому).

Функция headerData() должна вернуть заголовок, который будет отображаться в шапке таблицы сверху для каждого столбца или слева для каждой строки. Она принимает три параметра: номер секции (то есть столбца или строки), ориентацию (вертикально или горизонтально) и роль. В данном случае нас интересует лишь роль для отображения (есть и другие), поэтому мы сразу делаем соответствующую проверку и возвращаем пустое значение QVariant, если что-то не так. Далее мы проверяем ориентацию. Если она была вертикальной, то мы возвращаем номер секции, чтобы отображался номер строки. А для горизонтальной ориентации мы возвращаем текстовые значения полей с помощью оператора switch по номеру секции.

Если мы запустим приложение сейчас, оставив для оставшихся функций реализации по умолчанию, то должны увидеть следующее:

Конечно, все сделано без изысков, но мы успешно подключили модель к представлению. Первый шаг сделан. Теперь реализуем добавление записей:

Функция-член data() должна вернуть данные ячейки. Для этого ей передаются индекс QModelIndex и роль. Индекс модели содержит довольно много информации о ячейке, на которую указывает, но нам будет достаточно узнать ряд и столбец. Однако сначала мы проверяем, что все переданные параметры в порядке. Мы смотрим, чтобы индекс был действительным, ряд не превосходил число строк в таблице, а роль предназначалась либо для отображения, либо для редактирования. Для краткости я опустил проверку номера столбца, поскольку при запросе по хэш-карте это не вызовет серьезных проблем, но сюда можно было добавить и ее. Вернуть же значение по позиции ячейки в таблице совсем просто:

Функцию flags() мы переопределили для того, чтобы представление знало наши намерения относительно ожидаемого поведения ячеек. Мы могли бы провести тонкую настройку параметров для каждой ячейки, но в большинстве случаев это не имеет смысла. Для нашего приложения мы лишь добавили флаг ItemIsEditable для последнего столбца SELECTION, чтобы обеспечить возможность выбора строк для удаления.

В нашей функции appendPerson() мы просто добавляем в список новую запись с ФИО. Однако обратите внимание на фрагмент:

Чтобы представление знало о том, что мы вставили новые строки, необходимо сообщить ему об этом явно. Это делается с помощью комбинации вызовов beginInsertRows() и endInsertRows(). Причем, в первом из них мы должны указать диапазон строк, в котором произойдут изменения. Но поскольку мы вставляем всего одну запись за раз, то нам достаточно передать пустой диапазон из одного значения.

Этого кода уже хватит, чтобы заполнить таблицу.

Возможно, вы ожидали, что в последнем столбце будут отображены красивые чек-боксы, а не текстовые надписи. Сделать это не так сложно, но изменения потребуются на стороне представления, ведь внешний вид от модели никак не зависит по определению. Еще хорошо смотрится чек-бокс для всего столбца в шапке. Его тоже можно без особых проблем туда запихнуть, но если мы этим и займемся, то в следующий раз.

Но если вы попробуете запустить это приложение, то заметите, что значения в последнем столбце изменить можно, но они не сбрасываются обратно в значение False. Что не так? — Все дело в том, что мы еще не реализовали функцию-член setData(), которая отвечает за присвоение значений ячейкам, полученным из представления. А делается это очень схожим образом с тем, как мы это реализовали ранее для data():

Сначала мы проводим некоторые проверки входных параметров. Они практически полностью совпадают с тем, что было в data(). Затем мы выполняем присвоение значения для элемента списка m_persons и вызываем сигнал dataChanged(), который сообщает представлению о том, что что-то изменилось. Чтобы оптимизировать этот процесс и не перерисовывать все представление, dataChanged() принимает два индекса для указания области таблицы, ограниченной двумя ячейками в левом верхнем углу и правом нижнем. Однако поскольку мы меняем всего одну ячейку, то ни о каких диапазонах нам думать не приходится.

Ура! Теперь мы можем менять значения для ячеек последнего столбца и все сохраняется.

Теперь осталось добавить лишь функцию удаления. Так давайте сделаем это:

В removeSelected() мы проходим по каждому элементу списка в цикле и проверяем состояние поля SELECTION. Если оно равно True, то текущий элемент удаляется. Следует заметить, что как и в случае добавления строк, нам нужно явно сообщать об изменениях в представление. Для этого мы используем beginRemoveRows() и endRemoveRows().

Посмотрим, что произойдет, если сейчас в приложении нажать Удалить.

Отлично. Отмеченная запись ФИО была удалена, а остальные никуда не делись. Программа работает. Однако в реальном приложении не лишним было бы реализовать хранение данных модели в БД. Кроме того, не помешало бы предусмотреть возможность экспорта и импорта данных в файлы, чтобы можно было передать их кому-нибудь.

Заключение

Вот мы и рассмотрели основы работы с концепцией модель-представление в Qt на примере таблицы. Полученных знаний хватит вам для реализации достаточно сложных приложений с динамичным изменением структуры и данных таблиц, а также схожих с ними виджетов Qt.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий