Russian Qt Forum

Qt => Model-View (MV) => Тема начата: xintrea от Январь 05, 2009, 15:22



Название: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 05, 2009, 15:22
Здравствуйте!


Имею модель kntrmodel с древовидными данными (класс knowtreemodel, унаследован от QAbstractItemModel). Делаю метод, который бы перемещал ветку вверх-вниз на одну позицию (ветки перемещаю только в пределах  уровня вложенности). То есть, по факту, при перемещении ветки на одну позицию вверх, перемещаемая ветка меняется местами с вышележащей. А при перемещении вниз, перемещаемая ветка меняется местами с нижележащей.

Перемещение ветки происходит в два этапа.

1. В TreeItem (аналог примера /qtdemo/examples/itemviews/editabletreemodel) сделал метод move_up()

Код:
bool TreeItem::move_up(void)
{
 // Выясняется номер данного элемента в списке родителя
 int num=childNumber();

 // Если двигать вверх некуда, ничего делать ненужно
 if(num==0)return false;

 // Элемент перемещается вверх по списку
 ( parentItem->childItems ).swap(num,num-1);
 
 return true;
}


2. После вызова метода move_up(), модель отправляет сигнал что данные изменены dataChanged(). Делается это в методе объекта knowtreemodel, который и является моделью (унаследован от QAbstractItemModel)

Код:
// Перемещение ветки вверх
void knowtreemodel::move_up_branch(const QModelIndex &index)
{
 // Получение QModelIndex расположенного над элементом index
 QModelIndex swap_index=index.sibling(index.row()-1,0);
 
 // Получение ссылки на Item элемент по QModelIndex
 TreeItem *current=getItem(index);

 // Элемент перемещается через метод move_up()
 if(current->move_up())
  emit dataChanged(swap_index,index); // Данные на экране обновляются
}


На экране, после этих действий, ветка, как и положено, перемещается вверх на одну строку.

Т.е. было

Овощи
Фрукты
Ягоды <----- Перемещаем вверх
Корнеплоды


Стало

Овощи
Ягоды
Фрукты
Корнеплоды


Однако, если теперь ткнуть на Ягоды, и сделать вывод в консоль значения data() для данного QModelIndex

Код:
// Действия при клике на ветку дерева
void treescreen::on_knowtree_clicked(const QModelIndex &index)
{
 // Получаем указатель на текущую выбранную ветку дерева
 TreeItem *item = kntrmodel->getItem(index);
 
 qDebug() << "Name " << item->data();

 ...
}

то окажется, что данный пункт дерева всеравно указывает на Фрукты!

Совершенно не понимаю, почему так происходит. Метод dataChanged() все правильно обновил на экране, т.е. метод data() вызывался коственно при dataChanged(), и выдал нужные данные в соответсвии с новым расположением элементов. Однако, при обращении к изменённым элементам, метод data() выдает старые значения... Такое впечатление, что QModelIndex потянулись вслед за ветками, и были обменены между этими двумя ветками, хотя нигде таких действий не проводилось.

В любом случае, не могу понять, что я сделал неправильно, и как исправить. Кто что может подсказать?



Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 05, 2009, 15:34
А getItem() как находит нужный TreeItem? По index.row()? Или хранит некий хеш с QPersistentModelIndex?


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 05, 2009, 16:48
А getItem() как находит нужный TreeItem? По index.row()? Или хранит некий хеш с QPersistentModelIndex?

Этот метод тот же, что и в примере /qtdemo/examples/itemviews/editabletreemodel. Вот его реализация

Код:
// Получение указателя на Item-злемент связанный с заданным QModelIndex
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
    if (index.isValid()) {
        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        if (item) return item;
    }
    return rootItem;
}

Пояснение, откуда взялось TreeModel. Класс knowtreemodel наследуется по цепочке knowtreemodel->TreeModel->QAbstractItemModel.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 05, 2009, 19:46
View для оптимизации рисования кеширует индексы, делается это через единственный доступный механизм - QPersistentModelIndex. Это такая штука, которая сама подстраивает row(), column() и parent() индекса, если модель вздумала измениться. Естественно когда вы сами двигаете внутреннее представление - модель никаким образом знать об этом не может.

Вы спрашиваете почему визуально элемент изменился, а в слот вам пришло предыдущее значение? Это потому что TreeItem ассоциирован с указателем на сам элемент. Ваша ошибка в том, что вы не обменяли значения даных между TreeItem'ами, а рокирнули сами указатели. То-есть закешированные View индексы, они же указатели на TreeItem'ы, - не изменились.

