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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: QAbstractItemModel::beginInsertRows и вставка неизвестно чего неизвестно куда.  (Прочитано 26439 раз)
Eugene Efremov
Гость
« : Август 28, 2008, 10:15 »

Имеем примерно такой код:

Код:
bool insertRows(int pos, int rows, const QModelIndex &ind)
{
beginInsertRows(ind, pos, pos+rows-1);
for(int i=0; i<rows; i++)
{
if(hasName(i)) hash[getName(i)] = getValue(i);
}
endInsertRows();
return true;
}

И в нем две проблемы:

Во-первых, в хэше оно вставиться туда, куда само захочет. А отнюдь не в заданную позицию. И меня это вполне устраивает. Но саму QAbstractItemModel это не устраивает. В результате, оно ругается в консоль и ресетит модель. Как ему объяснить, что я буду вставлять строку по произвольному индексу? А если hasName из данного примера вернула false и ничего вставлять не надо? Как это откатить?

Во-вторых сами данные, которые вставляются в модель. Функции приходится как-то добывать их самой, хотя ей, вообще говоря, неизвестно, что именно надо вставлять. Зато все это известно в том месте, где была вызвана insertRow. В этом примере это обходится таким образом: сперва внешний объект (ну, или функция-оболочка самой модели) получает данные, которые будет вставлять, сохраняет их где-то во временных переменных, потом вызывает insertRow, а затем getName и getValue их оттуда получают. Понятно, что этот способ мне сильно не нравится. И при использовании с потоками с ним будут большие проблемы. Есть ли способ лучше?
Записан
ритт
Гость
« Ответ #1 : Август 28, 2008, 15:03 »

я, конечно, извиняюсь, но все перечисленные проблемы от черезжопности написания модели. оно всегда так бывает, когда вставляешь неизвестно что неизвестно куда
Записан
Eugene Efremov
Гость
« Ответ #2 : Август 29, 2008, 02:53 »

я, конечно, извиняюсь, но все перечисленные проблемы от черезжопности написания модели.

Хорошо. Оставим данную конкретную модель в покое. Рассмотрим общий случай. Вот у нас модель. Хорошая, не «черезжопная». Вот поступил ей сигнал: вставь в себя строку с такими-то данными. Куда конкретно — на твое усмотрение. Как она должна это делать, если это хорошая модель?

Дальше. Допустим, вставляет она эти данные, используя свой внутренний интерфейс к чему-то там, во что она их вставляет. И второй интерфейс, через который она их получает. А один из этих интерфейсов ей и говорит: опаньки, сервер недоступен/кончилось место на винте/permission denied/недостающее вписать, лишнее вычеркнуть. Что она должна делать в этом случае? Если beginInsertRows уже вызван?
Записан
Hort
Гость
« Ответ #3 : Август 29, 2008, 11:35 »

Хорошо. Оставим данную конкретную модель в покое. Рассмотрим общий случай. Вот у нас модель. Хорошая, не «черезжопная». Вот поступил ей сигнал: вставь в себя строку с такими-то данными. Куда конкретно — на твое усмотрение. Как она должна это делать, если это хорошая модель?
ну вообще-то пользователь указывает куда вставлять, например поле выделенного элемента в пердставлении, вот и узнаешь индекс этого элемента через курентИндекс() и после него вставляешь.
Записан
Tonal
Гость
« Ответ #4 : Август 29, 2008, 12:52 »

У меня делается так:
Ищется куда надо вставлять и надо ли вообще.
Если надо, вызываем beginInsertRows, вставляем, endInsertRows.
То же самое для удаления.
Записан
ритт
Гость
« Ответ #5 : Август 29, 2008, 14:58 »

Вот поступил ей сигнал: вставь в себя строку с такими-то данными. Куда конкретно — на твое усмотрение. Как она должна это делать, если это хорошая модель?
так не бывает. модель не решает сама куда что вставить - модель работает с индексами (если это index-based модель) или с айтемами (если это item-based модель). модель не получает сигналов на вставку данных неизвестно куда - ты можешь сигнализировать модели, что данные в источнике изменились/доавились/удалились и опционально "место", где произошли изменения (свой ид привязки или указатель на объект, или позицию в массиве).

