So, I have this simple PyQt5 code which is essentially a file explorer. I need to be able to select arbitrary files or groups of files (directories and all of the children). I would like to:
- Add a Checkbox next to each item
- If an item is checked/uncheck and has sub-items, the state of the sub-items should be set to the state of the item. So if you check a directory, everything underneath it should also get checked.
- When an items check state is changed, directly or indirectly, I need to invoke a callback with the full path (relative to the root) of the item.
I am essentially building a list of selected files to process.
import sys
from PyQt5.QtWidgets import QApplication, QFileSystemModel, QTreeView, QWidget, QVBoxLayout
from PyQt5.QtGui import QIcon
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 file system view - pythonspot.com'
self.left = 10
self.top = 10
self.width = 640
self.height = 480
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.model = QFileSystemModel()
self.model.setRootPath('')
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setIndentation(20)
self.tree.setSortingEnabled(True)
self.tree.setWindowTitle("Dir View")
self.tree.resize(640, 480)
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.tree)
self.setLayout(windowLayout)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The QFileSystemModel doesn't load the contents of a directory until explicitly requested (in case of a tree view, it onyl happens when the directory is expanded the first time).
This requires to carefully verify and set the check state of each path recursively not only whenever a new file or directory is added (or renamed/removed), but also when the directory contents are actually loaded.
In order to correctly implement this, the check states should also be stored using file paths, because when the contents of a directory change some indexes might be invalidated.
The following implementation should take care of all written above, and emit a signal only when an item state is actively changed and the parent state is changed, but not for the children items of a checked directory.
While this choice might seem partially incoherent, it's a performance requirement, as you cannot get the individual signals for each subdirectory (nor you might want to): if you check the top level directory, you might receive thousands of unwanted notifications; on the other hand, it might be important to receive a notification if the parent directory state has changed, whenever all items become checked or unchecked.