Вариантов два:

1. Менять не указатели, а данные по указателям. Как вы могли верно подметить, это гемморойно - сохранять структуру указателей такой же, а менять только данные по ним. Кроме того указатель является уникальным идентификатором, через который к TreeItem'у ссылаются не только из модели, а откуда-то ещё.

2. Делать так как делаете вы, но сказать модели, что внутренняя структура модели изменилась через QAbstractItemModel::changePersistentIndex ( const QModelIndex & from, const QModelIndex & to ). И ещё - советую обьединить логику move_up() в одном месте.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 06, 2009, 12:19
2. Делать так как делаете вы, но сказать модели, что внутренняя структура модели изменилась через QAbstractItemModel::changePersistentIndex ( const QModelIndex & from, const QModelIndex & to ). И ещё - советую обьединить логику move_up() в одном месте.

Спасиба, вы отлично все расписали.

Остался у меня неразрешенный вопрос - в какой момент надо вызывать changePersistentIndex() ? Я пробую делать так

Код:
 if(current->move_up()) // Если перемещение в TreeItem представлении прошло нормально
  {
   changePersistentIndex(swap_index,index);
   emit dataChanged(swap_index,index);
  }

Но поведение осталось прежнее. В какой момент надо вызывать changePersistentIndex() ?


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 06, 2009, 12:35
По идее - в любое время, так как метод только меняет содержимое QPersistentModelIndex. Забыли:

Код:
 if(current->move_up()) // Если перемещение в TreeItem представлении прошло нормально
  {
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index); // <==
   emit dataChanged(swap_index,index);
  }


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 06, 2009, 12:49
Судя по документации должно быть что-то типа:

Код:
 if ( willChangeTheModel )
  {
   emit layoutAboutToBeChanged();
   // code that moves data
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index);
   emit layoutChanged();
  }


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 06, 2009, 21:32
Спасибо, все получилось.

Но осталась еще одна проблема. После перемещения ветки, нужно установить засветку на новой позиции, куда переместилась ветка. QModelIndex для новой позиции мне известен. И я для вида knowtree даю следующую команду

Код:
 knowtree->selectionModel()->setCurrentIndex(index_after_move,QItemSelectionModel::ClearAndSelect);

В результате, синенькая засветка движется вслед за перемещаемой веткой (т.е. после перемещения, перемещаемая ветка оказывается выбраной синей засветкой). Но в Qt помимо синей засветки есть еще и пунктирное обрамление. Пунктирное обрамление появляется например, если ткнуть на любую ветку мышкой - ветка станет выделена синей засветкой, вокруг которой есть пунктирное обрамление.

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

Как сделать это?

ЗЫЖ Попробовал добавить

Код:
 knowtree->selectionModel()->select(index_after_move,QItemSelectionModel::ClearAndSelect);

тоже не помогает.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 06, 2009, 22:07
Весь код в студию.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: BRE от Январь 06, 2009, 22:11
Попробуй поиграть с флагами QItemSelectionModel::SelectionFlags. Добавить QItemSelectionModel::Current.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 06, 2009, 23:01
Добавил в их пример пару кнопок, сделал то же, что а топикстартер. Текущий элемент (пунктиром который) двигается вместе с перемещением. Другое дело что его не видно, когда вьюв теряет фокус.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 07, 2009, 00:11
Весь код в студию.

В обсервере, который знает о виде и модели, имеем