для примера приведу таблицу в бд и связанную с ней модель на основе QSqlTableModel: допустим, мы подписались на уведомления о событиях бд (либо наплодили хп/триггеров в бд и по периодически зачитываем таблицу изменений) - если третья сторона добавит в таблицу свои записи, мы об этом узнаем
в лучшем случае узнаем значение примарикей для изменившейся записи и тогда два варианта: найдём в модели индекс, содержащий данное значение ключа, и изменим данные всей строки в соответсвии с изменениями в бд - изменим значения или удалим строку; не найдём индекса с таким ключем и добавим в модель строчку с этим ключем и соответствующими ему данными - куда вставить строку в модели зависит исключительно от реализации: либо вставим её по какому-то признаку сортировки (например, ид по возрастанию), либо уверенные в том, что над моделью сидит прокся, просто добавим строку после всех существующих строк;
в худшем случае мы узнаем лишь в какой таблице произошли изменения и придётся перевыбирать таблицу целиком;
+не стоит забывать, что даже в случае, когда в таблицу пишем мы и точно знаем что и куда пишем, какие-то триггеры могут модифицировать данные в этой же таблице - поэтому в стандартной троллёвской реализации после отправки изменений в бд модель перевыбирается полностью;
конечный пользователь вносит изменения в модель ( и посредством модели - в источник) _только_ через представление - будь то тэйблвью или группа самописных виджетов; и чем меньше в публичном апи модели отступлений от апи абстрактной модели, тем лучше (причин множество)...

Дальше. Допустим, вставляет она эти данные, используя свой внутренний интерфейс к чему-то там, во что она их вставляет. И второй интерфейс, через который она их получает. А один из этих интерфейсов ей и говорит: опаньки, сервер недоступен/кончилось место на винте/permission denied/недостающее вписать, лишнее вычеркнуть. Что она должна делать в этом случае? Если beginInsertRows уже вызван?
а что мешает спросить "второй интерфейс, через который она их (данные) получает" доступен ли сервер, есть ли место на винте и др., и пр. _до_ того, как позвать beginInsertRows ? я вижу только одну причину - это когда "второй интерфейс" сам не знает о всех этих засадах, пока не попробует записать данные. тогда настаёт вопрос о "черезжопности" написания этого-самого "второго интерфейса", который в частном случае может быть сторонним и с закрытым кодом...но и тут можно _сначала_ записать данные во "второй интерфейс", а лишь при успешной записи звать beginInsertRows
« Последнее редактирование: Август 29, 2008, 15:00 от Константин » Записан
Eugene Efremov
Гость
« Ответ #6 : Апрель 25, 2009, 16:19 »

Мда. Перечитав тему полугодовой давности, прихожу к выводу, что я слоупок. Похоже, в моем понимании всего этого механизма отсутствует какая-то важная деталь. По сему — еще раз, медленно и по буквам:

конечный пользователь вносит изменения в модель ( и посредством модели - в источник) _только_ через представление - будь то тэйблвью или группа самописных виджетов; и чем меньше в публичном апи модели отступлений от апи абстрактной модели, тем лучше (причин множество)...

Вернемся к примеру, с которого все начиналось. Имеем простейшую модель, в качестве источника данных у нее QHash<QString, QString>. Т.е. имеем таблицу с двумя столбцами — key и value. Мы хотим предоставить пользователю возможность вставлять в нее новые строки, проверяя, при этом, введенные пользователем ключи на уникальность. Как мы должны все это реализовывать?

Если более конкретно, то вопросы такие:

1. Где хранить сам хеш — в классе модели, где-то еще?
2. Как реализовать саму вставку, соблюдая требование «_только_ через представление»? Мне ничего более путного, чем кнопка Insert, вызывающая простейший диалог, в голову не приходит — а это с существующими View никак не связано...
3. Главное — сама вставка. Как и в каком порядке производить следующие операции:
  • проверка введенного ключа на уникальность;
  • вставка элемента в хэш, если эта проверка успешна;
  • откат, если она завершилась неудачей;
  • вызов insertRows и действия (какие именно?) внутри него?
