Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QTreeView header shows/hides incorrect columns after setRootIndex
Qt 6.11 is out! See what's new in the release blog

QTreeView header shows/hides incorrect columns after setRootIndex

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 2 Posters 1.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • F Offline
    F Offline
    FrancescoK
    wrote on last edited by FrancescoK
    #1

    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.
    Thanks

    My 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_H
    
    

    mainwindow.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>
    
    
    VRoninV 1 Reply Last reply
    0
    • F Offline
      F Offline
      FrancescoK
      wrote on last edited by FrancescoK
      #3

      Hello VRonin, I tried your suggestion, but somehow it breaks things:

      Before adding the new root item:
      Schermata del 2021-05-24 12-10-53.png

      After:
      Schermata del 2021-05-24 12-11-09.png

      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

      VRoninV 1 Reply Last reply
      1
      • F FrancescoK

        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.
        Thanks

        My 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_H
        
        

        mainwindow.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>
        
        
        VRoninV Offline
        VRoninV Offline
        VRonin
        wrote on last edited by VRonin
        #2

        Change MainWindow::on_addButton_clicked() to

        void 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 of columnCount() then you can subclass QSortFilterProxyModel and reimplement filterAcceptsColumn

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        1 Reply Last reply
        0
        • F Offline
          F Offline
          FrancescoK
          wrote on last edited by FrancescoK
          #3

          Hello VRonin, I tried your suggestion, but somehow it breaks things:

          Before adding the new root item:
          Schermata del 2021-05-24 12-10-53.png

          After:
          Schermata del 2021-05-24 12-11-09.png

          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

          VRoninV 1 Reply Last reply
          1
          • F FrancescoK

            Hello VRonin, I tried your suggestion, but somehow it breaks things:

            Before adding the new root item:
            Schermata del 2021-05-24 12-10-53.png

            After:
            Schermata del 2021-05-24 12-11-09.png

            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

            VRoninV Offline
            VRoninV Offline
            VRonin
            wrote on last edited by
            #4

            @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 insertColumns

            QTreeView will fall back to the invisible root of the model

            Where would you want it to go?

            "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
            ~Napoleon Bonaparte

            On a crusade to banish setIndexWidget() from the holy land of Qt

            1 Reply Last reply
            0
            • F Offline
              F Offline
              FrancescoK
              wrote on last edited by
              #5

              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

              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved