Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Display custom list of files in QTreeView using QFileSystemModel or QAbstractItemModel

Display custom list of files in QTreeView using QFileSystemModel or QAbstractItemModel

Scheduled Pinned Locked Moved Solved Qt for Python
9 Posts 5 Posters 2.8k 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.
  • M Offline
    M Offline
    midnightdim
    wrote on last edited by
    #1

    I'm building an open source PySide6 app based on the custom file browser in QTreeView.
    I've already subclassed QFileSystemModel to display a custom column with some extra data.

    Now my goal is to display a specific subset of files (they can be located on different drives) in a treeview.

    To simplify things imagine that I have a function:

    def files_to_display():
        return ['C:\file1', 'D:\file2', 'D:\folder1\file3']
    

    Now I need to display these files in my QTreeView. I tried using QSortFilterProxyModel and filterAcceptsRow to filter out everything else and it worked. However on a relatively large number of files it's extremely slow and unusable. I'm pretty sure a simpler custom file tree would work faster because afaik QFileSystemModel tracks the folder state and runs other extra stuff that I can live without.

    I'm not sure how to solve this problem.
    I see basically 2 ways:

    1. Somehow cut out what I don't need from QFileSystemModel.
      With this solution I don't fully understand how I do this. In particular, how do I fill the model with the data from my function? How do it use setRootPath?

    2. Subclass QAbstractItemModel.
      This solution is more or less clear, however, it's missing some of the important things that go with QFileSystemModel out of the box: I need the columns and the data it provides (name, size, type, modification date), I also need file/folder icons that I'm using with QFileIconProvider.

    So basically I'd like to use a light-weighted version of QFileSystemModel without watching the file system and with my list of files.

    I'm open to alternative solutions.

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      You should take a look at the sources of QFileSystemModel to see how things are implemented there. You can then see what is the most useful to you.

      Otherwise, you can also leverage Python's os.walk to parse your folders of interest.

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      M 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        You should take a look at the sources of QFileSystemModel to see how things are implemented there. You can then see what is the most useful to you.

        Otherwise, you can also leverage Python's os.walk to parse your folders of interest.

        M Offline
        M Offline
        midnightdim
        wrote on last edited by midnightdim
        #3

        @SGaist Thanks, but my goal is not to parse folders of interest. As I mentioned in my post, I have a list of specific files that I want to display, so os.walk is not relevant.
        As for the sources of QFileSystemModel:

        1. Where can I find them?
        2. Is it really a better path than implementing QAbstractItemModel?
        jsulmJ 1 Reply Last reply
        0
        • M midnightdim

          @SGaist Thanks, but my goal is not to parse folders of interest. As I mentioned in my post, I have a list of specific files that I want to display, so os.walk is not relevant.
          As for the sources of QFileSystemModel:

          1. Where can I find them?
          2. Is it really a better path than implementing QAbstractItemModel?
          jsulmJ Offline
          jsulmJ Offline
          jsulm
          Lifetime Qt Champion
          wrote on last edited by
          #4

          @midnightdim said in Display custom list of files in QTreeView using QFileSystemModel or QAbstractItemModel:

          Where can I find them?

          https://wiki.qt.io/Get_the_Source or use https://code.woboq.org/qt5/qtbase/src/widgets/dialogs/qfilesystemmodel.cpp.html

          https://forum.qt.io/topic/113070/qt-code-of-conduct

          M 1 Reply Last reply
          4
          • jsulmJ jsulm

            @midnightdim said in Display custom list of files in QTreeView using QFileSystemModel or QAbstractItemModel:

            Where can I find them?

            https://wiki.qt.io/Get_the_Source or use https://code.woboq.org/qt5/qtbase/src/widgets/dialogs/qfilesystemmodel.cpp.html

            M Offline
            M Offline
            midnightdim
            wrote on last edited by
            #5

            @jsulm Thanks. I will do my best to study this, but my initial request is still actual. My knowledge and experience are quite limited, I'm not a professional programmer, so I appreciate any help and advice here.

            1 Reply Last reply
            0
            • jazzycamelJ Offline
              jazzycamelJ Offline
              jazzycamel
              wrote on last edited by jazzycamel
              #6

              @midnightdim What follows is a (pretty lengthy/complex) example of what you might be wanting. It's adapted from a project of mine that sounds similar to your own. FileSystemModelLite() is just about enough to represent a predefined list of files in a QTreeView() similar to the way that QFileSystemModel() does, but you don't get any bells or whistles and, as this is a static model, it will not respond to updates from the file system and is not editable. Also, it expects the files provided to exist, i.e. it does no checking. Final caveat, I believe this should work fine on Windows (your list of example files indicated you were), but, as I don't have a Windows machine to test it on, I can't be 100% sure.!

              If it all works, it should look something like the following:

              screenshot

              Here's the code:

              import os.path as osp
              import posixpath, mimetypes
              import time
              from typing import Any, List, Union
              
              from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
              from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeView, QFileIconProvider
              
              FSMItemOrNone = Union["_FileSystemModelLiteItem", None]
              
              
              def sizeof_fmt(num, suffix="B"):
                  """Creates a human readable string from a file size"""
                  for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
                      if abs(num) < 1024.0:
                          return f"{num:3.1f}{unit}{suffix}"
                      num /= 1024.0
                  return f"{num:.1f}Yi{suffix}"
              
              
              class _FileSystemModelLiteItem(object):
                  """Represents a single node (drive, folder or file) in the tree"""
              
                  def __init__(
                      self,
                      data: List[Any],
                      icon=QFileIconProvider.Computer,
                      parent: FSMItemOrNone = None,
                  ):
                      self._data: List[Any] = data
                      self._icon = icon
                      self._parent: _FileSystemModelLiteItem = parent
                      self.child_items: List[_FileSystemModelLiteItem] = []
              
                  def append_child(self, child: "_FileSystemModelLiteItem"):
                      self.child_items.append(child)
              
                  def child(self, row: int) -> FSMItemOrNone:
                      try:
                          return self.child_items[row]
                      except IndexError:
                          return None
              
                  def child_count(self) -> int:
                      return len(self.child_items)
              
                  def column_count(self) -> int:
                      return len(self._data)
              
                  def data(self, column: int) -> Any:
                      try:
                          return self._data[column]
                      except IndexError:
                          return None
              
                  def icon(self):
                      return self._icon
              
                  def row(self) -> int:
                      if self._parent:
                          return self._parent.child_items.index(self)
                      return 0
              
                  def parent_item(self) -> FSMItemOrNone:
                      return self._parent
              
              
              class FileSystemModelLite(QAbstractItemModel):
                  def __init__(self, file_list: List[str], parent=None, **kwargs):
                      super().__init__(parent, **kwargs)
              
                      self._icon_provider = QFileIconProvider()
              
                      self._root_item = _FileSystemModelLiteItem(
                          ["Name", "Size", "Type", "Modification Date"]
                      )
                      self._setup_model_data(file_list, self._root_item)
              
                  def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
                      if not index.isValid():
                          return None
              
                      item: _FileSystemModelLiteItem = index.internalPointer()
                      if role == Qt.DisplayRole:
                          return item.data(index.column())
                      elif index.column() == 0 and role == Qt.DecorationRole:
                          return self._icon_provider.icon(item.icon())
                      return None
              
                  def flags(self, index: QModelIndex) -> Qt.ItemFlags:
                      if not index.isValid():
                          return Qt.NoItemFlags
                      return super().flags(index)
              
                  def headerData(
                      self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
                  ) -> Any:
                      if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                          return self._root_item.data(section)
                      return None
              
                  def index(
                      self, row: int, column: int, parent: QModelIndex = QModelIndex()
                  ) -> QModelIndex:
                      if not self.hasIndex(row, column, parent):
                          return QModelIndex()
              
                      if not parent.isValid():
                          parent_item = self._root_item
                      else:
                          parent_item = parent.internalPointer()
              
                      child_item = parent_item.child(row)
                      if child_item:
                          return self.createIndex(row, column, child_item)
                      return QModelIndex()
              
                  def parent(self, index: QModelIndex) -> QModelIndex:
                      if not index.isValid():
                          return QModelIndex()
              
                      child_item: _FileSystemModelLiteItem = index.internalPointer()
                      parent_item: FSMItemOrNone = child_item.parent_item()
              
                      if parent_item == self._root_item:
                          return QModelIndex()
              
                      return self.createIndex(parent_item.row(), 0, parent_item)
              
                  def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
                      if parent.column() > 0:
                          return 0
              
                      if not parent.isValid():
                          parent_item = self._root_item
                      else:
                          parent_item = parent.internalPointer()
              
                      return parent_item.child_count()
              
                  def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
                      if parent.isValid():
                          return parent.internalPointer().column_count()
                      return self._root_item.column_count()
              
                  def _setup_model_data(
                      self, file_list: List[str], parent: "_FileSystemModelLiteItem"
                  ):
                      def _add_to_tree(_file_record, _parent: "_FileSystemModelLiteItem", root=False):
                          item_name = _file_record["bits"].pop(0)
                          for child in _parent.child_items:
                              if item_name == child.data(0):
                                  item = child
                                  break
                          else:
                              data = [item_name, "", "", ""]
                              if root:
                                  icon = QFileIconProvider.Computer
                              elif len(_file_record["bits"]) == 0:
                                  icon = QFileIconProvider.File
                                  data = [
                                      item_name,
                                      _file_record["size"],
                                      _file_record["type"],
                                      _file_record["modified_at"],
                                  ]
                              else:
                                  icon = QFileIconProvider.Folder
              
                              item = _FileSystemModelLiteItem(data, icon=icon, parent=_parent)
                              _parent.append_child(item)
              
                          if len(_file_record["bits"]):
                              _add_to_tree(_file_record, item)
              
                      for file in file_list:
                          file_record = {
                              "size": sizeof_fmt(osp.getsize(file)),
                              "modified_at": time.strftime(
                                  "%a, %d %b %Y %H:%M:%S %Z", time.localtime(osp.getmtime(file))
                              ),
                              "type": mimetypes.guess_type(file)[0],
                          }
              
                          drive = True
                          if "\\" in file:
                              file = posixpath.join(*file.split("\\"))
                          bits = file.split("/")
                          if len(bits) > 1 and bits[0] == "":
                              bits[0] = "/"
                              drive = False
              
                          file_record["bits"] = bits
                          _add_to_tree(file_record, parent, drive)
              
              
              class Widget(QWidget):
                  def __init__(self, parent=None, **kwargs):
                      super().__init__(parent, **kwargs)
              
                      file_list = [
                          "/var/log/boot.log",
                          "/var/lib/mosquitto/mosquitto.db",
                          "/tmp/some.pdf",
                      ]
                      self._fileSystemModel = FileSystemModelLite(file_list, self)
              
                      layout = QVBoxLayout(self)
              
                      self._treeView = QTreeView(self)
                      self._treeView.setModel(self._fileSystemModel)
                      layout.addWidget(self._treeView)
              
              
              if __name__ == "__main__":
                  from sys import argv, exit
                  from PyQt5.QtWidgets import QApplication
              
                  a = QApplication(argv)
                  w = Widget()
                  w.show()
                  exit(a.exec())
              

              This was tested using Python 3.8.5 and PyQt5.15.3 (and PySide2 5.15.2) on Ubuntu 20.04.2LTS.

              Hope this helps :o)

              For the avoidance of doubt:

              1. All my code samples (C++ or Python) are tested before posting
              2. As of 23/03/20, my Python code is formatted to PEP-8 standards using black from the PSF (https://github.com/psf/black)
              M 1 Reply Last reply
              3
              • jazzycamelJ jazzycamel

                @midnightdim What follows is a (pretty lengthy/complex) example of what you might be wanting. It's adapted from a project of mine that sounds similar to your own. FileSystemModelLite() is just about enough to represent a predefined list of files in a QTreeView() similar to the way that QFileSystemModel() does, but you don't get any bells or whistles and, as this is a static model, it will not respond to updates from the file system and is not editable. Also, it expects the files provided to exist, i.e. it does no checking. Final caveat, I believe this should work fine on Windows (your list of example files indicated you were), but, as I don't have a Windows machine to test it on, I can't be 100% sure.!

                If it all works, it should look something like the following:

                screenshot

                Here's the code:

                import os.path as osp
                import posixpath, mimetypes
                import time
                from typing import Any, List, Union
                
                from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
                from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeView, QFileIconProvider
                
                FSMItemOrNone = Union["_FileSystemModelLiteItem", None]
                
                
                def sizeof_fmt(num, suffix="B"):
                    """Creates a human readable string from a file size"""
                    for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
                        if abs(num) < 1024.0:
                            return f"{num:3.1f}{unit}{suffix}"
                        num /= 1024.0
                    return f"{num:.1f}Yi{suffix}"
                
                
                class _FileSystemModelLiteItem(object):
                    """Represents a single node (drive, folder or file) in the tree"""
                
                    def __init__(
                        self,
                        data: List[Any],
                        icon=QFileIconProvider.Computer,
                        parent: FSMItemOrNone = None,
                    ):
                        self._data: List[Any] = data
                        self._icon = icon
                        self._parent: _FileSystemModelLiteItem = parent
                        self.child_items: List[_FileSystemModelLiteItem] = []
                
                    def append_child(self, child: "_FileSystemModelLiteItem"):
                        self.child_items.append(child)
                
                    def child(self, row: int) -> FSMItemOrNone:
                        try:
                            return self.child_items[row]
                        except IndexError:
                            return None
                
                    def child_count(self) -> int:
                        return len(self.child_items)
                
                    def column_count(self) -> int:
                        return len(self._data)
                
                    def data(self, column: int) -> Any:
                        try:
                            return self._data[column]
                        except IndexError:
                            return None
                
                    def icon(self):
                        return self._icon
                
                    def row(self) -> int:
                        if self._parent:
                            return self._parent.child_items.index(self)
                        return 0
                
                    def parent_item(self) -> FSMItemOrNone:
                        return self._parent
                
                
                class FileSystemModelLite(QAbstractItemModel):
                    def __init__(self, file_list: List[str], parent=None, **kwargs):
                        super().__init__(parent, **kwargs)
                
                        self._icon_provider = QFileIconProvider()
                
                        self._root_item = _FileSystemModelLiteItem(
                            ["Name", "Size", "Type", "Modification Date"]
                        )
                        self._setup_model_data(file_list, self._root_item)
                
                    def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
                        if not index.isValid():
                            return None
                
                        item: _FileSystemModelLiteItem = index.internalPointer()
                        if role == Qt.DisplayRole:
                            return item.data(index.column())
                        elif index.column() == 0 and role == Qt.DecorationRole:
                            return self._icon_provider.icon(item.icon())
                        return None
                
                    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
                        if not index.isValid():
                            return Qt.NoItemFlags
                        return super().flags(index)
                
                    def headerData(
                        self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
                    ) -> Any:
                        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                            return self._root_item.data(section)
                        return None
                
                    def index(
                        self, row: int, column: int, parent: QModelIndex = QModelIndex()
                    ) -> QModelIndex:
                        if not self.hasIndex(row, column, parent):
                            return QModelIndex()
                
                        if not parent.isValid():
                            parent_item = self._root_item
                        else:
                            parent_item = parent.internalPointer()
                
                        child_item = parent_item.child(row)
                        if child_item:
                            return self.createIndex(row, column, child_item)
                        return QModelIndex()
                
                    def parent(self, index: QModelIndex) -> QModelIndex:
                        if not index.isValid():
                            return QModelIndex()
                
                        child_item: _FileSystemModelLiteItem = index.internalPointer()
                        parent_item: FSMItemOrNone = child_item.parent_item()
                
                        if parent_item == self._root_item:
                            return QModelIndex()
                
                        return self.createIndex(parent_item.row(), 0, parent_item)
                
                    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
                        if parent.column() > 0:
                            return 0
                
                        if not parent.isValid():
                            parent_item = self._root_item
                        else:
                            parent_item = parent.internalPointer()
                
                        return parent_item.child_count()
                
                    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
                        if parent.isValid():
                            return parent.internalPointer().column_count()
                        return self._root_item.column_count()
                
                    def _setup_model_data(
                        self, file_list: List[str], parent: "_FileSystemModelLiteItem"
                    ):
                        def _add_to_tree(_file_record, _parent: "_FileSystemModelLiteItem", root=False):
                            item_name = _file_record["bits"].pop(0)
                            for child in _parent.child_items:
                                if item_name == child.data(0):
                                    item = child
                                    break
                            else:
                                data = [item_name, "", "", ""]
                                if root:
                                    icon = QFileIconProvider.Computer
                                elif len(_file_record["bits"]) == 0:
                                    icon = QFileIconProvider.File
                                    data = [
                                        item_name,
                                        _file_record["size"],
                                        _file_record["type"],
                                        _file_record["modified_at"],
                                    ]
                                else:
                                    icon = QFileIconProvider.Folder
                
                                item = _FileSystemModelLiteItem(data, icon=icon, parent=_parent)
                                _parent.append_child(item)
                
                            if len(_file_record["bits"]):
                                _add_to_tree(_file_record, item)
                
                        for file in file_list:
                            file_record = {
                                "size": sizeof_fmt(osp.getsize(file)),
                                "modified_at": time.strftime(
                                    "%a, %d %b %Y %H:%M:%S %Z", time.localtime(osp.getmtime(file))
                                ),
                                "type": mimetypes.guess_type(file)[0],
                            }
                
                            drive = True
                            if "\\" in file:
                                file = posixpath.join(*file.split("\\"))
                            bits = file.split("/")
                            if len(bits) > 1 and bits[0] == "":
                                bits[0] = "/"
                                drive = False
                
                            file_record["bits"] = bits
                            _add_to_tree(file_record, parent, drive)
                
                
                class Widget(QWidget):
                    def __init__(self, parent=None, **kwargs):
                        super().__init__(parent, **kwargs)
                
                        file_list = [
                            "/var/log/boot.log",
                            "/var/lib/mosquitto/mosquitto.db",
                            "/tmp/some.pdf",
                        ]
                        self._fileSystemModel = FileSystemModelLite(file_list, self)
                
                        layout = QVBoxLayout(self)
                
                        self._treeView = QTreeView(self)
                        self._treeView.setModel(self._fileSystemModel)
                        layout.addWidget(self._treeView)
                
                
                if __name__ == "__main__":
                    from sys import argv, exit
                    from PyQt5.QtWidgets import QApplication
                
                    a = QApplication(argv)
                    w = Widget()
                    w.show()
                    exit(a.exec())
                

                This was tested using Python 3.8.5 and PyQt5.15.3 (and PySide2 5.15.2) on Ubuntu 20.04.2LTS.

                Hope this helps :o)

                M Offline
                M Offline
                midnightdim
                wrote on last edited by
                #7

                @jazzycamel Thank you so much! This works for me and looks very close to what I need.
                Quick question: I'll probably need to add sorting to this view/model, would you recommend using QSortFilterProxyModel for that?

                jazzycamelJ 1 Reply Last reply
                0
                • M midnightdim

                  @jazzycamel Thank you so much! This works for me and looks very close to what I need.
                  Quick question: I'll probably need to add sorting to this view/model, would you recommend using QSortFilterProxyModel for that?

                  jazzycamelJ Offline
                  jazzycamelJ Offline
                  jazzycamel
                  wrote on last edited by
                  #8

                  @midnightdim Yes, QSortFilterProxyModel is the right way to go.

                  For the avoidance of doubt:

                  1. All my code samples (C++ or Python) are tested before posting
                  2. As of 23/03/20, my Python code is formatted to PEP-8 standards using black from the PSF (https://github.com/psf/black)
                  JonBJ 1 Reply Last reply
                  3
                  • jazzycamelJ jazzycamel

                    @midnightdim Yes, QSortFilterProxyModel is the right way to go.

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

                    @jazzycamel
                    Dear camel,

                    Now that this topic is resolved....

                    In your signature you have:

                    3. I know how super() works

                    I well-remember when and why you appended this over a year ago, because of a "difficult" Python/PyQt5 user we had on this site at the time! (I had had similar run-ins with that user.) Purely FYI, that user got "removed", so you could risk removing this from your sig now ;-)

                    All the Best.

                    1 Reply Last reply
                    1
                    • M MaMo referenced this topic on

                    • Login

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