Может быть insertRows при этом раскладе вообще не нужен? А нужно что-то еще? Что именно? И зачем тогда insertRows вообще нужен?

модель работает с индексами (если это index-based модель) или с айтемами (если это item-based модель).

А можно подробнее — чем одно отличается от другого? И/или где можно про это прочитать?
Или под «item-based» имеется в виду просто использование стандартных Q(List|Table|Tree)Widget и связанных с ними ...WidgetItem?

P.S. Подумал, что если тему раскрыть — может получиться неплохое howto, которое, при удаче, можно будет добавить в соответствующий раздел...
« Последнее редактирование: Апрель 25, 2009, 19:05 от Eugene Efremov » Записан
ритт
Гость
« Ответ #7 : Апрель 26, 2009, 03:00 »

пришлось перечитывать тему, т.к. уже ни черта не помню (кому на что отвечал и кому так и забыл ответить)

1. для такого простого случая я бы хранил хэш (источник) приватом в модели
2. под «_только_ через представление» я не имел в виду Q*View (возможно, я путанно изъяснился, но перечитывать снова лениво). важный пункт - все манипуляции с данными моделями _очень и очень_ желательно организовывать через абстрактный интерфейс модели. редко когда требуется нечто странное с извращениями...и чаще это от дефицита мозга. для нашего конкретного примера вариантов больше одного...
3. ...самым простым мне представляется вариант с диалогом добавления/редактирования записи: если требуется отредактировать запись, то данные из текущей строки (model::data(...)) переносим на виджеты новоиспечённого диалога (для простейшего случая можно просто воспользоваться сеттерами соответствующих виджетов-редакторов), а по завершении редактирования валидируем данные и возвращаем в модель (model::setData(...)); при добавлении данных лишь валидируем данные по окончании редактирования и возвращаем их в модель, предварительно создав строку посредством insertRows (т.к. в данном случае валидация уже прошла и мы уверены в корректности, уникальности и т.п. данных). вставлять строку можно до/после текущей строки или в начало/конец списке - в зависимости от требований

insertRows нужен! - это стандартная интерфейсная часть абстрактной модели. документация призывает не забывать об insertRows в кастомных моделях - документация не врёт (обычно)


попросту говоря, item-based модель, это модель, в которой работа с индексами частично или полностью завуалирована работой с айтемами. айтемом может служить указатель на объект, структура, цифробуквенный шифр - в зависимости от задачи и фантазии.

> Подумал, что если тему раскрыть...
пожалуйста, я не против ) копирайтов на свои посты не накладываю Улыбающийся
Записан
Eugene Efremov
Гость
« Ответ #8 : Апрель 26, 2009, 20:30 »

2. важный пункт - все манипуляции с данными моделями _очень и очень_ желательно организовывать через абстрактный интерфейс модели. редко когда требуется нечто странное с извращениями...и чаще это от дефицита мозга. для нашего конкретного примера вариантов больше одного...

Пока что у меня картинка не складывается: получается, что для валидации нужно или расширять интерфейс модели, или работать с источником напрямую (см. ниже).


3. ...самым простым мне представляется вариант с диалогом добавления/редактирования записи: если требуется отредактировать запись, то данные из текущей строки (model::data(...)) переносим на виджеты новоиспечённого диалога (для простейшего случая можно просто воспользоваться сеттерами соответствующих виджетов-редакторов), а по завершении редактирования валидируем данные и возвращаем в модель (model::setData(...)); при добавлении данных лишь валидируем данные по окончании редактирования и возвращаем их в модель, предварительно создав строку посредством insertRows (т.к. в данном случае валидация уже прошла и мы уверены в корректности, уникальности и т.п. данных). вставлять строку можно до/после текущей строки или в начало/конец списке - в зависимости от требований

