Russian Qt Forum
Май 16, 2024, 16:43 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: QModelIndex указывает куда-то не туда...  (Прочитано 20889 раз)
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« : Январь 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 потянулись вслед за ветками, и были обменены между этими двумя ветками, хотя нигде таких действий не проводилось.

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

Записан

Собираю информацию по крупицам
http://webhamster.ru
Dendy
Гость
« Ответ #1 : Январь 05, 2009, 15:34 »

А getItem() как находит нужный TreeItem? По index.row()? Или хранит некий хеш с QPersistentModelIndex?
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #2 : Январь 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.
Записан

Собираю информацию по крупицам
http://webhamster.ru
Dendy
Гость
« Ответ #3 : Январь 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() в одном месте.
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #4 : Январь 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() ?
Записан

Собираю информацию по крупицам
http://webhamster.ru
Dendy
Гость
« Ответ #5 : Январь 06, 2009, 12:35 »

По идее - в любое время, так как метод только меняет содержимое QPersistentModelIndex. Забыли:

Код:
 if(current->move_up()) // Если перемещение в TreeItem представлении прошло нормально
  {
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index); // <==
   emit dataChanged(swap_index,index);
  }
Записан
Dendy
Гость
« Ответ #6 : Январь 06, 2009, 12:49 »

Судя по документации должно быть что-то типа:

Код:
 if ( willChangeTheModel )
  {
   emit layoutAboutToBeChanged();
   // code that moves data
   changePersistentIndex(swap_index,index);
   changePersistentIndex(index,swap_index);
   emit layoutChanged();
  }
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #7 : Январь 06, 2009, 21:32 »

Спасибо, все получилось.

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

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

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

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

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

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

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

тоже не помогает.
« Последнее редактирование: Январь 06, 2009, 21:34 от xintrea » Записан

Собираю информацию по крупицам
http://webhamster.ru
Dendy
Гость
« Ответ #8 : Январь 06, 2009, 22:07 »

Весь код в студию.
Записан
BRE
Гость
« Ответ #9 : Январь 06, 2009, 22:11 »

Попробуй поиграть с флагами QItemSelectionModel::SelectionFlags. Добавить QItemSelectionModel::Current.
Записан
Dendy
Гость
« Ответ #10 : Январь 06, 2009, 23:01 »

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

Сообщений: 754



Просмотр профиля WWW
« Ответ #11 : Январь 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;
}


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

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

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

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

Собираю информацию по крупицам
http://webhamster.ru
BRE
Гость
« Ответ #12 : Январь 07, 2009, 00:23 »

Выделенных (Select) элементов может быть много, текущий (Current) только один, он подсвечивается пунктиром. По идее тебе нужно старое выделение очищать (Clear), а новое подсвечивать (Select) и делать текущим (Current).
Записан
Dendy
Гость
« Ответ #13 : Январь 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() - желающие имеют право получить корректные данные по старым закешированным у них индексам, а у вас уже в этот момент внутренняя структура изменена.
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #14 : Январь 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.


Затем я попробовал изменить метод 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 мусор находится?
Записан

Собираю информацию по крупицам
http://webhamster.ru
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.084 секунд. Запросов: 22.