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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: Странное поведение QTreeView.  (Прочитано 3311 раз)
ksergey85
Гость
« : Июль 06, 2016, 17:13 »

Давайте немножко копнем вглубь Qt (что то слишком часто мне приходится это делать в последнее время). Класс QHeaderViewPrivate (приватная реализация QHeaderView) имеет два приватных слота _q_layoutAboutToBeChanged() и _q_layoutChanged(). Как несложно догадаться из их названия эти слоты нужны, чтобы обрабатывать ситуации, в которых модель, подключенная к хедеру, тем или иным образом меняет местами свои индексы (как правило это банальная сортировка) - первый слот срабатывает перед началом сортировки, второй - после. Взглянув на эти слоты, нетрудно заметить, что они предназначены для работы в паре, как и все слоты-обработчики парных сигналов "до-после". Первый заполняет заполняет список persistent-индексов persistentHiddenSections, второй обрабатывает его и очищает. Вроде бы все логично.
На практике я увидел другую картину. Вызов метода сортировки модели обычного QTreeView (а QTreeView как мы с вами знаем имеет лишь один хедер - горизонтальный) привел к вызову лишь одного приватного слота хедера _q_layoutAboutToBeChanged(). Парный слот _q_layoutChanged() вызван не был Шокированный. Отыскать кто отламывает слот от сигнала было нетрудно. Вот это место

Код:
void QTreeView::setModel(QAbstractItemModel *model)
{
    Q_D(QTreeView);
    if (model == d->model)
        return;
    if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
        disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
                this, SLOT(rowsRemoved(QModelIndex,int,int)));

        disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset()));
    }

    if (d->selectionModel) { // support row editing
        disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
                   d->model, SLOT(submit()));
        disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
                   this, SLOT(rowsRemoved(QModelIndex,int,int)));
        disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset()));
    }
    d->viewItems.clear();
    d->expandedIndexes.clear();
    d->hiddenIndexes.clear();
    d->header->setModel(model);
    QAbstractItemView::setModel(model);

    // QAbstractItemView connects to a private slot
    disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
               this, SLOT(_q_rowsRemoved(QModelIndex,int,int)));
!!!!!!!
    // do header layout after the tree
    disconnect(d->model, SIGNAL(layoutChanged()),
               d->header, SLOT(_q_layoutChanged()));  
!!!!!!!
    // QTreeView has a public slot for this
    connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
            this, SLOT(rowsRemoved(QModelIndex,int,int)));

    connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset()));

    if (d->sortingEnabled)
        d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder());
}

Комментарий к этому дисконнекту меня конечно дюже порадовал. Из него следует, что дисконнект сделан для изменения порядка вызова слотов (чтобы хедер вызвал свой слот после вызова слота самого дерева), но тогда где повторный коннект Непонимающий.
В итоге получаем, что при многократной сортировке модели список persistent-индексов спрятанных секций persistentHiddenSections будет набиваться данными, а очищен он так и не будет.

А теперь представьте следующую цепочку вызовов:
1. Прячем все столбцы хедера.
2. Вызываем метод сортировки модели. Все столбцы попадают в список persistentHiddenSections и там остаются.
3. Отображаем все или некоторые столбцы хедера.
4. Вызываю собственный метод модели, который меняет строки (не столбцы, а именно строки!!!) местами с помощью методов beginMoveRows() и endMoveRows(). Вызов endMoveRows() приводит к вызову невызванного слота хедера _q_layoutChanged() и ... правильно, этот слот скрывает все столбцы, т. к. все они до сих пор содержаться в persistentHiddenSections.
Кстати говоря, beginMoveRows() тоже не приводит к вызову _q_layoutAboutToBeChanged(), т.к. это горизонтальный хедер. То есть в этом случае из пары слотов вызовется теперь только второй. Но это как раз не является критичным, т.к. если список persistentHiddenSections пуст, никаких деструктивных действий слот с хедером не производит.

Получается, что изменение порядка строк модели приводит к скрытию всех столбцов хедера, что меня крайне удивило. Посмотрел на гите - реализация метода QTreeView::setModel() не менялась со времен царя Гороха. Сижу и думаю то ли я что то не доглядел, то ли это прикол у кутешников такой. Какой смысл специально отрывать парный слот хедера ума не приложу. Может вы мне поясните?
« Последнее редактирование: Июль 06, 2016, 17:15 от ksergey85 » Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #1 : Июль 06, 2016, 18:21 »

Комментарий к этому дисконнекту меня конечно дюже порадовал. Из него следует, что дисконнект сделан для изменения порядка вызова слотов (чтобы хедер вызвал свой слот после вызова слота самого дерева), но тогда где повторный коннект Непонимающий.
Всё верно.
Тролли снимают QHeaderView::_q_layoutChanged(), повешенный в QHeaderView::setModel(), т.к. обновление header-a будет инициировано после отработки QTreeView QAbstractItemView::_q_layoutChanged() в QTreeView::updateGeometries().
Т.е. после испускания моделью сигнала layoutChanged() в итоге будет вызвана QHeaderView::updateGeometries().

Цитировать
Вызов endMoveRows() приводит к вызову невызванного слота хедера _q_layoutChanged() ...
Очень странно, т.к. этого быть не должно.

UPD. не должно быть в Qt4, а вот в Qt5 после испускания моделью rowsMoved() или columnsMoved() будет
UPD2. скорее всего эту проблему решит workaround:
Код
C++ (Qt)
header->model()->disconnect( SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), header );
« Последнее редактирование: Июль 06, 2016, 18:58 от GreatSnake » Записан

Qt 5.11/4.8.7 (X11/Win)
ksergey85
Гость
« Ответ #2 : Июль 07, 2016, 09:42 »

Еще раз повторюсь, что вызов слота _q_layoutChanged() без предварительного вызова _q_layoutAboutToBeChanged() не вызывает проблем. Тут workaround никакой не нужен.
Проблема как раз в обратной ситуации когда _q_layoutAboutToBeChanged() вызывается, а _q_layoutChanged() нет. В этом случае список скрытых столбцов не будет никем использован и очищен тоже не будет и любое последующее срабатывание _q_layoutChanged() не важно в какой ситуации скорее всего приведет к скрытию тех столбцов, которые были помещены в список гораздо раньше. Не является ли это багом, о котором стоит сообщить разработчикам?
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #3 : Июль 07, 2016, 11:11 »

Не является ли это багом, о котором стоит сообщить разработчикам?
Является, поэтому и нужен workaround, ибо горизонтальный header никоим образом не должен реагировать на изменение строк.
И сей баг появился в Qt5.
Записан

Qt 5.11/4.8.7 (X11/Win)
ksergey85
Гость
« Ответ #4 : Июль 07, 2016, 16:31 »

Спасибо. Отправлю им на бугтрекер. Себе пока костыль workaround прилеплю.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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