Так... Попробую разобрать по порядку. Получается следующая последовательность действий:
1. Вызов диалога и ввод пользователем данных.
2. Валидация, дальнейшие действия — только при ее успехе.
3. Вызов insertRows и добавление строки.
4. Собственно добавление данных в хэш через вызов setData.
Так?

Если так, то по всем пунктам — кроме первого — сразу же возникают вопросы:

Валидация:
Чтобы проверить ключ на уникальность — нужно получить доступ к самому хэшу и вызвать contains. А это стандартным интерфейсом абстрактной модели не предусмотрено. Т.е. нужно или расширять интерфейс модели, или — лезть непосредственно к источнику данных мимо этой модели... И то, и другое — противоречит п.2.
Или я в этом стандартном интерфейсе что-то пропустил и заготовка для валидации там имеется?

insertRows:
Если данные в хэш мы вставляем в другом месте — то что мы делаем между вызовом beginInsertRows и endInsertRows? Или insertRows вообще не следует переопределять?

Вставка данных:
Вызываем setData — и имеем ту же проблему, с которой все начиналось, но уже внутри нее. Данные в хэш вставляются не в заданной позиции, а туда, куда этот хэш сам захочет. И как это корректно обработать?
И еще — допустимо ли вставлять оба поля сразу, или придется ее два раза вызывать — для ключа и значения?


попросту говоря, item-based модель, это модель, в которой работа с индексами частично или полностью завуалирована работой с айтемами. айтемом может служить указатель на объект, структура, цифробуквенный шифр - в зависимости от задачи и фантазии.

Т.е. здесь у нас — item-based, поскольку работаем мы, в основном, с парой ключ/значение?


> Подумал, что если тему раскрыть...
пожалуйста, я не против ) копирайтов на свои посты не накладываю Улыбающийся

Спасибо.
Только пока у меня задача — самому понять что к чему. А потом уже можно и накатать что-нибудь на тему...

P.S. Кстати, заметил, что непонятки у меня в том месте, где в стандартной MVC находится контроллер. Здесь контроллера нет, а делегаты всех случаев не охватывают. Дырка приходится как раз на вставку новых элементов — где меня и заклинило...

Записан
ритт
Гость
« Ответ #9 : Апрель 26, 2009, 21:14 »

по моему скромному мнению кутэ чудно обходится и без контроллеров.

валидация:
упущен важный момент - QAbstractItemModel::match(...)

insertRows:
между beginInsertRows и endInsertRows можно ничего не делать. это всего лишь необходимые звенья в цепи уведомлений об изменении данных. в сложных моделях, критичных к производительности между beginInsertRows и endInsertRows выполняется работа с источником, т.к. между данными звеньями перевыборки данных и т.п. гарантированно не будет.
чтобы было более понятно, поясню на простом примере:
Код:
void MyModel::insertMyRow(const QString& key, const MyData& data)
{
    beginInsertRows(...);

    m_sourceHash[key] = data;

    endInsertRows(...);
}
после endInsertRows вьюхи смогут работать с целой строкой данных, тогда как setData(...) уведомит об обновлении только одной ячейки данных

Цитировать
Т.е. здесь у нас — item-based <snip>
согласен, в данном случае было бы уместно реализовать навигацию по ключу вместо навигации по индексу

советую почитать исходники QStringListModel - хоть и не совсем то, что нужно, но тепло...
Записан
Eugene Efremov
Гость
« Ответ #10 : Апрель 27, 2009, 04:21 »

валидация:
упущен важный момент - QAbstractItemModel::match(...)

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


insertRows:
между beginInsertRows и endInsertRows можно ничего не делать. это всего лишь необходимые звенья в цепи уведомлений об изменении данных. в сложных моделях, критичных к производительности между beginInsertRows и endInsertRows выполняется работа с источником, т.к. между данными звеньями перевыборки данных и т.п. гарантированно не будет.

А способ просто запретить эту перевыборку/etc. существует? Если, к примеру, с источником работать надо, а строки вставлять — нет?


Код:
void MyModel::insertMyRow(const QString& key, const MyData& data)

