ws_e_c421
ws_e_c421

Reputation: 1113

How to use the indices of a QFileSystemModel to track a subset of items?

I am trying to create a file picker dialog that uses checkboxes to allow multiple file to be selected. Additionally, I would like to dialog to start with some items already selected. I have found some examples online that use QTreeView and a subclass of QFileSystemModel to do this. How can I connect my list of file paths to be initially checked with the QModelIndex items in the model describing those paths once the dialog is fully rendered?

The examples I looked at keep a list of which QModelIndex items should be checked and override some methods of the QFileSystemModel subclass like the data() method to update this list when items are checked or unchecked. To support having some items checked when the dialog is first created, I tried to use a second list of file paths that should be pre-checked. In the data() method of my QFileSystemModel subclass, I check the file path of the QModelIndex input and, if it is in my the pre-checked list, I remove the file path from the pre-checked list and put that QModelIndex into the other list of checked items. The problem I am having is that the model seems to regenerate itself several times during creation. The first time data() is called for an index with a certain path that path is removed from the pre-checked list and the QModelIndex added to the other list. However, once the dialog is fully displayed, the QModelIndex for that file path is a different instance from the one that was put into the pre-checked list and so my code doesn't know that it should be checked.

Here is an example to illustrate what I mean:

import os
import sys

from PyQt5 import QtWidgets

all_entries = list()


class MyFileSystemModel(QtWidgets.QFileSystemModel):

    def data(self, index, role):
        if index not in all_entries:
            all_entries.append(index)
        return super().data(index, role)


class Ui_Dialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        QtWidgets.QDialog.__init__(self, parent)

        self.model = MyFileSystemModel()
        self.model.setRootPath(os.path.abspath('.'))

        self.tree = QtWidgets.QTreeView()
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(os.path.abspath('.')))

        self.llayout = QtWidgets.QVBoxLayout(parent)
        self.but = QtWidgets.QPushButton("OK")

        self.llayout.addWidget(self.tree)
        self.llayout.addWidget(self.but)

        self.setLayout(self.llayout)

        self.but.clicked.connect(self.print_entries)

    def print_entries(self):
        print('*'*80)
        for index in all_entries:
            print(index, os.path.relpath(self.model.filePath(index),
                                         os.path.abspath('.')))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    ui = Ui_Dialog()
    ui.show()

    sys.exit(app.exec_())

Running this script in a directory with a single file named test and then clicking the OK button, I see

********************************************************************************
<PyQt5.QtCore.QModelIndex object at 0x7ff46b991f28> test
<PyQt5.QtCore.QModelIndex object at 0x7ff46b991f98> test
<PyQt5.QtCore.QModelIndex object at 0x7ff46b910048> test
<PyQt5.QtCore.QModelIndex object at 0x7ff46b9100b8> test

So all_entries ends up with four different QModelIndex items for the test file path. In my more involved checkable dialog, I would end up with the QModelIndex instance at 0x7ff46b991f28 in list of checked items but it seems like once the dialog is displayed I need to be using the instance at 0x7ff46b9100b8. I could handle everything in my own list of paths and not use QModelIndex objects, but the advantage of using them is that QFileSystemModel uses a file system watcher to keep track of file system changes and I would like to take advantage of that to keep track of properties of the selected files without needing to query the file sytem to tell if files have changed.

Upvotes: 0

Views: 800

Answers (1)

eyllanesc
eyllanesc

Reputation: 243993

The problem has the following causes:

  • QModelIndex should not be used to differentiate between items of a model, According to the docs:

Note: Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items. If you need to keep a model index over time use a QPersistentModelIndex.

  • A file has several associated columns in QFileSystemModel, it is appropriate to save the index of the first column.

enter image description here

Considering both things the code should be the following:

all_entries = list()


class MyFileSystemModel(QtWidgets.QFileSystemModel):
    def data(self, index, role):
        if index.column() == 0:
            if QPersistentModelIndex(index) not in all_entries:
                all_entries.append(QPersistentModelIndex(index))
        return super().data(index, role)

[...]

def print_entries(self):
    print('*'*80)
    for index in all_entries:
        ix = QModelIndex(index) # get QModelIndex from QPersistentModelIndex
        print(index, os.path.relpath(self.model.filePath(ix),os.path.abspath('.')))

Upvotes: 2

Related Questions