Код:
void treescreen::move_up_branch(void)
{
 // Если ветку нельзя перемещать
 if(!move_check_enable()) return;
 
 // Получение индекса выделенной строки
 QModelIndex index=get_current_item_index();
 
 // Ветка перемещается
 QModelIndex index_after_move;
 index_after_move=kntrmodel->move_up_branch(index);
 
 // Установка курсора на позицию, куда была перенесена ветка
 if(index_after_move.isValid())
  {
   knowtree->selectionModel()->setCurrentIndex(index_after_move,QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
   knowtree->selectionModel()->select(index_after_move,QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
  }
}

В модели имеем

Код:
// Перемещение ветки вверх
QModelIndex knowtreemodel::move_up_branch(const QModelIndex &index)
{
 // Получение QModelIndex расположенного над элементом index
 QModelIndex swap_index=index.sibling(index.row()-1,0);
 
 // Получение ссылки на Item элемент по QModelIndex
 TreeItem *current=getItem(index);

 // Перемещается ветка, и если она нормально переместилась
 if(current->move_up())
  {
   emit layoutAboutToBeChanged();
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index);
   emit layoutChanged();

   // Возвращается указатель на перемещенную ветку
   return swap_index;
  }
 else
  return QModelIndex(); // Возвращается пустой указатель
}

В Item-классе имеем

Код:
bool TreeItem::move_up(void)
{
 // Выясняется номер данного элемента в списке родителя
 int num=childNumber();

 // Если двигать вверх некуда, ничего делать ненужно
 if(num==0)return false;

 // Элемент перемещается вверх по списку
 ( parentItem->childItems ).swap(num,num-1);
 
 return true;
}


Добавил в их пример пару кнопок, сделал то же, что а топикстартер. Текущий элемент (пунктиром который) двигается вместе с перемещением. Другое дело что его не видно, когда вьюв теряет фокус.

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

А вот если просто нажимать кнопку перемещения вверх несколько раз (не клацая каждый раз по выделенному пункту), то пунктир появляется каждый второй клик (с чего бы?). И в момент, когда пунктира нет, перемещается вверх не выделенный синеньким пункт, а пункт под ним. То есть, при втором клике, пункты опять меняются местами, и список становится прежним. Но после этого синяя засветка с пунктиром (!) выделяет пункт над этими двумя, которые поменялись дважды местами, и все повторяется с новыми, вышележащими пунктами. Чехорда одним словом.

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


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: BRE от Январь 07, 2009, 00:23
Выделенных (Select) элементов может быть много, текущий (Current) только один, он подсвечивается пунктиром. По идее тебе нужно старое выделение очищать (Clear), а новое подсвечивать (Select) и делать текущим (Current).


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 07, 2009, 00:49
Сильно вникнуть не получилось, но в коде по крайней мере одна ошибка - QModelIndex knowtreemodel::move_up_branch(const QModelIndex &index) возвращает мусор. Потому что QModelIndex статический обьект и перестаёт быть валидным при нарушении внутренней структуры данных.

Код:
// Перемещение ветки вверх
QModelIndex knowtreemodel::move_up_branch(const QModelIndex &index)
{
 // Получение QModelIndex расположенного над элементом index
 QModelIndex swap_index=index.sibling(index.row()-1,0); <<<=== ЗДЕСЬ ИНДЕКС ЕЩЁ ВАЛИДНЫЙ
 
 // Получение ссылки на Item элемент по QModelIndex
 TreeItem *current=getItem(index);

 // Перемещается ветка, и если она нормально переместилась
 if(current->move_up())
  {
             <<== А ЗДЕСЬ УЖЕ НЕТ
   emit layoutAboutToBeChanged();
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index);
   emit layoutChanged();

   // Возвращается указатель на перемещенную ветку
   // return swap_index; <<== МУСОР
   return index( swap_index.row(), swap_index.column(), swap_index.parent() ); <<== ЧЕРЕЗ ОДНО МЕСТО, ЗАТО КОРРЕКТНО
  }
 else
  return QModelIndex(); // Возвращается пустой указатель
}

Ещё я вам привёл пример, что смена внутренней структуры должна быть между layoutAboutToBeChanged()/layoutChanged(), а у вас она вылезла за пределы. Думайте сами как вам лучше её вынести эту логику внутрь. После получения сигнала layoutAboutToBeChanged() - желающие имеют право получить корректные данные по старым закешированным у них индексам, а у вас уже в этот момент внутренняя структура изменена.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 07, 2009, 21:49
Уф, вчера сайт недоступен был, сразу не ответил.

Я для упрощенья разговора модифицировал пример /qtdemo/examples/itemviews/editabletreemodel.

Добавил:

1. Кнопочку перемещения вверх (можно жать Ctrl+U)
2. Метод move_up_branch() в mainwindow.cpp
3. Метод move_up_branch() в treemodel.cpp
4. Метод move_up в treeitem.cpp

При запуске программы, и попытке перемещения ветки вверх, можно наблюдать описанную мной чехорду. Ссылка на модифицированный (рабочий) пример http://webfile.ru/2535740 (http://webfile.ru/2535740).


Затем я попробовал изменить метод move_up_branch() в treemodel.cpp так как вы советуете. Сделал его вот таким

