crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

2 страниц V   1 2 >  
Ответить в данную темуНачать новую тему
> Странное поведение QTreeWidgetItem::removeChild
chereppiter
  опции профиля:
сообщение 18.12.2012, 10:56
Сообщение #1


Студент
*

Группа: Участник
Сообщений: 30
Регистрация: 12.11.2012
Пользователь №: 3595

Спасибо сказали: 0 раз(а)




Репутация:   0  


Есть функция построения дерева (QTreeWidget), в которой для каждого элемента верхнего уровня рекурсивно вызывается функция добавления отпрысков:

void addChilds(QTreeWidgetItem* item, int id)
{
QTreeWidgetItem* child;
for (unsigned int i = 0; i < nodes.size(); ++i)
{
if (nodes[i].parentid == id)
{
child = new QTreeWidgetItem(item);
child->setText(0, QString::fromUtf8(nodes[i].name.c_str()));
addChilds(child, nodes[i].id);
if (!child->childCount())
{
item->removeChild(child);
delete child;
}
}

}
}

По возвращению из рекурсивной 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();
for (unsigned int i = 0; i < nodes.size(); ++i)
{
if (nodes[i].parentid == 0)
{
QTreeWidgetItem* item = new QTreeWidgetItem(tree);
item->setText(0, QString::fromUtf8(nodes[i].name.c_str()));
addChilds(item, nodes[i].id);
if (!item->childCount()) delete item;
}
}
}


void SelCameraDialog::addChilds(QTreeWidgetItem* item, int id)
{
QTreeWidgetItem* child;
for (unsigned int i = 0; i < nodes.size(); ++i)
{
if (nodes[i].parentid == id)
{
child = new QTreeWidgetItem(item);
child->setText(0, QString::fromUtf8(nodes[i].name.c_str()));
addChilds(child, nodes[i].id);
if (!child->childCount()) delete child;

}

}
for (unsigned int i = 0; i < cameras.size(); ++i)
{
if (cameras[i]->nodeid == id)
{
QString camName = QString::fromUtf8(cameras[i]->name.c_str());
QString camId = QString::number(cameras[i]->id);
child = new QTreeWidgetItem(item);
child->setText(1, camId);
child->setText(0, camName);
child->setText(2, (cameras[i]->allowOnline ? "+" : "-"));
child->setText(3, (cameras[i]->allowArchive ? "+" : "-"));
child->setText(4, (cameras[i]->ptz ? "+" : "-"));
child->setTextAlignment(2, Qt::AlignCenter);
child->setTextAlignment(3, Qt::AlignCenter);
child->setTextAlignment(4, Qt::AlignCenter);

}

}

}

В buildTree в дерево добавляются корневые узлы, не имеющие родителя (т.е. у которых parentid==0). Далее вызывается метод addChilds(item, id) для рекурсивного добавления дочерних элементов к узлу item. id в теле item нету, т.к. item содержит только название узла. У узла может быть 2 типа дочерних элементов: другой узел или камера. И то, и то - по сути просто структуры, имеющие в качестве основных полей идентификатор (id), идентификатор родительского узла (parentid) и название (name). Отличие только в том, что узел может иметь детей, а камера - нет.

2) nodes - это стандартный вектор структур:
struct NodeParams
{
int id;
std::string name;
int parentid;

};

Заполняется заранее инфой, распарсенной из xml-ки.
cameras - аналогичный вектор.

3) Ребёнка в любом случае приходится сначала создать, чтобы было к чему подцеплять потомков, если таковые есть. Соответственно, если потомков нет, его приходится удалять. К дереву его, конечно, можно подцеплять только в том случае, если потомки есть, но это в данном случае не принципиально.
На самом деле интересен сам факт такого странного поведения removeChild. Что вообще по идее делает этот метод? Мне кажется, что он должен просто удалять указатель на объект QTreeWidgetItem из списка дочерних элементов. А тут получается, что я создал 2 разных объекта QTreeWidgetItem, прицепил указатели на них к родительскому QTreeWidgetItem, потом вызовом removeChild удалил указатель на один из них из списка дочерних элементов родительского QTreeWidgetItem, после чего этот список оказался пуст, хотя должен был содержать 1 элемент. Выглядит как-то нелогично :)

Выше приведённый код работает правильно, неправильно работал такой фрагмент addChilds:

if (nodes[i].parentid == id)
{
child = new QTreeWidgetItem(item);
child->setText(0, QString::fromUtf8(nodes[i].name.c_str()));
addChilds(child, nodes[i].id);
if (!child->childCount())
{
item->removeChild(child);
delete child;

}

}
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Алексей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.

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

