Понимание архитектуры модели представления Qt: когда создавать и как очищать индексы в реализации QAbstractItemModel?

В настоящее время я переношу свой проект с QTreeWidget на QtreeView, и у меня много проблем, вызванных плохим пониманием дизайна представления модели Qt. Пока я не смог найти ответы даже в примерах Qt.

Я реализовал свой QAbstractItemModel. Я возвращаю строки для просмотра в методе с QTreeView по data. Теперь базовые данные будут изменяться во время выполнения. Чтобы справиться с этим, моя модель подписана на уведомление, которое делает emit dataChanged(index(0,0), index(rowCount() - 1, LastColumn));. Вопрос: как создавать и очищать QModelIndex объекты? Один из примеров Qt переопределяет метод index, поэтому я сделал то же самое:

QModelIndex CFileListModel::index(int row, int column, const QModelIndex &/*parent*/) const
{
    QModelIndex index = createIndex(row, column);
    return index;
}

Однако в этом примере данные статичны, а в моем случае они меняются во время выполнения. Верна ли моя реализация index? Что, если index вызывается более одного раза для одних и тех же координат? Нужно ли как-то очищать старые индексы перед отправкой dataChanged?


person Violet Giraffe    schedule 22.09.2013    source источник


Ответы (1)


Ваш вопрос об «удалении» индексов не имеет смысла в свете семантики С++. У вас просто нет возможности уничтожить объект, который вы вернули по значению внутри функции — по крайней мере, не прибегая к целенаправленным грязным хакам. Так что давайте забудем об этом.

Сигнал dataChanged и время жизни индексов на самом деле не связаны. Когда ваш метод index() возвращает индекс, вы не тот, кто может его «удалить»; тот, кто вызвал метод index() вашей модели, несет ответственность за уничтожение индекса. Неважно, что индекс, который вы выдаете, в любом случае не размещается в свободном хранилище, поэтому понятие удаления вообще не применяется.

QModelIndex - это то, что написано на коробке: индекс. Когда дело доходит до того, как его можно использовать, он очень похож на итератор C++. Он поставляется с несколькими предостережениями, которые предостерегает зеркальный итератор:

  1. Он должен быть создан моделью с помощью фабричного метода index(). Внутри вы используете фабрику createIndex(), чтобы создать ее для себя в модели. Подумайте о возвращающих итератор методах контейнеров C++ do (begin(), end() и т. д.).

  2. Его нужно использовать сразу, а затем выбросить. Он не останется действительным, если вы внесете изменения в модель. То же самое общее ограничение применяется к итераторам контейнеров C++.

  3. Если вам нужно сохранить индекс модели с течением времени, используйте файл QPersistentModelIndex. Стандартная библиотека C++ не предлагает этого.

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

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

Если вы выдали индекс, затем сгенерируйте dataChanged, и любой метод вашей модели будет вызван с этим старым индексом, вы можете сбой, утверждение, прерывание, что угодно.

Давайте также проясним, как вы используете dataChanged: вы должны испускать его всякий раз, когда элемент данных в заданном индексе изменяется. Вы должны быть как можно более конкретными: не вообще не рекомендуется просто сообщать о своих взглядах, что все изменилось, если на самом деле это не так. Если один индекс изменился, подайте сигнал с topLeft и bottomRight, установленными на один и тот же индекс. Если изменилась небольшая прямоугольная область, выделите углы этого прямоугольника. Если были изменены несколько несвязанных элементов, которые находятся слишком далеко друг от друга, чтобы их можно было осмысленно объединить в небольшой ограничивающий прямоугольник индекса, вы должны указать такие изменения отдельно для каждого измененного элемента.

Вы обязательно должны использовать modeltest, чтобы убедиться, что ваша модель ведет себя нормально.

Это можно сделать, добавив в проект modeltest.cpp и modeltest.h и создав экземпляр тестера для каждого экземпляра модели. Вы можете сделать это прямо в вашей модели:

#include "modeltest.h"

MyModel(QObject * parent) : ... {
   new ModelTest(this, parent);
   ...
}

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

Модели, предоставляющие интерфейсы для структур данных с изменяемым размером, могут предоставлять реализации insertRows(), removeRows(), insertColumns() и removeColumns(). При реализации этих функций важно уведомлять любые подключенные представления об изменениях размеров модели как до, так и после их возникновения:

  • Реализация insertRows() должна вызывать beginInsertRows() перед вставкой новых строк в структуру данных и endInsertRows() сразу после этого.
  • Реализация insertColumns() должна вызывать beginInsertColumns() перед вставкой новых столбцов в структуру данных и endInsertColumns() сразу после этого.
  • Реализация removeRows() должна вызывать beginRemoveRows() до того, как строки будут удалены из структуры данных, и endRemoveRows() сразу после этого.
  • Реализация removeColumns() должна вызывать beginRemoveColumns() до того, как столбцы будут удалены из структуры данных, и endRemoveColumns() сразу после этого.

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

person Kuba hasn't forgotten Monica    schedule 22.09.2013
comment
Кроме того, вы пишете модель, почему вы спрашиваете кого-то еще, хранит ли ваша модель индексы? Если вы их не храните, вы их не храните. - person Kuba hasn't forgotten Monica; 22.09.2013
comment
Я в порядке с основами C++. Тот факт, что ресурс возвращается по значению, не означает, что мне не нужно его release или что-то в этом роде. Или вызовите reset для модели, когда ее старое состояние станет недействительным. Всегда лучше перепроверить, чем потом разбираться с утечками памяти, а в документации нет ясности по этому вопросу. Поскольку я наследую базу QAbstractItemModel, я понятия не имею (за исключением проверки ее источника), хранит ли она индексы или нет. - person Violet Giraffe; 22.09.2013
comment
Как ты вообще используешь modeltest? Мой проект отказывается компилироваться, как только я добавляю QT += testlib - person Violet Giraffe; 22.09.2013
comment
Класс QModelIndex очень прост. Если вы сомневаетесь, вы всегда можете обратиться к источникам. Это четырехчленная структура только для чтения с частным конструктором. - person Kuba hasn't forgotten Monica; 22.09.2013
comment
The fact that a resource is returned by value doesn't mean I don't have to release it or whatever. Вы слишком много думаете и придумываете несуществующие проблемы. QModelIndex поставляется с идиоматической закрытой фабрикой внутри QAbstractItemModel. Это единственный способ предотвратить создание пользователями моделей собственных недействительных индексов. Это обычная схема. То, что есть фабрика, не означает, что есть менеджмент. В абстрактной модели нет метода reset, а сброс модели не имеет ничего общего с непостоянными индексами. Сброс модели — это как новый диск в приводе. - person Kuba hasn't forgotten Monica; 23.09.2013