Код:
QModelIndex TreeModel::move_up_branch(const QModelIndex &index)
{
 // Получение QModelIndex расположенного над элементом index
 QModelIndex swap_index=index.sibling(index.row()-1,0);
 int swpidx_row=swap_index.row();
 int swpidx_column=swap_index.column();
 QModelIndex swpidx_parent=swap_index.parent();

 // Получение ссылки на Item элемент по QModelIndex
 TreeItem *current=getItem(index);

 // Перемещается ветка, и если она нормально переместилась
 if(current->move_up())
  {
   emit layoutAboutToBeChanged();
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index);
   emit layoutChanged();

   // Возвращается указатель на перемещенную ветку
   QModelIndex retidx=index(swpidx_row, swpidx_column, swpidx_parent); // <--- Тут ошибка
   return retidx;
  }
 else
  return QModelIndex(); // Возвращается пустой указатель
}

Однако при компиляции получаю ошибку

Цитировать
treemodel.cpp: In member function 'QModelIndex TreeModel::move_up_branch(const QModelIndex&)':
treemodel.cpp:306: ошибка: не найден метод для преобразования в '(const QModelIndex) (int&, int&, QModelIndex&)'
make: *** [treemodel.o] Ошибка 1

Эта ошибка выше моего понимания, курение ассистента ясности не внесло.


ЗЫЖ Да, я готов изменить логику этих методов, но только мне нужно сначала добиться хоть какого-то рабочего результата, который будет работать правильно.

ЗЗЫЖ А как вы узнали, что в QModelIndex swap_index мусор находится?


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 07, 2009, 22:11
1. Вы так и не обрамили изменения структуры данных в пару вызовов layoutAboutToBeChanged();layoutChanged(); - оно у вас осталось в методе current->move_up().
2. QModelIndex - статический обьект с временными данными, валидный только за пределами модели только в пределах текущего стека, в котором этот QModelIndex получен. Когда он подставляется обратно в модель - по хорошему должен быть логически разбит обратно на row, column, parent. Если же он выходит за пределы модели - он должен быть пересобран.
3. Ошибка в том, что в области видимости уже есть переменная с именем index. Делайте: this->index( ... );


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 07, 2009, 22:16
Совет. Откройте Ассистент и прочитайте всю документацию по методам Model/View/Index/PersistentIndex. Если останутся неясности, сомнения что к чему и как работает - открываете исходники и читаете код. Можно перед зеркалом с интонацией. Мне помогает.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 07, 2009, 22:19
Фух, наконец заработало какнада.

Цитировать
Ошибка в том, что в области видимости уже есть переменная с именем index. Делайте: this->index( ... );

никогда бы до этого недодумался!


"QModelIndex - статический обьект с временными данными, валидный только за пределами модели только в пределах текущего стека, в котором этот QModelIndex получен", чота я завис на этой фразе... Надо поспать.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 07, 2009, 22:24
Совет. Откройте Ассистент и прочитайте всю документацию по методам Model/View/Index/PersistentIndex.

Не, вы один пункт упустили. Правильно будет так - "Выучите английский язык. Откройте Ассистент и прочитайте всю документацию по методам Model/View/Index/PersistentIndex."


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: xintrea от Январь 08, 2009, 14:42
А у меня вопрос к гуру.

Если закрыть глаза на весь код, который был в этой ветке, и сделать все "с нуля", то какую бы реализацию перемещения ветки вверх-вниз (без выхода за границы уровня вложенности) вы бы использовали, если в основу взять пример /qtdemo/examples/itemviews/editabletreemodel ?


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: Dendy от Январь 08, 2009, 15:52
Всё у вас там нормально. Кроме того что проверка на то может ли Item переместиться вверх/вниз находится в самом Item'е. Замените возвращаемый bool на void, в проверки - на ассерты. Саму проверку вынесите наружу.


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: ритт от Январь 08, 2009, 23:38
а я бы на сортфильтерпроксимодели сделал бы минут за десять :)


Название: Re: QModelIndex указывает куда-то не туда...
Отправлено: vaprele07 от Январь 09, 2009, 06:03
на примере: /qtdemo/examples/itemviews/editabletreemodel.

Код:
void TreeModel::swap(const QModelIndex &parent, int i, int j)
{
  TreeItem *rootItem = getItem(parent);
  rootItem->swap(i, j); // childItems.swap(i, j);

  QModelIndex ix = createIndex(i, 0, rootItem->child(i));
  QModelIndex jx = createIndex(j, 0, rootItem->child(j));

  dataChanged(ix, ix);
  dataChanged(jx, jx);
}