Сделай минимального размера дерева, где ошибка появляется , затем отладчиком прошагай рекурсию, обращая внимания на адреса элементов и момент, когда удалился не тот элемент, который ожидалось

Ну и про правильный подход:

//класс элемента
class MyQTreeWidgetItem:public QTreeWidgetItem
{
    MyQTreeWidgetItem* m_parent;

    uint32_t m_id;//некая дополнительная инфа элемента.

    QTreeWidgetItem(QTreeWidgetItem* m_parent=0):m_parent(m_parent)
    {
    }
    ....
    ....
};

typedef std::multimap<
     MyQTreeWidgetItem* //parent
    ,MyQTreeWidgetItem* //child
    > td_my_tree; //это тип контейнера, содержащего ВСЁ наше дерево

//класс модели дерева
class MyTree
{
    td_my_tree m_tree;//контейнер с указателями на все элементы дерева
};



пример создания элементов дерева (легко переделать в рекурсивный вариант)
//создание корневого элемента:
MyQTreeWidgetItem* root=new MyQTreeWidgetItem(0);
m_tree.insert(td_my_tree::value_type(root.m_parent,root));

//добавляем ему два потомка
MyQTreeWidgetItem* с1=new MyQTreeWidgetItem(root);
MyQTreeWidgetItem* с2=new MyQTreeWidgetItem(root);

m_tree.insert(td_my_tree::value_type(с1.m_parent,с1));
m_tree.insert(td_my_tree::value_type(с2.m_parent,с2));

//....


тут мы контролируем всю подноготную жизни элементов , всегда их все можем быстро найти и посмотреть. По указателю на потомка всегда найдём его родителя (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, тогда прикрепи проект (удали лишнее), чтобы я смог повторить глюк у себя, тогда я смогу в отладчике точно посмотреть, что происходит.

Цитата(chereppiter @ 19.12.2012, 20:55) *
Не понимаю, в чём неправильность моего подхода к дереву. Строится оно вполне логично и правильно, если не использовать removeChild. Зачем мне дополнительная прослойка в виде stl-ного словаря, если мне это дерево нужно только для того, чтобы его отобразить в виджете и больше ни для чего?


1) неправильность подхода - не ООП , элементы дерева (виджета) раскиданы неизвестно где.
2) какая ещё прослойка ? Ты всё то же самое создаёшь. Только в мапе все указатели хранятся стройно и доступно
3) ошибка в том, что ты пытаешься одновременно отладить две вещи, при этом точно не знаю, какая из них не работает - то ли массив, то ли виджет. Я же предлагаю построить дерево без участия виджета (вещь остаётся одна), а потом уже разбираться с деревом-виджетом.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
chereppiter
  опции профиля:
сообщение 20.12.2012, 15:53
Сообщение #7


Студент
*

Группа: Участник
Сообщений: 30
Регистрация: 12.11.2012
Пользователь №: 3595

Спасибо сказали: 0 раз(а)




Репутация:   0  


Создал отдельный маленький тестовый проект. Глючная строчка кода закомментирована.
Прикрепленные файлы
Прикрепленный файл  TestTreeWidget.zip ( 11,27 килобайт ) Кол-во скачиваний: 100
 
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Алексей1153
  опции профиля:
сообщение 21.12.2012, 0:25
Сообщение #8


фрилансер
******

Группа: Участник
Сообщений: 2941
Регистрация: 19.6.2010
Из: Обливион
Пользователь №: 1822

Спасибо сказали: 215 раз(а)




Репутация:   34  


в общем, вскрытие показало, что чукча умер от вскрытия действительно есть косяк у метода removeChild : он удаляет не тот элемент, причём в контейнере виджета тот, который хотели удалить, занимает место на самом деле удалённого :D

При этом вызов деструктора чайлда (с помощью 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 в коде добавить строчку
(void)item->child(0);
то модель будет пересортирована и произойдёт удаление правильного элемента.
Другое дело, что removeChild в этом коде действительно не нужен, поскольку элемент всё равно далее удаляется.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Алексей1153
  опции профиля:
сообщение 21.12.2012, 11:10
Сообщение #10


фрилансер
******

Группа: Участник
Сообщений: 2941
Регистрация: 19.6.2010
Из: Обливион
Пользователь №: 1822

Спасибо сказали: 215 раз(а)




Репутация:   34  


iReset, вообще, забавная ситуация - указываем конкретный указатель, а виджет мочит другой )
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

2 страниц V   1 2 >
Быстрый ответОтветить в данную темуНачать новую тему
Теги
Нет тегов для показа


1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 25.11.2024, 8:36