Это уже не стандартный интерфейс. А как же «все манипуляции с данными моделями _очень и очень_ желательно организовывать через абстрактный интерфейс модели»?
Значит, расширять этот интерфейс все-таки стоит?


Код:
    beginInsertRows(...);
    m_sourceHash[key] = data;
    endInsertRows(...);

Но оно здесь вставляется не туда, куда указывает beginInsertRows, а туда, куда хэш захочет (subj, однако). Это нормально?
Может быть, после endInsertRows стоит вызвать dataChanged, указав ему «от нуля и до конца»?


Цитировать
Т.е. здесь у нас — item-based <snip>
согласен, в данном случае было бы уместно реализовать навигацию по ключу вместо навигации по индексу

Как это сделать? В чем будет выражаться отличие от навигации по индексу?


советую почитать исходники QStringListModel - хоть и не совсем то, что нужно, но тепло...

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

Хотя, кое-что полезное я оттуда извлек — похоже, что insertRows используется только для вставки пустых строк (нигде в документации этого явно не сказано). Это так?
Записан
ритт
Гость
« Ответ #11 : Апрель 27, 2009, 06:43 »

меня этот тред начинает утомлять...

запретить перевыборку нельзя - вьюха не спрашивает у модели готова ли модель отдать данные - если модель заявляет, что имеет 2 столбца и 3 строки, должны быть доступны данные для каждой из 6 ячеек (плюс 5 ячеек вертикального и горизонтального заголовков). чтобы подсказать вьюхе не делать запросы данных, нужно использовать стандартный механизм - layoutAboutToBeChanged()/layoutChanged(), rowsAboutToBeInserted()/rowsInserted() и т.д.

> void MyModel::insertMyRow(const QString& key, const MyData& data)
да, это нестандартный интерфейс. я специально назвал это примером. если читать этот пример, не выдирая его из контекста, это пример оптимизированной вставки данных.
такой конструкцией я пользуюсь, когда необходимо заполнить модель из кэша - не вставлять же данные поиндексно, если после endInsertRows всё-равно произойдёт выборка всех данных для новых строк! и строк там обычно куда более одной.

> Как это сделать? В чем будет выражаться отличие от навигации по индексу?
смотреть документацию и код любой item-based модели...
Записан
Eugene Efremov
Гость
« Ответ #12 : Апрель 27, 2009, 14:33 »

меня этот тред начинает утомлять...

Жаль.
Хотя, в принципе, понимаю, что таким дотошным выяснением деталей можно достать кого угодно.  Улыбающийся

Тогда предлагаю сделать так. Я иду в тему «Уроки и статьи», пишу там — в меру своего текущего понимания — заготовку для обещанного howto, после чего общими усилиями доводим его до ума. На готовом примере с описанием все ошибки и неточности сразу будут видны. А в конечном счете получится (надеюсь) что-то, полезное для всего форума.


> Как это сделать? В чем будет выражаться отличие от навигации по индексу?
смотреть документацию и код любой item-based модели...

Можно список таких моделей — код которых можно посмотреть?
В документации, кстати, термин «item-based» употребляется только в контексте «Item View Convenience Classes». Про «item-based model» там ни слова...

P.S. Тема переехала... QAbstractItemModel — это, оказывается, GUI. Не знал...
Записан
ритт
Гость
« Ответ #13 : Апрель 28, 2009, 22:05 »

QStandardItemModel, например
Записан
Eugene Efremov
Гость
« Ответ #14 : Май 01, 2009, 19:42 »

Тогда предлагаю сделать так. Я иду в тему «Уроки и статьи», пишу там — в меру своего текущего понимания — заготовку для обещанного howto, после чего общими усилиями доводим его до ума. На готовом примере с описанием все ошибки и неточности сразу будут видны. А в конечном счете получится (надеюсь) что-то, полезное для всего форума.

Статья готова. Приглашаю принять участие в обсуждении и вычистке...


QStandardItemModel, например

Хм... Там каждый элемент соответствует одной ячейке. Вроде бы, обычно элемент занимает строку, так что пример, похоже, не слишком характерный...
« Последнее редактирование: Май 01, 2009, 22:09 от pastor » Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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