Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > QGraphicsScene: рекреат


Автор: Любитель 12.12.2006, 18:13
Упрощенно говоря имеем в классе некоторого виджета графикс-вью (graphicsView_) и прайват-функцию createScene, которая заполняет сцену всякой ерундой. В числе прочего там есть кнопочка, щелчок по которой должен вызщвать пересоздание сцены. Кнопка (отдельный класс график-итемов) обрабатывает нажатие/отпуск мыхи и т. д.

Вариант 1: просто создаём новую сцену и устанавливаем её во вью. Работает. Но память всех сцен освобождается только после разрушение рассматриваемого виджета (он выступает парентом для сцен). Впрочем сие логично, но затратно.

Вариант 2: что-то такое:
Код

QGraphicsScene* oldScene = graphicsView_->scene();
QGraphicsScene* newScene = new QGraphicsScene(this);
graphicsView_->setScene(newScene);
oldScene->SetParent(0);
delete oldScene;
createScene(); // берёт текущую сцену

Сие не работает. Точнее, работает, если рекреат сцены инициируется внешним способом, но не кликом по кнопке сцены. После mouseReleasedEvent куте инициирует хаувер-события (точно не смотрел что именно). Причём даже когда на сцене нет ни одного итема, аксептющего хауверы (по видимому этот аксепт работает как обычный ивент-филтер). Так как сцены к тому моменту не существует, то this сцены получается не валидный. В итоге получаем вылет.

Вариант 3: типа такого:
Код

// делитим все итемы сцены
for (;;)
{
   QList<QGraphicsItem*> items = graphicsView_->scene()->items();
   if (items.isEmpty())
      break;
   graphicsView_->scene()->removeItem(items.first());
   delete items.first();
}

createScene();

Сие работает. Но:
1. Не красиво выглядит. Почему я не нашёл у сцены методов вроде rest, removeAllItems или clear (или ночью глаза плохо видят в асистенете?) ???
2. Память (по таск-менеджеру) после рекреата сцены все равно прибавляется. На 4-5 рекреатах - ничего, а потом потихоньку начинает тормозить (память идёт под 100 мегабайт). Правда, там ещё тупанул в одном месте (дома исправлю) - пиксмэпы на кнопках создаются каждый раз заново (на сцене много кнопок с одной картинкой). Пожалуй, надо статик-мембер завести. Насколько я помню, копирование QPixmap ведёт к shared-доступу. Но всё же к увеличению используемой памяти это не должно вести (кол-во кнопок старой и новой сцены всегда почти совпадает).

Как лучше всего рекреатить сцену? Ваши мнения?

Автор: JackYF 12.12.2006, 18:20
Вариант 3, завернутый в отдельную функцию и сделанный while' ом:
Код

QList<QGraphicsItem*> items = graphicsView_->scene()->items();
while ( ! items.isEmpty() )
{
   graphicsView_->scene()->removeItem(items.first());
   delete items.first();
}
createScene();

Автор: Любитель 14.12.2006, 18:07
JackYF, сие не сработает - ты получишь вечный цикл (ты не удаляешь элемент из списка).

Что касается отдельной функции - это и есть recreateScene().

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

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

Автор: zabivator 14.12.2006, 18:28
Любитель, вот за это плюс поставить можно! Вдохновил!

Автор: Любитель 14.12.2006, 18:35
Начинаем флуд.

Честно - не понял чем (хотя б в каком посте).

Автор: zabivator 14.12.2006, 18:37
Любитель,  QGraphicsScene - видел столько раз эту шнягу в ассистансе. Но когда сейчас увидел код в его использованием... прозрел, что это ведь на самом деле очень-очень вкусно!

Автор: Любитель 15.12.2006, 11:13
Да, кстати, вариант с непереприсваиванием списка итемов (при рекреате) не прокатит и в модифицированном варианте. Когда мы будем удалять первый элемент из списка (для списка это достаточно эффективно) или итерировать по нему (вместо условия isEmpty). Точнее в исключительных случаях это сработает, но в большинстве (в т. ч. у меня) - нет.

Почему? Объясняю. В куте кругом используется простая концепция парентов, реализованная на уровне QObject. Деструктор парента удаляет все свои child-итема. QGRaphicsItem не является наследником QObject, но там реализуется подобная вещь. У итема есть сцена к которой он относится и есть парентовый график-итем. Последний может быть нулевым указателем. В этом случае итем делитит сцена. Метод items вернёт все итемы. В итоге мы можем долбануть родителя, его дети тоже подохнут, но указатели на них (уже невалидные) останутся. Мы будем долбать и их. Сие вызовет вылет. Посему останавливаюсь на прошлом варианте.

Правда, что тупанул - явно удалял элемент из сцены. С этим прекрасно справляется его деструктор.

Если не нравиться выход посередине. Можно просто искуственно размножить начало цикла (благо оно в одну строчку):
Код

QList<QGraphicsItem*> items = graphicsView_->scene()->items();
while (!items.isEmpty())
{
   delete items.first();
   items = graphicsView_->scene()->items();
}


Пожалуй этот вариант даже эстетичней. Даже в чистом виде потерь между двойным написанием кода почти нет. 90% компилер в состоянии скомпилировать сие с фактическим выходом посередине.

Автор: Любитель 15.12.2006, 11:56
Вопрос решён.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)