Qt-шные прокси-модели и их друзья

от автора

Разрабатывая приложения на Qt или PyQt/PySide2 вы рано или поздно столкнетесь с необходимостью создания собственной реализации QAbstractItemModel и/или QAbstractProxyModel. Я хочу рассказать про одну конкретную проблему, с которой я столкнулся, разрабатывая прокси-модель с возможностью группировки объектов дерева. .

TL;DR

Я реализовал прокси-модель на основе QAbstractProxyModel, при попытке редактирования элементов прокси-модели я получил ошибку edit: editing failed , не нашел решения в интернетах, но разобрался что эту ошибку можно исправить, если реализовать у прокси-модели метода buddy.

Теперь длинная история

Стоит сказать, в чем может быть необходимость разработки своей модели на основе QAbstractItemModel или QAbstractProxyModel, и почему не хватает стандартных реализаций.

Ответ очень прост — казалось бы — когда возможностей готовых реализаций не хватает. Qt из коробки предоставляет не так много реализованных моделей. И стандартный совет из документации — возьмите готовую модель и доработайте ее поведение под ваши нужды. Но есть задачи, для которых стандартные модели не подходят никак. Например, имеется модель QSortFilterProxyModel (на основе QAbstractProxyModel), которая может применяться для реализации поиска объектов в дереве (filter в названии про это), но реализовать инкрементальный поиск, чтобы значительно уменьшить время поиска, с помощью этой модели у вас не получится. Или, другой пример, вам необходимо реализовать группировку объектов на основе атрибутов или дать возможность пользователю произвольно группировать объекты исходного дерева. В этих случаях вам придется реализовать свою прокси-модель на основе QAbstractProxyModel.

Как ее правильно реализовать — об этом можно поговорить отдельно. Сегодня я хотел рассказать про ошибку, с которой вы можете столкнуться, если будете реализовывать прокси-модель из последнего примера:

edit: editing failed

Да вот такая лаконичная ошибка из недр Qt.

Возникает она при попытке редактирования объектов, например, в QTreeView (я далее буду называть ее вью, потому что ‘представление‘ на мой взгляд слишком официально) через F2 или другие EditTriggers. Причем ваш метод setData в этом случае даже не вызывается.

Распространенные советы из интернета советуют (простите за тавтологию) прописать правильные флаги для ваших объектов. В нашем случае мы должны так реализовать метод flags у прокси-модели, чтобы он возвращал корректные QModelIndex (далее буду называть их индексами для простоты) — для них должны быть установлены ItemIsEnabled и ItemIsEditable. Но вам это может не помочь. Даже так — вы заметите, что для некоторых индексов setData вызывается и редактирование работает без проблем, а для некоторых нет.

Дальнейшие поиски в сети не дают вразумительных результатов, и вы пробуете почитать исходники Qt — там все просто и прямолинейно. Одна из проверок связана с тем, что ваш индекс в корректном состоянии (IsValid == true), другая что вью уже не находится в режиме редактирования и еще несколько других, которые вы, например, снаружи проверить не сможете. И вы проверяете свой индекс — он корректный, с нужными флагами, проверяете вью — оно не в режиме редактирования и проверяется что-нибудь еще, потому что что-то же надо делать. Но результата нет — редактирование не работает.

Скрытый текст

В некоторых интернетах советуют еще установить setCurrentIndex перед вызовом метода edit у QAbstractItemView и якобы кому-то это помогало — но этот совет о другом и к нашей проблему не относится.

Потом вы судорожно начинаете перечитывать документацию на QAbstractProxyModel — без результата, потом точно также читать документацию на QAbstractItemModel и натыкаетесь на описание метода buddy:

Returns a model index for the buddy of the item represented by index. When the user wants to edit an item, the view will call this function to check whether another item in the model should be edited instead. Then, the view will construct a delegate using the model index returned by the buddy item.

The default implementation of this function has each item as its own buddy.

И вспоминаете что видели использование этого метода, когда изучали исходный код QAbstractItemView.

Это наводит вас на мысли и вы возвращаетесь к документации на QAbstractProxyModel и видите там следующее (красочное ничего):

Reimplements: QAbstractItemModel::buddy(const QModelIndex &index) const.

Приходится снова браться за исходный код Qt и искать как же реализован данный метод — оказывается, что он в своей реализации использует пару mapToSource и mapFromSource для получения бадди-индекса. И тут то у вас в голове все складывается:
Для прокси-индексов, у которых есть индекс из исходной модели редактирование будет работать, потому что mapToSource вернет корректный индекс из исходной модели, с нужными флагами, а mapFromSource вернет для него корректный прокси-индекс. А для прокси-индексов, для которых в исходной модели индексы отсутствуют (а мы помним, что мы хотели сделать пользовательские группы) — редактирование работать не будет, потому что mapToSource в этом случае может вернуть только пустой индекс, у которого IsValid == false.

И добавив простейшую реализацию метода buddy (которая, например, возвращает полученный прокси-индекс) вы получите работающее редактирование объектов в вашем вью.

Эта банальная история говорит нам в очередной раз — что нужно внимательно читать документацию и не только на то, с чем вы работаете в данный момент, но и немножко рядом. А еще она говорит, что нужно чаще думать головой.

Резюме

Если вы создаете прокси-модель на основе QAbstractProxyModel, создаете в этой модели объекты, которые отсутствуют в исходной модели и хотите, чтобы они были редактируемы в вашем вью, то необходимо (помимо всего прочего):

  1. Реализовать QAbstractProxyModel::flags для этих объектов, чтобы он возвращал флаги ItemIsEnabled и ItemIsEditable.

  2. Реализовать QAbstractProxyModel::setData, который будет обрабатывать запрос на изменение объектов.

  3. Реализовать QAbstractProxyModel::buddy, чтобы он возвращал корректный индекс, для объектов(индексов), которые отсутствуют в исходной модели.

Спасибо за уделенное внимание.


ссылка на оригинал статьи https://habr.com/ru/articles/851850/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *