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. Problem with QTreeView, custom tree model and proxy model

Problem with QTreeView, custom tree model and proxy model

Scheduled Pinned Locked Moved Solved General and Desktop
qtreeviewqsorfilterproxymodelviewqabstractitemmo
6 Posts 3 Posters 5.0k 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.
  • Y Offline
    Y Offline
    YuriQ
    wrote on 30 Dec 2016, 10:06 last edited by YuriQ
    #1

    Hello.
    I have rather hard problem. I need to implement my own tree data model (based on QAbstractItemModel) to use with QTreeView and QSortFilterProxyModel. I did it. It work almost ok but in some cases I can't use QTreeView's ability to select сurrent cell and scroll to cell. I don't know is it result of wrong model implementation or is it Qt bug.

    Test cases:

    1. QTreeView + my model with 1 column - works fine
    2. QTreeView + my model with 2 columns - works fine
    3. QTreeView + proxy model + my model with 1 column - works fine
    4. QTreeView + proxy model + my model with 2 columns - works only first few times

    Seems like proxy model can't handle my model properly. Can you please take a look at (or even try to run) sample code? Maybe you will see something that I can't see. Like wrong model implementation or something different.

    You can choose columns count with first macro option.
    You can choose to use or not to use proxy model with second macro option.
    You can test desired functionality (QTreeView::setCurrentIndex and QTreeView::scrollTo) with button.

    I tried to make example program as simple as possible so here it is (this is one single source file, may require to run qmake before compilation):

    // ---------------- Test options
    
    // Option 1: Choose 1 or 2
    #define COLUMNS_COUNT 2
    
    // Option 2: Comment or uncomment line below
    #define USE_PROXY
    
    // ---------------- Include
    
    #include <QAbstractItemModel>
    #ifdef USE_PROXY
    #include <QSortFilterProxyModel>
    #endif
    #include <QTreeView>
    #include <QPushButton>
    #include <QApplication>
    #include <QVBoxLayout>
    
    // ---------------- Model
    
    class CPersonAndFriends
    {
    public:
        QString     name;
        QStringList friends;
    };
    
    class CModel: public QAbstractItemModel
    {
        Q_OBJECT
    public:
        CModel(QObject* parent);
    
        virtual int         rowCount   (const QModelIndex& parentIndex = QModelIndex()) const;
        virtual int         columnCount(const QModelIndex& parentIndex = QModelIndex()) const;
        virtual QModelIndex index      (int row, int column, const QModelIndex& parentIndex = QModelIndex()) const;
        virtual QModelIndex parent     (const QModelIndex& itemIndex) const;
        virtual QVariant    data       (const QModelIndex& itemIndex, int role = Qt::DisplayRole) const;
    
        void appendPerson(QString personName);
        void appendFriend(QString friendName);
    
    private:
        QList<CPersonAndFriends> modelData;
    };
    
    CModel::CModel(QObject* parent): QAbstractItemModel(parent)
    {
    }
    
    int CModel::rowCount(const QModelIndex& parentIndex) const
    {
        if (!parentIndex.isValid())
            return modelData.size();
        else
        if(!parentIndex.parent().isValid())
            return modelData.at(parentIndex.row()).friends.size();
    
        return 0;
    }
    
    int CModel::columnCount(const QModelIndex&) const
    {
        return COLUMNS_COUNT;
    }
    
    QModelIndex CModel::index(int row, int column, const QModelIndex& parentIndex) const
    {
        if (!parentIndex.isValid())
            return createIndex(row, column, (void*)(0));
        else
            return createIndex(row, column, (void*)(parentIndex.row()+1));
    }
    
    QModelIndex CModel::parent(const QModelIndex& itemIndex) const
    {
        int row = (int)itemIndex.internalPointer();
    
        if (row == 0)
            return QModelIndex();
        else
            return index(row-1, 0);
    }
    
    QVariant CModel::data(const QModelIndex& itemIndex, int role) const
    {
        if (itemIndex.isValid() &&
            role == Qt::DisplayRole)
        {
            if (!itemIndex.parent().isValid())
                return modelData.at(itemIndex.row()).name;
            else if (!itemIndex.parent().parent().isValid())
                return modelData.at(itemIndex.parent().row()).friends.at(itemIndex.row());
        }
        return QVariant();
    }
    
    void CModel::appendPerson(QString personName)
    {
        int firstLast = modelData.size();
        CPersonAndFriends item;
        item.name = personName;
    
        beginInsertRows(QModelIndex(), firstLast, firstLast);
        modelData << item;
        endInsertRows();
    }
    
    void CModel::appendFriend(QString friendName)
    {
        if (modelData.size() == 0)
            return;
    
        int row = modelData.size()-1;
        CPersonAndFriends& item = modelData[row];
        QModelIndex parentIndex = index(row, 0);
        int firstLast = rowCount(parentIndex);
    
        beginInsertRows(parentIndex, firstLast, firstLast);
        item.friends << friendName;
        endInsertRows();
    }
    
    // ---------------- Window
    
    class CWidget: public QWidget
    {
        Q_OBJECT
    public:
        CWidget(QWidget* parent = 0);
    private:
    #ifdef USE_PROXY
        QSortFilterProxyModel* proxy;
    #endif
        QTreeView   view;
        CModel*     model;
        QPushButton button;
    private slots:
        void selectNextItem();
    };
    
    CWidget::CWidget(QWidget* parent): QWidget(parent)
    {
        setLayout(new QVBoxLayout);
        layout()->addWidget(&view);
        model = new CModel(this);
    #ifdef USE_PROXY
        proxy = new QSortFilterProxyModel(this);
        proxy->setSourceModel(model);
        view.setModel(proxy);
    #else
        view.setModel(model);
    #endif
    
        layout()->addWidget(&button);
        button.setText("Select next item");
        connect(&button, &QPushButton::clicked, this, &CWidget::selectNextItem);
    
        model->appendPerson("Homer");
        model->appendFriend("Moe");
        model->appendFriend("Barney");
        model->appendPerson("Philipe");
        model->appendFriend("Amy");
        model->appendFriend("Bender");
    }
    
    void CWidget::selectNextItem()
    {
    #ifdef USE_PROXY
        QAbstractItemModel* mdl = proxy;
    #else
        QAbstractItemModel* mdl = model;
    #endif
    
        QModelIndex currentIndex = view.selectionModel()->currentIndex();
    
        if (!currentIndex.isValid())
            currentIndex = mdl->index(0, COLUMNS_COUNT-1);
    
    #ifdef USE_PROXY
        if (currentIndex.model() == model)
            currentIndex = proxy->mapFromSource(currentIndex);
        Q_ASSERT(currentIndex.model() == proxy);
    #endif
    
        if (currentIndex.parent() == QModelIndex())
        {
            if (mdl->rowCount(currentIndex))
                currentIndex = mdl->index(0, COLUMNS_COUNT-1, currentIndex);
            else
            {
                int nextRow = currentIndex.row() + 1;
                if (nextRow >= mdl->rowCount())
                    nextRow = 0;
                currentIndex = mdl->index(nextRow, COLUMNS_COUNT-1);
            }
        }
        else
        {
            int nextRow = currentIndex.row() + 1;
            if (nextRow >= mdl->rowCount(currentIndex.parent()))
            {
                nextRow = currentIndex.parent().row() + 1;
                if (nextRow >= mdl->rowCount())
                    nextRow = 0;
                currentIndex = mdl->index(nextRow, COLUMNS_COUNT-1);
            }
            else
                currentIndex = mdl->index(nextRow, COLUMNS_COUNT-1, currentIndex.parent());
        }
    
    #ifdef USE_PROXY
        Q_ASSERT(currentIndex.model() == proxy);
    #endif
    
        // Commands below will not work for case of proxy model and 2 columns
    
        view.setCurrentIndex(currentIndex);
        view.scrollTo(currentIndex);
    }
    
    // ---------------- Main function
    
    #include "main.moc"
    
    int main( int argc, char *argv[] )
    {
        QApplication oApplication( argc, argv );
        CWidget oVMainWindow;
        oVMainWindow.show();
        return oApplication.exec();
    }
    
    

    It was tested with Qt 5.6.1 under windows.

    I tried to investigate it with no success. Internals of Qt are complicated, you know... All I found is QTreeView::scrollTo function tries to get viewIndex(index) and fails.

    // qtreeview:cpp - QTreeView::scrollTo
        int item = d->viewIndex(index);
        if (item < 0) // item is -1 in my case but index is valid
            return;
    

    UPD:
    possible related topic: http://stackoverflow.com/questions/17529654/qtreeviewscrollto-not-working
    another one: https://forum.qt.io/topic/58517/qtableview-scrollto-doesn-t-scroll-when-using-proxy
    But I check model of index so it is not my case.

    1 Reply Last reply
    0
    • VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on 30 Dec 2016, 11:42 last edited by VRonin
      #2

      No bug in Qt, just a bit of confusion with parents...
      The problem is in this snippet:

      if (nextRow >= mdl->rowCount(currentIndex.parent()))
              {
                  nextRow = currentIndex.parent().row() + 1;
                  if (nextRow >= mdl->rowCount())
                      nextRow = 0;
                  currentIndex = mdl->index(nextRow, COLUMNS_COUNT-1);
              }
      

      when the first if is true you are setting the parent to index(nextRow,1,QModelIndex()) but this index has no children ( index(nextRow,0,QModelIndex()) is the one that has children) so when you then call currentIndex = mdl->index(0, COLUMNS_COUNT-1, currentIndex); the result is a null index

      to solve change currentIndex = mdl->index(0, COLUMNS_COUNT-1, currentIndex); into currentIndex = mdl->index(0, COLUMNS_COUNT-1, mdl->index(currentIndex.row(),0));

      P.S.
      When reimplementing QAbstractItemModel is always wise to make it run through the model test to make sure you did all the internals correctly.

      "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

      Y 1 Reply Last reply 30 Dec 2016, 13:22
      4
      • Y Offline
        Y Offline
        YuriQ
        wrote on 30 Dec 2016, 13:20 last edited by
        #3

        Perfect! Big thanks for your time and solution.
        I feel like I was not attentive enough while inspecting my own code.

        1 Reply Last reply
        0
        • VRoninV VRonin
          30 Dec 2016, 11:42

          No bug in Qt, just a bit of confusion with parents...
          The problem is in this snippet:

          if (nextRow >= mdl->rowCount(currentIndex.parent()))
                  {
                      nextRow = currentIndex.parent().row() + 1;
                      if (nextRow >= mdl->rowCount())
                          nextRow = 0;
                      currentIndex = mdl->index(nextRow, COLUMNS_COUNT-1);
                  }
          

          when the first if is true you are setting the parent to index(nextRow,1,QModelIndex()) but this index has no children ( index(nextRow,0,QModelIndex()) is the one that has children) so when you then call currentIndex = mdl->index(0, COLUMNS_COUNT-1, currentIndex); the result is a null index

          to solve change currentIndex = mdl->index(0, COLUMNS_COUNT-1, currentIndex); into currentIndex = mdl->index(0, COLUMNS_COUNT-1, mdl->index(currentIndex.row(),0));

          P.S.
          When reimplementing QAbstractItemModel is always wise to make it run through the model test to make sure you did all the internals correctly.

          Y Offline
          Y Offline
          YuriQ
          wrote on 30 Dec 2016, 13:22 last edited by
          #4

          @VRonin Didn't know about model test. Seems like useful thing. Thanks.

          1 Reply Last reply
          0
          • michalosM Offline
            michalosM Offline
            michalos
            wrote on 30 Dec 2016, 14:13 last edited by
            #5

            Do You know where can I find source code for the model test, that works with Qt 5.7?
            Ive downloaded the official, but can't figure out how to build it.
            main problem is with: load(qttest_p4)

            Y 1 Reply Last reply 30 Dec 2016, 14:35
            0
            • michalosM michalos
              30 Dec 2016, 14:13

              Do You know where can I find source code for the model test, that works with Qt 5.7?
              Ive downloaded the official, but can't figure out how to build it.
              main problem is with: load(qttest_p4)

              Y Offline
              Y Offline
              YuriQ
              wrote on 30 Dec 2016, 14:35 last edited by
              #6

              @michalos I'm not sure if I can help you but seems like model test code works for me.

              What I did:

              1. Added modeltest.h and cpp into my project
              2. Added "testlib" into "QT" section of project file
              3. Added this to modeltest.h:
              #include <QtTest/qtestcase.h>
              #define qVariantCanConvert(_type_, _var_) _var_.canConvert(QMetaType::_type_)
              
              1. Changed expressions like "qVariantCanConvert<QString> ( variant)" to "qVariantCanConvert(QString, variant)" in modeltest.h

              And... my sample model failed to pass the test of course :D

              1 Reply Last reply
              1

              1/6

              30 Dec 2016, 10:06

              • Login

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