Когда испускать dataChanged из QAbstractItemModel

В Qt у меня есть подкласс модели QAbstractItemModel - это дерево, отображаемое в QTreeView.

Модель поддерживает различные формы изменений, и все они работают нормально. Два релевантных:

1) Некоторые данные в небольшом количестве связанных строк изменяются

2) Изменение визуализации означает, что у большинства строк должно измениться их форматирование, в частности у них изменена подсветка фона. Их DisplayRole данные не меняются.

Текущий дизайн работает с обоими одинаково: для каждой строки, в которой есть какие-либо изменения, модель выдает dataChanged(start_of_row_index,end_of_row_index). Я испускаю сигнал для обеих родительских строк, которые изменились, и для всех их дочерних строк, которые изменились.

Однако это плохо работает в случае 2, поскольку модель становится большой: испускается очень большое количество сигналов dataChanged.

Я изменил код так, что в случае 2 модель выдает dataChanged только для (одной) строки, которая является родительской для всего дерева.

Это по-прежнему работает правильно, но не соответствует моему пониманию обязанностей модели. Но я подозреваю, что могу ошибаться.

Возможно, я неправильно понимаю сигнал dataChanged? Действительно ли это приводит к тому, что представление обновляет все дочерние элементы, а также указанный диапазон? Или я могу избежать испускания dataChanged, когда изменяется не DisplayRole?

Отредактировано с моим прогрессом до сих пор

Как указывает Ян, в случае 2 я должен выдать dataChanged либо для большинства, либо для всех строк.

Мой код изначально делал это, выдавая dataChanged для каждой измененной строки, но это слишком дорого — представление слишком долго обрабатывает все эти сигналы.

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

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

Из-за причуды класса QTreeView возможно (хотя и неправильно согласно спецификации) испускать только один dataChanged(tl,br) до tl != br. У меня это работало, и оно прошло наше тестирование, но заставило меня нервничать.

На данный момент я остановился на версии, которая проходит по дереву и выдает один dataChanged(tl,br) для каждого родителя (с tl,br, охватывающими всех дочерних элементов этого родителя). Это соответствует протоколу модель/представление и для наших моделей обычно уменьшает количество сигналов примерно в 10 раз.

Однако он не кажется идеальным. Любые другие предложения кто-нибудь?


person strubbly    schedule 15.05.2013    source источник
comment
Я прочитал этот поток, который предполагает, что все видимые ячейки обновляются при отправке моего dataChanged. ‹qt-project.org/forums/viewthread/14723› Что частично объясняет поведение Я вижу. Но я все еще в замешательстве о том, что я ДОЛЖЕН делать. Возможно, даже в случае 1 мне нужно отправить только один dataChanged?   -  person strubbly    schedule 15.05.2013


Ответы (1)


Ожидается, что вы будете сообщать своим представлениям об изменении каких-либо данных. Это «сообщение» может происходить несколькими способами; эмиссия dataChanged является наиболее распространенной, когда структура индексов не изменилась; другие — «серьезные», такие как modelReset или layoutChanged. По совпадению, некоторые из представлений Qt могут получать изменения даже без dataChanged, например. наведение курсора мыши, но вы не должны полагаться на это. Это деталь реализации, которая может быть изменена.

Чтобы ответить на последний вопрос вашего вопроса, да, dataChanged должен генерироваться всякий раз, когда какие-либо данные, возвращаемые из QAIM::data(), изменяются, даже если это «просто» какая-то другая роль, чем Qt::DisplayRole.

Вы ссылаетесь на проблемы с производительностью. Каковы точные цифры — вы действительно получаете какое-либо измеримое замедление или вы просто преждевременно беспокоитесь, что это может стать проблемой позже? Знаете ли вы, что вы можете использовать оба аргумента dataChanged, чтобы сигнализировать об изменении большой матрицы индексов?

ИЗМЕНИТЬ:

Еще пара вещей, чтобы попробовать:

  • Убедитесь, что ваше представление не запрашивает дополнительные данные. Например, если вы не установите uniformRowHeights для QTreeView (IIRC), представление должно будет выполнять вызовы O (n) для каждого сигнала dataChanged, что приводит к сложности O (n ^ 2). Плохо.

  • Если вы действительно уверены, что это невозможно, вы можете избежать этого, объединив layoutAboutToBeChanged, updatePersistentIndexes и layoutChanged. Поскольку вы на самом деле не меняете структуру своих индексов, это может быть довольно дешево. Тем не менее, возможность оптимизации в предыдущем пункте по-прежнему стоит того.

person Jan Kundrát    schedule 15.05.2013
comment
Да, у меня возникают проблемы: у меня более 400 000 строк, и для обновления требуется минимум 5–10 секунд. Конечно, на самом деле видимых гораздо меньше. Это означает, что если я испускаю какие-либо dataChanged(a,b) с помощью a != b, то на самом деле представление корректно обновляется намного быстрее. Но это не соответствует спецификации - я использую то, как на самом деле работает QTreeView. - person strubbly; 15.05.2013
comment
В исходном дизайне я испускал один сигнал на (измененную) строку. В текущей рабочей (но несовместимой) версии я испускаю один сигнал. Существует промежуточная версия, в которой я испускаю один сигнал на каждого родителя, что дает мне что-то среднее (возможно, 40 000 сигналов вместо 400 000) — не пробовал. - person strubbly; 15.05.2013
comment
ОК переключился на версию, которая излучает для каждого блока дочерних элементов. Это работает нормально (не очень хорошо) и, кажется, соответствует протоколу. Я описал это в редактировании к моему исходному сообщению. - person strubbly; 15.05.2013
comment
Добавлено еще пару способов потимизации модели. - person Jan Kundrát; 15.05.2013
comment
Да, у меня уже есть uniformRowHeights(True) спасибо. Это все еще довольно вяло. У меня возникает соблазн вернуться к изворотливой версии — в краткосрочной перспективе я буду получать меньше жалоб от пользователей. Пока следующая версия Qt не заставит все пойти не так :-( - person strubbly; 15.05.2013
comment
Если я сделаю LayoutChanged и т. д., это приведет к сбросу представления (например, забудете, какие части дерева расширены и выбраны)? Нужно ли мне обновлятьPersistentIndexes, учитывая, что структура индекса полностью не изменилась? Может, мне просто попробовать... - person strubbly; 15.05.2013
comment
@strubbly Не уверен, почему вы приняли ответ, кажется, это все еще открытая проблема? Или я неправильно читаю? Вы когда-нибудь находили нокаутирующий удар для решения своей проблемы? - person eric; 21.07.2014
comment
@neuronet Я принял ответ, потому что он правильно описывает, когда я должен выдать dataChanged, что было вопросом, хотя мне не нравится ответ. Я оставил свой код, используя версию, которая испускает один сигнал (я вижу в коде QTreeVièw, что это работает), и задокументировал мое несоответствие в моем коде. Не идеал, но работает хорошо. - person strubbly; 25.01.2015