QTreeView header shows/hides incorrect columns after setRootIndex
-
Hello,
I'm trying to build an application that will show the elements of a model in two different views: a QListView that will show only the root elements of the model, and a QTreeView that will show only the child elements of the root element selected in the QListView.
In addition to it, I have to implement filtering on the child elements of the QTreeView, so I have to rely on a QSortFilterProxyModel on the QTreeView:
model = new QStandardItemModel(this); ui->listView->setModel(model); proxy = new QSortFilterProxyModel(this); proxy->setSourceModel(model); ui->treeView->setModel(proxy);Every time the current index of QListView changes, I call setRootIndex() on the QTreeView.
Contrary to the root elements, the child items of the model have several columns, some hidden and some shown.
ui->treeView->setColumnHidden(1, true); ui->treeView->setColumnHidden(2, true); ui->treeView->setColumnHidden(3, true);Now the problem: every time I add a new root element to the model, the QTreeView will be emptied (correct, because the new root element has 0 children), but the header disappears (wrong!). And if I select a different root element (with some children), the header will be back, but this time with all columns shown (very wrong!).
I'm very confused about it and I don't know what causes this behavior. I do know that I can force the setColumnHidden() every time I change the current index, but I can't help myself to think that there is a better solution for this problem somewhere. Especially because I don't understand why this is happening.
For replicate the problem: click on a collection in the QListView (e.g. Collection1, this is the desired behaviour), click the ADD button, click on the new created collection (Collection4) in the QListView (the header disappears), and then click on another collection (e.g. Collection3) in the QListView (the header is back, but with wrong columns).
Does somebody know something? Any help is very appriciated.
ThanksMy code:
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QStandardItemModel> #include <QSortFilterProxyModel> #include <QDebug> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_addButton_clicked(); void on_removeButton_clicked(); void on_lineEdit_textChanged(const QString &arg1); void on_currentChanged(const QModelIndex, const QModelIndex); private: Ui::MainWindow *ui; QStandardItemModel* model = nullptr; QSortFilterProxyModel* proxy = nullptr; }; #endif // MAINWINDOW_Hmainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); model = new QStandardItemModel(this); ui->listView->setModel(model); proxy = new QSortFilterProxyModel(this); proxy->setSourceModel(model); proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); proxy->setRecursiveFilteringEnabled(true); ui->treeView->setModel(proxy); ui->treeView->setAllColumnsShowFocus(true); ui->treeView->header()->setSectionResizeMode(QHeaderView::Stretch); connect (ui->listView->selectionModel(), SIGNAL(currentChanged(const QModelIndex, const QModelIndex) ), this, SLOT(on_currentChanged(const QModelIndex, const QModelIndex) )); QStringList labels; labels << "A" << "B" << "C" << "D"; model->setHorizontalHeaderLabels(labels); for (int i = 0; i < 4; i ++) { QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); model->invisibleRootItem()->appendRow(collection); QStandardItem* item = new QStandardItem(QString("Item%1").arg(i)); QList<QStandardItem*> list; list << item; list << new QStandardItem(QString()); list << new QStandardItem(QString::number(-1)); list << new QStandardItem(QString::number(-1)); collection->appendRow(list); } ui->treeView->setColumnHidden(1, true); ui->treeView->setColumnHidden(2, true); ui->treeView->setColumnHidden(3, true); //ui->treeView->header()->showSection(1); //ui->treeView->header()->showSection(2); //ui->treeView->header()->showSection(3); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_addButton_clicked() { int i = model->rowCount(); QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); model->invisibleRootItem()->appendRow(collection); } void MainWindow::on_removeButton_clicked() { if (ui->listView->selectionModel()->currentIndex().isValid()) { model->removeRow(ui->listView->selectionModel()->currentIndex().row()); } } void MainWindow::on_lineEdit_textChanged(const QString &arg1){ proxy->setFilterFixedString(arg1); } void MainWindow::on_currentChanged(const QModelIndex index, const QModelIndex) { ui->treeView->setRootIndex(proxy->mapFromSource(index)); //ui->treeView->setColumnHidden(1, true); //ui->treeView->setColumnHidden(2, true); //ui->treeView->setColumnHidden(3, true); }mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralWidget"> <layout class="QGridLayout" name="gridLayout"> <item row="3" column="1"> <widget class="QPushButton" name="removeButton"> <property name="text"> <string>REMOVE</string> </property> </widget> </item> <item row="1" column="1"> <widget class="QLineEdit" name="lineEdit"/> </item> <item row="3" column="0"> <widget class="QPushButton" name="addButton"> <property name="text"> <string>ADD</string> </property> </widget> </item> <item row="2" column="1"> <widget class="QTreeView" name="treeView"/> </item> <item row="1" column="0" rowspan="2"> <widget class="QListView" name="listView"/> </item> </layout> </widget> <widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>22</height> </rect> </property> </widget> <widget class="QToolBar" name="mainToolBar"> <attribute name="toolBarArea"> <enum>TopToolBarArea</enum> </attribute> <attribute name="toolBarBreak"> <bool>false</bool> </attribute> </widget> <widget class="QStatusBar" name="statusBar"/> </widget> <layoutdefault spacing="6" margin="11"/> <resources/> <connections/> </ui> -
Hello VRonin, I tried your suggestion, but somehow it breaks things:
Before adding the new root item:

