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. How to select the first item in QListView with QFileSystemModel ?
Forum Updated to NodeBB v4.3 + New Features

How to select the first item in QListView with QFileSystemModel ?

Scheduled Pinned Locked Moved Unsolved General and Desktop
11 Posts 3 Posters 768 Views 3 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.
  • D Offline
    D Offline
    dheerajshenoy
    wrote on last edited by dheerajshenoy
    #1

    Hello.

    I have been trying to create a file manager using QListView and QFileSystemModel . I'm stuck now because I want QListView to select the first item after the directory has loaded. I am using a slot to connect to the QFileSystemModel::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

    1 Reply Last reply
    0
    • sierdzioS Offline
      sierdzioS Offline
      sierdzio
      Moderators
      wrote on last edited by
      #2

      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?

      (Z(:^

      JonBJ 1 Reply Last reply
      0
      • D Offline
        D Offline
        dheerajshenoy
        wrote on last edited by
        #3

        As you can see in the code I provided, I am already doing this, but still it doesn't get the job done

        1 Reply Last reply
        0
        • sierdzioS Offline
          sierdzioS Offline
          sierdzio
          Moderators
          wrote on last edited by
          #4

          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().

          (Z(:^

          1 Reply Last reply
          1
          • D dheerajshenoy has marked this topic as solved on
          • D Offline
            D Offline
            dheerajshenoy
            wrote on last edited by
            #5

            It worked. I set a timeout of 100 ms. Thank you so much.

            1 Reply Last reply
            0
            • sierdzioS sierdzio

              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?

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

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

              1 Reply Last reply
              3
              • sierdzioS Offline
                sierdzioS Offline
                sierdzio
                Moderators
                wrote on last edited by
                #7

                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 with 0ms delay. Or invokeMethod(). can be used for the same effect.

                (Z(:^

                JonBJ 1 Reply Last reply
                1
                • sierdzioS sierdzio

                  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 with 0ms delay. Or invokeMethod(). can be used for the same effect.

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

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

                  delayedSortTimer.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 reports

                  This 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 intercepting directoryLoaded() 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 as 100?

                  All typed in by me on a wing and a prayer... ;-)

                  1 Reply Last reply
                  2
                  • D Offline
                    D Offline
                    dheerajshenoy
                    wrote on last edited by
                    #9

                    @JonB I am not sorting the model in any way. Anything below 100ms doesn't seem to work.

                    JonBJ 1 Reply Last reply
                    1
                    • D dheerajshenoy

                      @JonB I am not sorting the model in any way. Anything below 100ms doesn't seem to work.

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

                      @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(), running onDirectoryLoaded() immediately on signal directoryLoaded(), 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 by ls -f or ls -U.

                      The second connect(), running onDirectoryLoaded() on a timer delay on signal directoryLoaded(), 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 by ls --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 not sort() explicitly.

                      If the delay is QTimer::singleShot(0) then from the second connect() sorting has not yet happened and items comes out in same order as with no delay. But a delay of just 1 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.

                      1 Reply Last reply
                      1
                      • D Offline
                        D Offline
                        dheerajshenoy
                        wrote on last edited by dheerajshenoy
                        #11

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

                        47de3478-3e58-4677-a792-65b0926f08c8-image.png

                        Now that I think about it, I do not actually want to connect to the directoryLoaded signal. Since QFileSystemModel caches directory (?), whenever I change directory into an already loaded directory, the directoryLoaded signal is not fired. Maybe I am wrong about this caching mechanism in QFileSystemModel, 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.

                        1 Reply Last reply
                        0
                        • D dheerajshenoy has marked this topic as unsolved on

                        • Login

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