How to select the first item in QListView with QFileSystemModel ?
-
Hello.
I have been trying to create a file manager using
QListView
andQFileSystemModel
. I'm stuck now because I wantQListView
to select the first item after the directory has loaded. I am using a slot to connect to theQFileSystemModel::directoryLoaded
signal. No matter what I do I can't seem to get this to work.Here is a gist of what I've tried:
connect(m_model, &QFileSystemModel::directoryLoaded, this, [&](const QString& path) { QModelIndex rootIndex = m_model->index(path); m_item_count = m_model->rowCount(rootIndex); // Select the first item if available if (m_item_count > 0) { QModelIndex firstItemIndex = m_model->index(0, 0, rootIndex); m_list_view->setCurrentIndex(firstItemIndex); m_list_view->selectionModel()->select(firstItemIndex, QItemSelectionModel::Select); } emit dirItemCount(m_item_count); });
This selects some random item in the list, and sometimes does not select at all.
I want this feature really badly in my file manager, since I want to make it keyboard driven, so having the first item be selected would be nice.
If anyone wants to check out my project, check https://github.com/dheerajshenoy/cometfm
-
Maybe check whether at the time when
directoryLoaded()
is called the model is populated or not. Perhaps you are trying to select the row too soon, before the model is ready with new data? -
As you can see in the code I provided, I am already doing this, but still it doesn't get the job done
-
No, I do not see this in code provided. I am suggesting you can try to run this code with a delay, for example using
QTimer::singleShot()
. -
-
It worked. I set a timeout of 100 ms. Thank you so much.
-
Maybe check whether at the time when
directoryLoaded()
is called the model is populated or not. Perhaps you are trying to select the row too soon, before the model is ready with new data?@sierdzio
Although this delay "apparently works", it's not the "right" solution :) 100ms may be enough in one case but not another.The question (to me at least) is what precisely directoryLoaded() is supposed to signify/when it is emitted:
This signal is emitted when the gatherer thread has finished to load the path.
I know that
QFileSystemModel
is implemented via a secondary thread which does the reading in the background. The "helpful" signal would be "this path has finished loading all its children/descendants, so you can now safely e.g. test its child count or read its children". But apparently this is not the case, from the OP's finding. It looks more likely to mean "have loaded this directory but not its children", which doesn't seem very useful to me....Does any expert know (a) what it actually means and (b) how should you do what the OP wants (children have been read/counted) in a proper, robust way?
P.S.
Quickly looking around, @eyllanesc --- who used to be a user here and whose answers are always really good --- discusses this in solution at https://stackoverflow.com/a/58759339/489865. He claims@pyqtSlot(str) def on_directoryLoaded(self, directory): parentIndex = self.model.index(directory) for row in range(self.model.rowCount(parentIndex)): index = self.model.index(row, 0, parentIndex) print(index, index.data())
works. Which is what I thought. There is no timer-delay. Is that code not the same as what OP has did here originally?
-
You can always connect to model reset signal or something and do away with the delay.
Regarding python code - looks similar indeed. Either there are some delays in python or perhaps all that is really needed is a single spin of the event loop. In such case
QTimer::singleShot()
should work even with0ms
delay. OrinvokeMethod().
can be used for the same effect. -
You can always connect to model reset signal or something and do away with the delay.
Regarding python code - looks similar indeed. Either there are some delays in python or perhaps all that is really needed is a single spin of the event loop. In such case
QTimer::singleShot()
should work even with0ms
delay. OrinvokeMethod().
can be used for the same effect.@sierdzio
I would have thought model reset would be emitted at the start of the population rather than the end. But not tested.If the Python code works without
QTimer::singleShot()
I would expect the C++ code to work too, and that is what I expected. But OP reports it does not. I might have a play/look at this over weekend, because it intrigues me that it does not work as-is/was....I'd still like an expert here to make a comment on what they would expect, or an explanation :)
UPDATE
Random idea from me :) I don't have Qt source code so it's hard to test what's going on, I have to just look at online source code :) At https://codebrowser.dev/qt5/qtbase/src/widgets/dialogs/qfilesystemmodel.cpp.html#2073 I happen to noticedelayedSortTimer.setSingleShot(true);
QFileSystemModel
supports sorting. The above seems to be the implementation. The Python code just prints out whatever it finds in whatever order, so doesn't care. But the OP here reportsThis selects some random item in the list, and sometimes does not select at all.
Ignoring the "sometimes does not select at all" (I don't know about that) I am guessing QFSM does some sorting of children on a singleshot timer, perhaps just after
directoryLoaded()
? Maybe the reason the OP sees a "random" item in the list selected on interceptingdirectoryLoaded()
is that at that point no sorting has happened yet and the first child is whatever happened to come back from the OS first? And so we need our own singleshot timer to get the sorted first item after the internal sort call has completed?@dheerajshenoy Out of interest:
- Are you sorting the model explicitly?
- Whether or not, does using
QTimer::singleShot(1)
or even(0)
always work just as well as100
?
All typed in by me on a wing and a prayer... ;-)
-
@JonB I am not sorting the model in any way. Anything below 100ms doesn't seem to work.
-
@JonB I am not sorting the model in any way. Anything below 100ms doesn't seem to work.
@dheerajshenoy
So for the sake of completeness, and for anyone reading this thread in future, I examined this. It comes out as I guessed. Ubuntu 24.04, supplied Qt 6.4.2, C++. Code:#include <QFileSystemModel> #include <QPushButton> class DemoB : public QPushButton { Q_OBJECT public: DemoB(QWidget *parent = nullptr); private: QFileSystemModel *model; private slots: void onDirectoryLoaded(const QString &directory); };
DemoB::DemoB(QWidget *parent) : QPushButton(parent) { model = new QFileSystemModel(this); connect(this, &QPushButton::clicked, model, [this]() { model->setRootPath(QDir::homePath()); }); connect(model, &QFileSystemModel::directoryLoaded, this, &DemoB::onDirectoryLoaded); connect(model, &QFileSystemModel::directoryLoaded, this, [this](const QString &directory) { QTimer::singleShot(1, this, [this, directory]() { onDirectoryLoaded(directory); }); }); } void DemoB::onDirectoryLoaded(const QString &directory) { qDebug() << directory; QModelIndex parentIndex = model->index(directory); qDebug() << model->rowCount(parentIndex) << parentIndex.data().toString(); for (int row = 0; row < model->rowCount(parentIndex); row++) { QModelIndex childIndex = model->index(row, 0, parentIndex); qDebug() << model->rowCount(childIndex) << childIndex.data().toString(); } qDebug() << "============================="; }
The first
connect()
, runningonDirectoryLoaded()
immediately on signaldirectoryLoaded()
, prints out all my home directory sub-directories & files in what seems like a "random" order. In fact the order is that in which each file/directory was originally created, as (not surprisingly) that will be the order of the child inodes laid out inside the parent. Under Linux this can be produced byls -f
orls -U
.The second
connect()
, runningonDirectoryLoaded()
on a timer delay on signaldirectoryLoaded()
, prints the same items out but now sorted per: sub-directories first, in alphabetical order, followed by files, in alphabetical order. This will be the "default" order you would expect to see things listed under Windows. Under Linux this can be produced byls --group-directories-first
. This is the order produced by the default "internal" sort order function applied in Qt QFSM code, on its own internal singleshot delay, if you do notsort()
explicitly.If the delay is
QTimer::singleShot(0)
then from the secondconnect()
sorting has not yet happened and items comes out in same order as with no delay. But a delay of just1
IS sufficient to make the second call see the sorted order, as I surmised.@dheerajshenoy does not say which platform they are on, which tends to imply Windows! It is possible that could require a longer delay than 1 millisecond, but I am "surprised" if that is really the case. Since they say 100 is required they will never know really whether that is too little or too much, and it could vary by directory contents. I would suggest they check their code again and make sure they really have tested
QTimer::singleShot(1)
correctly as an acceptable delay to produce the desired order. -
@JonB I am running on Linux. Just to be clear, I am only interested in highlighting the first item in the
QListView
. Also, to clarify, I have not set any sorting setting to the model or the listview.As per your suggestion, I tried
QTimer::singleShot(1)
. It highlights some element in the list which is not the first one (refer to the image attached below).Item highlight after loading directory:
Now that I think about it, I do not actually want to connect to the
directoryLoaded
signal. SinceQFileSystemModel
caches directory (?), whenever I change directory into an already loaded directory, thedirectoryLoaded
signal is not fired. Maybe I am wrong about this caching mechanism inQFileSystemModel
, I have to check the documentation.Edit: I was wrong about the
directoryLoaded
signal being cached and not fired when going back to a directory which was loaded earlier. I tested using a print statement inside the signal, and it seems to fire every time. -