After:

But you give me a hint on which is wrong in my code. I simply added setColumnCount(4); to my function, and now it works flawlessy
void MainWindow::on_addButton_clicked() { int i = model->rowCount(); QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); collection->setColumnCount(4); // <- This is important! model->invisibleRootItem()->appendRow(collection); }Many thanks for the help :)
Now I will try to implement filtering/sorting. The challenge is that when a root item is filtered out, QTreeView will fall back to the invisible root of the model. I will try to set a null model when the current root item is filtered out, and then bring it back.Cheers
-
Hello,
I'm trying to build an application that will show the elements of a model in two different views: a QListView that will show only the root elements of the model, and a QTreeView that will show only the child elements of the root element selected in the QListView.
In addition to it, I have to implement filtering on the child elements of the QTreeView, so I have to rely on a QSortFilterProxyModel on the QTreeView:
model = new QStandardItemModel(this); ui->listView->setModel(model); proxy = new QSortFilterProxyModel(this); proxy->setSourceModel(model); ui->treeView->setModel(proxy);Every time the current index of QListView changes, I call setRootIndex() on the QTreeView.
Contrary to the root elements, the child items of the model have several columns, some hidden and some shown.
ui->treeView->setColumnHidden(1, true); ui->treeView->setColumnHidden(2, true); ui->treeView->setColumnHidden(3, true);Now the problem: every time I add a new root element to the model, the QTreeView will be emptied (correct, because the new root element has 0 children), but the header disappears (wrong!). And if I select a different root element (with some children), the header will be back, but this time with all columns shown (very wrong!).
I'm very confused about it and I don't know what causes this behavior. I do know that I can force the setColumnHidden() every time I change the current index, but I can't help myself to think that there is a better solution for this problem somewhere. Especially because I don't understand why this is happening.
For replicate the problem: click on a collection in the QListView (e.g. Collection1, this is the desired behaviour), click the ADD button, click on the new created collection (Collection4) in the QListView (the header disappears), and then click on another collection (e.g. Collection3) in the QListView (the header is back, but with wrong columns).
Does somebody know something? Any help is very appriciated.
ThanksMy code:
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QStandardItemModel> #include <QSortFilterProxyModel> #include <QDebug> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_addButton_clicked(); void on_removeButton_clicked(); void on_lineEdit_textChanged(const QString &arg1); void on_currentChanged(const QModelIndex, const QModelIndex); private: Ui::MainWindow *ui; QStandardItemModel* model = nullptr; QSortFilterProxyModel* proxy = nullptr; }; #endif // MAINWINDOW_Hmainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); model = new QStandardItemModel(this); ui->listView->setModel(model); proxy = new QSortFilterProxyModel(this); proxy->setSourceModel(model); proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); proxy->setRecursiveFilteringEnabled(true); ui->treeView->setModel(proxy); ui->treeView->setAllColumnsShowFocus(true); ui->treeView->header()->setSectionResizeMode(QHeaderView::Stretch); connect (ui->listView->selectionModel(), SIGNAL(currentChanged(const QModelIndex, const QModelIndex) ), this, SLOT(on_currentChanged(const QModelIndex, const QModelIndex) )); QStringList labels; labels << "A" << "B" << "C" << "D"; model->setHorizontalHeaderLabels(labels); for (int i = 0; i < 4; i ++) { QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); model->invisibleRootItem()->appendRow(collection); QStandardItem* item = new QStandardItem(QString("Item%1").arg(i)); QList<QStandardItem*> list; list << item; list << new QStandardItem(QString()); list << new QStandardItem(QString::number(-1)); list << new QStandardItem(QString::number(-1)); collection->appendRow(list); } ui->treeView->setColumnHidden(1, true); ui->treeView->setColumnHidden(2, true); ui->treeView->setColumnHidden(3, true); //ui->treeView->header()->showSection(1); //ui->treeView->header()->showSection(2); //ui->treeView->header()->showSection(3); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_addButton_clicked() { int i = model->rowCount(); QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); model->invisibleRootItem()->appendRow(collection); } void MainWindow::on_removeButton_clicked() { if (ui->listView->selectionModel()->currentIndex().isValid()) { model->removeRow(ui->listView->selectionModel()->currentIndex().row()); } } void MainWindow::on_lineEdit_textChanged(const QString &arg1){ proxy->setFilterFixedString(arg1); } void MainWindow::on_currentChanged(const QModelIndex index, const QModelIndex) { ui->treeView->setRootIndex(proxy->mapFromSource(index)); //ui->treeView->setColumnHidden(1, true); //ui->treeView->setColumnHidden(2, true); //ui->treeView->setColumnHidden(3, true); }mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralWidget"> <layout class="QGridLayout" name="gridLayout"> <item row="3" column="1"> <widget class="QPushButton" name="removeButton"> <property name="text"> <string>REMOVE</string> </property> </widget> </item> <item row="1" column="1"> <widget class="QLineEdit" name="lineEdit"/> </item> <item row="3" column="0"> <widget class="QPushButton" name="addButton"> <property name="text"> <string>ADD</string> </property> </widget> </item> <item row="2" column="1"> <widget class="QTreeView" name="treeView"/> </item> <item row="1" column="0" rowspan="2"> <widget class="QListView" name="listView"/> </item> </layout> </widget> <widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>22</height> </rect> </property> </widget> <widget class="QToolBar" name="mainToolBar"> <attribute name="toolBarArea"> <enum>TopToolBarArea</enum> </attribute> <attribute name="toolBarBreak"> <bool>false</bool> </attribute> </widget> <widget class="QStatusBar" name="statusBar"/> </widget> <layoutdefault spacing="6" margin="11"/> <resources/> <connections/> </ui>Change
MainWindow::on_addButton_clicked()tovoid MainWindow::on_addButton_clicked() { const int i = model->rowCount(); model->insertRow(i); const QModelIndex addedRoot = model->index(i,0); model->insertColumns(0,4,addedRoot); model->setData(addedRoot,tr("Collection%1").arg(locale().toString(i)); }Long explanation: the new row items have no columns and no rows. To determine how many headers to show, the view will call
model->columnCount(rootIndex());if that returns 0 then the headers will not be shown and also the information about the hidden columns is lost (as there are no columns so the hidden indexes stored are now invalid and get discarded).
This does not apply to your case but if you want to keep the hidden columns hidden regardless ofcolumnCount()then you can subclassQSortFilterProxyModeland reimplementfilterAcceptsColumn -
Hello VRonin, I tried your suggestion, but somehow it breaks things:
Before adding the new root item:

After:

But you give me a hint on which is wrong in my code. I simply added setColumnCount(4); to my function, and now it works flawlessy
void MainWindow::on_addButton_clicked() { int i = model->rowCount(); QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); collection->setColumnCount(4); // <- This is important! model->invisibleRootItem()->appendRow(collection); }Many thanks for the help :)
Now I will try to implement filtering/sorting. The challenge is that when a root item is filtered out, QTreeView will fall back to the invisible root of the model. I will try to set a null model when the current root item is filtered out, and then bring it back.Cheers
-
Hello VRonin, I tried your suggestion, but somehow it breaks things:
Before adding the new root item:

After:

But you give me a hint on which is wrong in my code. I simply added setColumnCount(4); to my function, and now it works flawlessy
void MainWindow::on_addButton_clicked() { int i = model->rowCount(); QStandardItem* collection = new QStandardItem(QString("Collection%1").arg(i)); collection->setColumnCount(4); // <- This is important! model->invisibleRootItem()->appendRow(collection); }Many thanks for the help :)
Now I will try to implement filtering/sorting. The challenge is that when a root item is filtered out, QTreeView will fall back to the invisible root of the model. I will try to set a null model when the current root item is filtered out, and then bring it back.Cheers
@FrancescoK said in QTreeView header shows/hides incorrect columns after setRootIndex:
but somehow it breaks things:
I fixed it now, I forgot the 3rd argument to
insertColumnsQTreeView will fall back to the invisible root of the model
Where would you want it to go?
-
To an empty model with the same number of columns and the same header labels.
// // Model // QStringList labels; labels << "A" << "B" << "C" << "D"; model = new QStandardItemModel(this); model->setColumnCount(4); model->setHorizontalHeaderLabels(labels); ui->collectionsListView->setModel(model); // // Empty Model // empty = new QStandardItemModel(this); empty->setColumnCount(4); empty->setHorizontalHeaderLabels(labels);So, if the current root item selected in the QListView is filtered out in the proxy model, the QTreView shows nothing, but mainteins the header.
Something like this:
// New root QModelIndex new_root = proxy->mapFromSource(index); if (new_root.isValid()) { // Valid root if (ui->itemsTreeView->model() != proxy) { QItemSelectionModel *m = ui->itemsTreeView->selectionModel(); ui->itemsTreeView->setModel(proxy); // This function will create and set a new selection model, replacing any model that was previously set with setSelectionModel(). delete m; connect (ui->itemsTreeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex, const QModelIndex) ), this, SLOT(on_currentChanged(const QModelIndex, const QModelIndex) )); } ui->itemsTreeView->setRootIndex(new_root); } else { // Invalid root if (ui->itemsTreeView->model() != empty) { QItemSelectionModel* m = ui->itemsTreeView->selectionModel(); ui->itemsTreeView->setModel(empty); delete m; } }But it needs more polishing