Странное поведение QTreeWidgetItem::removeChild |
Здравствуйте, гость ( Вход | Регистрация )
Странное поведение QTreeWidgetItem::removeChild |
chereppiter |
18.12.2012, 10:56
Сообщение
#1
|
Студент Группа: Участник Сообщений: 30 Регистрация: 12.11.2012 Пользователь №: 3595 Спасибо сказали: 0 раз(а) Репутация: 0 |
Есть функция построения дерева (QTreeWidget), в которой для каждого элемента верхнего уровня рекурсивно вызывается функция добавления отпрысков:
void addChilds(QTreeWidgetItem* item, int id) По возвращению из рекурсивной addChilds, если у текущего чаилда детей нет, то он удаляется. Ситуация такая: на одной из итераций цикла к item был добавлен child (и не был удалён, т.к. у него тоже есть дети). На одной из последующих итераций создаётся child, для которого потом childCount оказывается 0, поэтому он удаляется (item->removeChild(child)). При этом почему-то вместе с этим отпрыском удаляется и тот, который был добавлен на одной из предыдущих итераций. Проблема решается исключением из вышеприведённого кода строчки: item->removeChild(child); Т.е. если удаление выглядит так: if (!child->childCount()) { delete child;} , то всё работает нормально. Кто-нибудь знает причину этого загадочного явления? |
|
|
Алексей1153 |
18.12.2012, 23:54
Сообщение
#2
|
фрилансер Группа: Участник Сообщений: 2941 Регистрация: 19.6.2010 Из: Обливион Пользователь №: 1822 Спасибо сказали: 215 раз(а) Репутация: 34 |
в поставленном вопросе много неизвестного:
1) чем логически являются аргументы QTreeWidgetItem* item, int id - из их нзвания этого непонятно совершенно. Также название функции добавляет сумятицы (предположил бы я, что аргументы - это указатель на новый child и его id, но по названию функции это уже целая ветка. А id зачем - оно уже должно быть в теле item) 2) где и как описан объект nodes, что ты с ним делаешь раньше - ? 3) зачем добавлять дитёнка в дерево, чтобы узнать, что он пуст и потом удалить (ведь можно заранее проверить его на пустоту и сразу обойти) ? Я бы применил std::map для хранения структуры дерева, а также и указателей на созданные элементы. А сам контрол заполнял бы при необходимости, а потом полностью очищал (чтобы он сам не удалил элементы в деструкторе) |
|
|
chereppiter |
19.12.2012, 11:00
Сообщение
#3
|
Студент Группа: Участник Сообщений: 30 Регистрация: 12.11.2012 Пользователь №: 3595 Спасибо сказали: 0 раз(а) Репутация: 0 |
1) Чтобы было более понятно, приведу полный текст функций, строящих дерево:
void SelCameraDialog::buildTree() { tree->clear();} void SelCameraDialog::addChilds(QTreeWidgetItem* item, int id) { QTreeWidgetItem* child; } В buildTree в дерево добавляются корневые узлы, не имеющие родителя (т.е. у которых parentid==0). Далее вызывается метод addChilds(item, id) для рекурсивного добавления дочерних элементов к узлу item. id в теле item нету, т.к. item содержит только название узла. У узла может быть 2 типа дочерних элементов: другой узел или камера. И то, и то - по сути просто структуры, имеющие в качестве основных полей идентификатор (id), идентификатор родительского узла (parentid) и название (name). Отличие только в том, что узел может иметь детей, а камера - нет. 2) nodes - это стандартный вектор структур: struct NodeParams { int id; }; Заполняется заранее инфой, распарсенной из xml-ки. cameras - аналогичный вектор. 3) Ребёнка в любом случае приходится сначала создать, чтобы было к чему подцеплять потомков, если таковые есть. Соответственно, если потомков нет, его приходится удалять. К дереву его, конечно, можно подцеплять только в том случае, если потомки есть, но это в данном случае не принципиально. На самом деле интересен сам факт такого странного поведения removeChild. Что вообще по идее делает этот метод? Мне кажется, что он должен просто удалять указатель на объект QTreeWidgetItem из списка дочерних элементов. А тут получается, что я создал 2 разных объекта QTreeWidgetItem, прицепил указатели на них к родительскому QTreeWidgetItem, потом вызовом removeChild удалил указатель на один из них из списка дочерних элементов родительского QTreeWidgetItem, после чего этот список оказался пуст, хотя должен был содержать 1 элемент. Выглядит как-то нелогично Выше приведённый код работает правильно, неправильно работал такой фрагмент addChilds: if (nodes[i].parentid == id) { child = new QTreeWidgetItem(item); } |
|
|
Алексей1153 |
19.12.2012, 14:37
Сообщение
#4
|
фрилансер Группа: Участник Сообщений: 2941 Регистрация: 19.6.2010 Из: Обливион Пользователь №: 1822 Спасибо сказали: 215 раз(а) Репутация: 34 |
chereppiter, что делает этот метод, можно прочитать в доке
Цитата void QTreeWidgetItem::removeChild ( QTreeWidgetItem * child ) Removes the given item indicated by child. The removed item will not be deleted. метод работает так, как ему и положено, ошибка где-то у тебя. Неправилен сам подход к дереву, поэтому появляется дополнительная возможность ошибки - а где она, это без отладчика так сразу я не вижу, конечно. Сделай минимального размера дерева, где ошибка появляется , затем отладчиком прошагай рекурсию, обращая внимания на адреса элементов и момент, когда удалился не тот элемент, который ожидалось Ну и про правильный подход:
пример создания элементов дерева (легко переделать в рекурсивный вариант)
тут мы контролируем всю подноготную жизни элементов , всегда их все можем быстро найти и посмотреть. По указателю на потомка всегда найдём его родителя (m_parent) , а по мапе - всю ветку элемента. При удалении ветки следует рекурсивно пройтись по её потомкам и 1) вариант первый - удалить их из мапы (но не из кучи, так как это сделает сам QTreeWidgetItem) , затем delete корень ветки 2) вариант второй - удалить их из мапы , из всех родительских элементов и из кучи , затем delete корень ветки (первый вариант в нашем случае красивее) показать наше дерево в QTreeWidget - метод void QTreeWidget::insertTopLevelItem ( int index, QTreeWidgetItem * item ) , куда надо передать корень ну и не забывать отключать его от QTreeWidget , чтобы QTreeWidget не удалил всё сам в деструкторе, когда это нужно Попробуй Сообщение отредактировал Алексей1153 - 19.12.2012, 16:12 |
|
|
chereppiter |
19.12.2012, 17:55
Сообщение
#5
|
Студент Группа: Участник Сообщений: 30 Регистрация: 12.11.2012 Пользователь №: 3595 Спасибо сказали: 0 раз(а) Репутация: 0 |
Не понимаю, в чём неправильность моего подхода к дереву. Строится оно вполне логично и правильно, если не использовать removeChild. Зачем мне дополнительная прослойка в виде stl-ного словаря, если мне это дерево нужно только для того, чтобы его отобразить в виджете и больше ни для чего? Моя ошибка, скорее всего, в том, что я не совсем правильно понимаю назначение метода removeChild. Я-то предположил, что он работает в соответствии со своим названием, т.е. удаляет потомка, указатель на который передан в аргументе, ан-нет! Я смотрел и в отладчике, и логи: удаляется 2 потомка с разными указателями - тот, который нужно удалить, и тот, который был добавлен когда-то ранее. Это подтверждается и тем, что при delete child всё работает как надо. Т.е. подробно по шагам: в функции addChilds у меня есть указатель на текущий элемент item, к которому ранее был добавлен потомок, что я проверяю вызовом item->childCount(), который возвращает 1. Далее создаю child = new QTreeWidgetItem(item), опять вызываю item->childCount() - получаю 2; вызываю addChilds(child, child_id), после возврата имею child->childCount() == 0, вызываю для проверки item->childCount() - всё правильно, он до сих пор равен 2-м; теперь вызываю item->removeChild(child), после чего опять проверяю item->childCount() - теперь он уже равен 0. Т.е. каким-то образом после вызова item->removeChild(child) у item удалились все потомки. При простой замене item->removeChild(child) на delete child всё начинает работать правильно! Отсюда вывод: removeChild работает не так, как я от него ожидаю. Возникает вопрос: как он работает? И я не хочу сейчас переписывать алгоритм построения дерева, потому что он меня вполне устраивает и правильно работает! Вопрос не в том, как что-то поменять в моём коде, а в том, что делает метод removeChild Понятно, что если очень интересно, можно залезть в исходники Qt и посмотреть реализацию, но я надеялся на понятный ответ от кого-нибудь, кто уже разобрался с этим вопросом
|
|
|
Алексей1153 |
20.12.2012, 6:56
Сообщение
#6
|
фрилансер Группа: Участник Сообщений: 2941 Регистрация: 19.6.2010 Из: Обливион Пользователь №: 1822 Спасибо сказали: 215 раз(а) Репутация: 34 |
chereppiter, тогда прикрепи проект (удали лишнее), чтобы я смог повторить глюк у себя, тогда я смогу в отладчике точно посмотреть, что происходит.
Не понимаю, в чём неправильность моего подхода к дереву. Строится оно вполне логично и правильно, если не использовать removeChild. Зачем мне дополнительная прослойка в виде stl-ного словаря, если мне это дерево нужно только для того, чтобы его отобразить в виджете и больше ни для чего? 1) неправильность подхода - не ООП , элементы дерева (виджета) раскиданы неизвестно где. 2) какая ещё прослойка ? Ты всё то же самое создаёшь. Только в мапе все указатели хранятся стройно и доступно 3) ошибка в том, что ты пытаешься одновременно отладить две вещи, при этом точно не знаю, какая из них не работает - то ли массив, то ли виджет. Я же предлагаю построить дерево без участия виджета (вещь остаётся одна), а потом уже разбираться с деревом-виджетом. |
|
|
chereppiter |
20.12.2012, 15:53
Сообщение
#7
|
Студент Группа: Участник Сообщений: 30 Регистрация: 12.11.2012 Пользователь №: 3595 Спасибо сказали: 0 раз(а) Репутация: 0 |
Создал отдельный маленький тестовый проект. Глючная строчка кода закомментирована.
Прикрепленные файлы
|
|
|
Алексей1153 |
21.12.2012, 0:25
Сообщение
#8
|
|
фрилансер Группа: Участник Сообщений: 2941 Регистрация: 19.6.2010 Из: Обливион Пользователь №: 1822 Спасибо сказали: 215 раз(а) Репутация: 34 |
в общем, вскрытие показало, что
При этом вызов деструктора чайлда (с помощью delete) приводит к правильному удалению правильного элемента контейнера дерева (чайлд знает родителя, поэтому тут всё корректно у них завязано) дигноз:не пользоваться removeChild на скрине три этапа: до вызова removeChild , затем до вызова delete, и после вызова. Видно, что при вызове removeChild - чайлд (0xac77f30) затёр собой нулевой элемент После вызова delete он пропал и сам При моём подходе, кстати, вызов removeChild не нужен Сообщение отредактировал Алексей1153 - 21.12.2012, 0:31 |
|
|
||
iReset |
21.12.2012, 9:13
Сообщение
#9
|
Участник Группа: Участник Сообщений: 178 Регистрация: 6.6.2012 Пользователь №: 3414 Спасибо сказали: 23 раз(а) Репутация: 2 |
Насколько я понял при беглом просмотре исходников, для модели, используемой в QTreeWidget, иногда вызывается сортировка. Например, она вызывается в функции child. Т.е. если перед пресловутым removeChild в коде добавить строчку
то модель будет пересортирована и произойдёт удаление правильного элемента.Другое дело, что removeChild в этом коде действительно не нужен, поскольку элемент всё равно далее удаляется. |
|
|
Алексей1153 |
21.12.2012, 11:10
Сообщение
#10
|
фрилансер Группа: Участник Сообщений: 2941 Регистрация: 19.6.2010 Из: Обливион Пользователь №: 1822 Спасибо сказали: 215 раз(а) Репутация: 34 |
iReset, вообще, забавная ситуация - указываем конкретный указатель, а виджет мочит другой )
|
|
|
Текстовая версия | Сейчас: 29.11.2024, 0:12 |