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. Reimplementing QFileSystemModel Checkboxes using QMap inconsistent results
Forum Updated to NodeBB v4.3 + New Features

Reimplementing QFileSystemModel Checkboxes using QMap inconsistent results

Scheduled Pinned Locked Moved Solved General and Desktop
qfilesystemmodeqtreeviewqabstractitemmoqsortfilterprox
15 Posts 3 Posters 3.0k Views 2 Watching
  • 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.
  • moffa13M Offline
    moffa13M Offline
    moffa13
    wrote on last edited by
    #1

    Hello, I'm trying to display a root directory with expandable children in a QTreeView.
    I'm using a QFileSystemModel. Everything was working fine, I reimplemented data, setData and flags to add checkboxes to the first column.

    To achieve that I had QSet<QPersistentModelIndex> which retains the checked boxes (set.contains(index) is true).
    However I wanted to have an intermediate state which is Qt::partiallyChecked. To do that, I had to change QSet to a QMap<QPersistantModelIndex, Qt::CheckState> but now, everytime I run this code, different checkboxes get checked like random even if there's nothing random, data should always return the same. Here's the code simplified to its maximum :

    #include "QCheckableFileSystemModel.h"
    #include <QAbstractItemModel>
    #include <QStack>
    #include <QtDebug>
    
    QCheckableFileSystemModel::QCheckableFileSystemModel(QObject* parent) : QFileSystemModel (parent)
    {}
    
    QVariant QCheckableFileSystemModel::data(const QModelIndex &index, int role) const{
    	// Item is disabled or it's a column we don't need
    	if((index.flags() & Qt::ItemIsEnabled) != Qt::ItemIsEnabled || index.column() != 0){
    		return QVariant{};
    	}
    
    	if(role == Qt::CheckStateRole && index.column() == 0){ // We need to return if item is checked or not
    		int checked = _indexesCheckedStates.contains(index) ? _indexesCheckedStates[index] : Qt::Unchecked;
    		return checked;
    	}
    	return QFileSystemModel::data(index, role); // Default behavior
    }
    
    bool QCheckableFileSystemModel::setData(const QModelIndex &index, const QVariant &value, int role){
    
    	if(role == Qt::CheckStateRole && index.column() == 0){
    
    		_indexesCheckedStates[index] = value.toInt();
    
    		emit dataChanged(index, index);
    		return true;
    	}
    	return QFileSystemModel::setData(index, value, role);
    }
    
    
    Qt::ItemFlags QCheckableFileSystemModel::flags(const QModelIndex &index) const{
    	return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
    }
    
    

    And here's the code which calls setData :

    QPersistentModelIndex rootIndex = _sortModel->mapFromSource(_model->setRootPath(_installDir.absolutePath()));
    
    // Wait for model to load
    QEventLoop loop;
    connect(_model, &QCheckableFileSystemModel::directoryLoaded, &loop, &QEventLoop::quit);
    loop.exec();
    
    
    QStack<QPersistentModelIndex> toProcess;
    toProcess.push(rootIndex);
    
    while(!toProcess.empty()){
    	QPersistentModelIndex toTreat = toProcess.pop();
    	if(_model->isDir(_sortModel->mapToSource(toTreat)) && _sortModel->rowCount(toTreat) > 0){
                    // this returns a set of paths which should be checked in the parent (always returns correct data)
    		QSet<QString> neededFiles = detectNeededFiles(
    					_model->filePath(_sortModel->mapToSource(toTreat)),
    					detectedMods,
    					true
    		);
    		for(int i = 0; i < _sortModel->rowCount(toTreat); ++i){
    			QPersistentModelIndex child{_sortModel->index(i, 0, toTreat)};
    			if(neededFiles.contains(_model->filePath(_sortModel->mapToSource(child)))){
    				qDebug() << _model->filePath(_sortModel->mapToSource(child));
    				_model->setData(_sortModel->mapToSource(child), Qt::Checked, Qt::CheckStateRole);
    			}
    			if(_model->isDir(_sortModel->mapToSource(child))){
    				_model->discover(_sortModel->mapToSource(child));
    				toProcess.push(child);
    			}
    
    		}
    	}
    }
    

    Here, _sortModel is the proxyfilter and _model my custom QFileSystemModel

    Note that I have a QSortFilterProxyModel to sort directories I don't want to see since it can't be done using setNamesFilter

    So I don't know what could cause this, maybe a race condition ? I know that QFileSystemModel loads its directories in a different thread but I always wait for the signal directoryLoaded (that's what discover does)

    void QCheckableFileSystemModel::discover(QModelIndex const& index){                           
    	while(canFetchMore(index)){
    		QEventLoop loop;
    		connect(this, &QFileSystemModel::directoryLoaded, &loop, &QEventLoop::quit);
    		fetchMore(index);
    		loop.exec();
    	}
    }
    

    Thank you for your answers !

    JonBJ 1 Reply Last reply
    0
    • moffa13M moffa13

      Hello, I'm trying to display a root directory with expandable children in a QTreeView.
      I'm using a QFileSystemModel. Everything was working fine, I reimplemented data, setData and flags to add checkboxes to the first column.

      To achieve that I had QSet<QPersistentModelIndex> which retains the checked boxes (set.contains(index) is true).
      However I wanted to have an intermediate state which is Qt::partiallyChecked. To do that, I had to change QSet to a QMap<QPersistantModelIndex, Qt::CheckState> but now, everytime I run this code, different checkboxes get checked like random even if there's nothing random, data should always return the same. Here's the code simplified to its maximum :

      #include "QCheckableFileSystemModel.h"
      #include <QAbstractItemModel>
      #include <QStack>
      #include <QtDebug>
      
      QCheckableFileSystemModel::QCheckableFileSystemModel(QObject* parent) : QFileSystemModel (parent)
      {}
      
      QVariant QCheckableFileSystemModel::data(const QModelIndex &index, int role) const{
      	// Item is disabled or it's a column we don't need
      	if((index.flags() & Qt::ItemIsEnabled) != Qt::ItemIsEnabled || index.column() != 0){
      		return QVariant{};
      	}
      
      	if(role == Qt::CheckStateRole && index.column() == 0){ // We need to return if item is checked or not
      		int checked = _indexesCheckedStates.contains(index) ? _indexesCheckedStates[index] : Qt::Unchecked;
      		return checked;
      	}
      	return QFileSystemModel::data(index, role); // Default behavior
      }
      
      bool QCheckableFileSystemModel::setData(const QModelIndex &index, const QVariant &value, int role){
      
      	if(role == Qt::CheckStateRole && index.column() == 0){
      
      		_indexesCheckedStates[index] = value.toInt();
      
      		emit dataChanged(index, index);
      		return true;
      	}
      	return QFileSystemModel::setData(index, value, role);
      }
      
      
      Qt::ItemFlags QCheckableFileSystemModel::flags(const QModelIndex &index) const{
      	return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
      }
      
      

      And here's the code which calls setData :

      QPersistentModelIndex rootIndex = _sortModel->mapFromSource(_model->setRootPath(_installDir.absolutePath()));
      
      // Wait for model to load
      QEventLoop loop;
      connect(_model, &QCheckableFileSystemModel::directoryLoaded, &loop, &QEventLoop::quit);
      loop.exec();
      
      
      QStack<QPersistentModelIndex> toProcess;
      toProcess.push(rootIndex);
      
      while(!toProcess.empty()){
      	QPersistentModelIndex toTreat = toProcess.pop();
      	if(_model->isDir(_sortModel->mapToSource(toTreat)) && _sortModel->rowCount(toTreat) > 0){
                      // this returns a set of paths which should be checked in the parent (always returns correct data)
      		QSet<QString> neededFiles = detectNeededFiles(
      					_model->filePath(_sortModel->mapToSource(toTreat)),
      					detectedMods,
      					true
      		);
      		for(int i = 0; i < _sortModel->rowCount(toTreat); ++i){
      			QPersistentModelIndex child{_sortModel->index(i, 0, toTreat)};
      			if(neededFiles.contains(_model->filePath(_sortModel->mapToSource(child)))){
      				qDebug() << _model->filePath(_sortModel->mapToSource(child));
      				_model->setData(_sortModel->mapToSource(child), Qt::Checked, Qt::CheckStateRole);
      			}
      			if(_model->isDir(_sortModel->mapToSource(child))){
      				_model->discover(_sortModel->mapToSource(child));
      				toProcess.push(child);
      			}
      
      		}
      	}
      }
      

      Here, _sortModel is the proxyfilter and _model my custom QFileSystemModel

      Note that I have a QSortFilterProxyModel to sort directories I don't want to see since it can't be done using setNamesFilter

      So I don't know what could cause this, maybe a race condition ? I know that QFileSystemModel loads its directories in a different thread but I always wait for the signal directoryLoaded (that's what discover does)

      void QCheckableFileSystemModel::discover(QModelIndex const& index){                           
      	while(canFetchMore(index)){
      		QEventLoop loop;
      		connect(this, &QFileSystemModel::directoryLoaded, &loop, &QEventLoop::quit);
      		fetchMore(index);
      		loop.exec();
      	}
      }
      

      Thank you for your answers !

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #2

      @moffa13

      different checkboxes get checked like random even if there's nothing random

      QVariant QCheckableFileSystemModel::data(const QModelIndex &index, int role) const{
      ...
      return checked;
      

      I am not a C++er, so this is either very right or very wrong! Are you supposed to return an int for a QVariant? Because if not this could produce "random" state value for your checkboxes....

      [EDIT: Looks like my suggestion was very wrong, sigh :( ]

      mrjjM 1 Reply Last reply
      1
      • JonBJ JonB

        @moffa13

        different checkboxes get checked like random even if there's nothing random

        QVariant QCheckableFileSystemModel::data(const QModelIndex &index, int role) const{
        ...
        return checked;
        

        I am not a C++er, so this is either very right or very wrong! Are you supposed to return an int for a QVariant? Because if not this could produce "random" state value for your checkboxes....

        [EDIT: Looks like my suggestion was very wrong, sigh :( ]

        mrjjM Offline
        mrjjM Offline
        mrjj
        Lifetime Qt Champion
        wrote on last edited by
        #3

        @JonB
        Very good question, but i think it will return a QVariant constructed from the
        int checked variable. Else compiler should be very unhappy if no automatic
        conversion can happen.

        1 Reply Last reply
        1
        • moffa13M Offline
          moffa13M Offline
          moffa13
          wrote on last edited by moffa13
          #4

          I am supposed to return a Qt::CheckState elem from enum which is int.

          Yes the compiler constructs a QVariant from the int value.

          So, any idea about what's going wrong here ?

          1 Reply Last reply
          0
          • moffa13M Offline
            moffa13M Offline
            moffa13
            wrote on last edited by
            #5

            Any help please

            mrjjM 1 Reply Last reply
            0
            • moffa13M moffa13

              Any help please

              mrjjM Offline
              mrjjM Offline
              mrjj
              Lifetime Qt Champion
              wrote on last edited by
              #6

              @moffa13
              Add some qDebug() statement to see what index are sent and what it set pr index.
              I think its one of those cases where debugger and tools are more useful than guessing looking at code.

              1 Reply Last reply
              1
              • moffa13M Offline
                moffa13M Offline
                moffa13
                wrote on last edited by
                #7

                @mrjj said in Reimplementing QFileSystemModel Checkboxes using QMap inconsistent results:

                @moffa13
                Add some qDebug() statement to see what index are sent and what it set pr index.
                I think its one of those cases where debugger and tools are more useful than guessing looking at code.

                If it was that simple I wouldn't have asked.

                qDebug is showing me correct paths before calling setData. The problem is that for whatever reason, some checkboxes won't get checked. I almost always get a different result whenever I run this code.

                qDebug in setData tells me that the function receives Qt::Checked but it's not the case.

                mrjjM 1 Reply Last reply
                0
                • moffa13M moffa13

                  @mrjj said in Reimplementing QFileSystemModel Checkboxes using QMap inconsistent results:

                  @moffa13
                  Add some qDebug() statement to see what index are sent and what it set pr index.
                  I think its one of those cases where debugger and tools are more useful than guessing looking at code.

                  If it was that simple I wouldn't have asked.

                  qDebug is showing me correct paths before calling setData. The problem is that for whatever reason, some checkboxes won't get checked. I almost always get a different result whenever I run this code.

                  qDebug in setData tells me that the function receives Qt::Checked but it's not the case.

                  mrjjM Offline
                  mrjjM Offline
                  mrjj
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  @moffa13
                  Hi
                  Hmm, i would suspect the QSet<QPersistentModelIndex>
                  could contain incorrect indexes.
                  Im wondering if QFileSystemModel might invalidate QPersistentModelIndexes i have seen with QSqlTableModel.

                  moffa13M 1 Reply Last reply
                  0
                  • mrjjM mrjj

                    @moffa13
                    Hi
                    Hmm, i would suspect the QSet<QPersistentModelIndex>
                    could contain incorrect indexes.
                    Im wondering if QFileSystemModel might invalidate QPersistentModelIndexes i have seen with QSqlTableModel.

                    moffa13M Offline
                    moffa13M Offline
                    moffa13
                    wrote on last edited by
                    #9

                    @mrjj It only has this behavior when I use QMap if I do this with QSet everything works fine. That's what I don't get

                    mrjjM 1 Reply Last reply
                    0
                    • moffa13M moffa13

                      @mrjj It only has this behavior when I use QMap if I do this with QSet everything works fine. That's what I don't get

                      mrjjM Offline
                      mrjjM Offline
                      mrjj
                      Lifetime Qt Champion
                      wrote on last edited by
                      #10

                      @moffa13
                      Hmm. yes that is odd.
                      One difference with Set/QMap is if u ask QMap for non existing value
                      a default-constructed value is returned but you seem to check with contains so
                      should not happen?

                      1 Reply Last reply
                      0
                      • moffa13M Offline
                        moffa13M Offline
                        moffa13
                        wrote on last edited by
                        #11

                        @mrjj

                        Yeah, exactly, for me it should work.

                        However I noticed something which may be useful. I added a QEventLoop when doing setData to look for the dataChanged signal but when I do this and I use qDebug to show the files returned by model::index, I get something like this :

                        Normal dir is
                        a.txt
                        b.txt
                        c.txt

                        filePath in the loop returns this :

                        a.txt
                        b.txt
                        b.txt

                        And c.txt does not get checked which is also odd.

                        mrjjM 1 Reply Last reply
                        0
                        • moffa13M moffa13

                          @mrjj

                          Yeah, exactly, for me it should work.

                          However I noticed something which may be useful. I added a QEventLoop when doing setData to look for the dataChanged signal but when I do this and I use qDebug to show the files returned by model::index, I get something like this :

                          Normal dir is
                          a.txt
                          b.txt
                          c.txt

                          filePath in the loop returns this :

                          a.txt
                          b.txt
                          b.txt

                          And c.txt does not get checked which is also odd.

                          mrjjM Offline
                          mrjjM Offline
                          mrjj
                          Lifetime Qt Champion
                          wrote on last edited by
                          #12

                          @moffa13
                          Hmm. you know how b.txt is included twice ?

                          moffa13M 1 Reply Last reply
                          0
                          • mrjjM mrjj

                            @moffa13
                            Hmm. you know how b.txt is included twice ?

                            moffa13M Offline
                            moffa13M Offline
                            moffa13
                            wrote on last edited by
                            #13

                            @mrjj this is absolutely not normal. It' a for loop iterating from 0 to rowCount(parent). Then it uses model->index(i, 0, parent). So this is not possible to output twice the same file.

                            mrjjM 1 Reply Last reply
                            0
                            • moffa13M moffa13

                              @mrjj this is absolutely not normal. It' a for loop iterating from 0 to rowCount(parent). Then it uses model->index(i, 0, parent). So this is not possible to output twice the same file.

                              mrjjM Offline
                              mrjjM Offline
                              mrjj
                              Lifetime Qt Champion
                              wrote on last edited by
                              #14

                              @moffa13
                              Could you try to do the same for loop from say a button and use
                              no localEvent loop or anything like that and see if its
                              reproducible in other context ?

                              1 Reply Last reply
                              0
                              • moffa13M Offline
                                moffa13M Offline
                                moffa13
                                wrote on last edited by
                                #15

                                Ok I found what was causing this awful bug.

                                When I check if the map contains the index, I actually check the raw index not the QPersistantModelIndex so this is not the same object and I think qmap does not check equality using == operator .

                                So I have to iterate over the map and check using QPersistantModelIndex "==" operators which can compare from a QModelIndex.

                                1 Reply Last reply
                                1

                                • Login

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