Reimplementing QFileSystemModel Checkboxes using QMap inconsistent results
-
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 !
-
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 aQVariant
? Because if not this could produce "random" state value for your checkboxes....[EDIT: Looks like my suggestion was very wrong, sigh :( ]
-
@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.
-
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.txtfilePath in the loop returns this :
a.txt
b.txt
b.txtAnd c.txt does not get checked which is also odd.
-
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.