带有复选框的QFileSystemModel

问题描述 投票:0回答:2

所以,我有这个简单的 PyQt5 代码,它本质上是一个文件浏览器。我需要能够选择任意文件或文件组(目录和所有子文件)。我愿意:

  1. 在每个项目旁边添加一个复选框
  2. 如果某个项目被选中/取消选中并且有子项目,则子项目的状态应设置为该项目的状态。因此,如果您检查一个目录,那么它下面的所有内容也应该被检查。
  3. 当项目检查状态直接或间接更改时,我需要使用该项目的完整路径(相对于根)调用回调。

我实质上是在构建要处理的选定文件的列表。

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_())
python python-3.x pyqt pyqt5 qfilesystemmodel
2个回答
7
投票

QFileSystemModel 在明确请求之前不会加载目录的内容(在树视图的情况下,仅在第一次展开目录时发生)。

这需要不仅在添加(或重命名/删除)新文件或目录时,而且在实际加载目录内容时仔细验证和递归设置每个路径的检查状态。

为了正确实现这一点,检查状态还应该使用文件路径存储,因为当目录内容更改时,某些索引可能会失效。

下面的实现应该照顾上面写的所有内容,并且仅当项目状态主动更改并且父状态发生更改时才发出信号,但对于已检查目录的子项目,不是 虽然这种选择可能看起来有些不连贯,但这是一个性能要求,因为您无法获取每个子目录的单独信号(您也可能不想):如果您检查顶级目录,您可能会收到数千个不需要的通知;另一方面,如果父目录状态发生更改,无论何时所有项目都被选中或取消选中,接收通知可能很重要。

from PyQt5 import QtCore, QtWidgets class CheckableFileSystemModel(QtWidgets.QFileSystemModel): checkStateChanged = QtCore.pyqtSignal(str, bool) def __init__(self): super().__init__() self.checkStates = {} self.rowsInserted.connect(self.checkAdded) self.rowsRemoved.connect(self.checkParent) self.rowsAboutToBeRemoved.connect(self.checkRemoved) def checkState(self, index): return self.checkStates.get(self.filePath(index), QtCore.Qt.Unchecked) def setCheckState(self, index, state, emitStateChange=True): path = self.filePath(index) if self.checkStates.get(path) == state: return self.checkStates[path] = state if emitStateChange: self.checkStateChanged.emit(path, bool(state)) def checkAdded(self, parent, first, last): # if a file/directory is added, ensure it follows the parent state as long # as the parent is already tracked; note that this happens also when # expanding a directory that has not been previously loaded if not parent.isValid(): return if self.filePath(parent) in self.checkStates: state = self.checkState(parent) for row in range(first, last + 1): index = self.index(row, 0, parent) path = self.filePath(index) if path not in self.checkStates: self.checkStates[path] = state self.checkParent(parent) def checkRemoved(self, parent, first, last): # remove items from the internal dictionary when a file is deleted; # note that this *has* to happen *before* the model actually updates, # that's the reason this function is connected to rowsAboutToBeRemoved for row in range(first, last + 1): path = self.filePath(self.index(row, 0, parent)) if path in self.checkStates: self.checkStates.pop(path) def checkParent(self, parent): # verify the state of the parent according to the children states if not parent.isValid(): return childStates = [self.checkState(self.index(r, 0, parent)) for r in range(self.rowCount(parent))] newState = QtCore.Qt.Checked if all(childStates) else QtCore.Qt.Unchecked oldState = self.checkState(parent) if newState != oldState: self.setCheckState(parent, newState) self.dataChanged.emit(parent, parent) self.checkParent(parent.parent()) def flags(self, index): return super().flags(index) | QtCore.Qt.ItemIsUserCheckable def data(self, index, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.CheckStateRole and index.column() == 0: return self.checkState(index) return super().data(index, role) def setData(self, index, value, role, checkParent=True, emitStateChange=True): if role == QtCore.Qt.CheckStateRole and index.column() == 0: self.setCheckState(index, value, emitStateChange) for row in range(self.rowCount(index)): # set the data for the children, but do not emit the state change, # and don't check the parent state (to avoid recursion) self.setData(index.child(row, 0), value, QtCore.Qt.CheckStateRole, checkParent=False, emitStateChange=False) self.dataChanged.emit(index, index) if checkParent: self.checkParent(index.parent()) return True return super().setData(index, value, role) class Test(QtWidgets.QWidget): def __init__(self): super().__init__() layout = QtWidgets.QVBoxLayout(self) self.tree = QtWidgets.QTreeView() layout.addWidget(self.tree, stretch=2) model = CheckableFileSystemModel() model.setRootPath('') self.tree.setModel(model) self.tree.setSortingEnabled(True) self.tree.header().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) self.logger = QtWidgets.QPlainTextEdit() layout.addWidget(self.logger, stretch=1) self.logger.setReadOnly(True) model.checkStateChanged.connect(self.updateLog) self.resize(640, 480) QtCore.QTimer.singleShot(0, lambda: self.tree.expand(model.index(0, 0))) def updateLog(self, path, checked): if checked: text = 'Path "{}" has been checked' else: text = 'Path "{}" has been unchecked' self.logger.appendPlainText(text.format(path)) self.logger.verticalScrollBar().setValue( self.logger.verticalScrollBar().maximum()) if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) test = Test() test.show() sys.exit(app.exec_())
    

0
投票
我尝试将编码转换为 PyQt6,但第 58 行 (self.setData(self.index.child(row, 0)) 现在是一个问题。任何帮助将不胜感激。

from PyQt6 import QtWidgets, QtGui, QtCore class QFileSystemModelCheckBox(QtGui.QFileSystemModel): checkStateChanged = QtCore.pyqtSignal(str, bool) def __init__(self): super().__init__() self.setFilter(QtCore.QDir.Filter.AllEntries | QtCore.QDir.Filter.NoDotAndDotDot) self.rowsAboutToBeRemoved.connect(self.checkRemoved) self.rowsInserted.connect(self.checkAdded) self.rowsRemoved.connect(self.checkParent) self.checkStates = {} def checkState(self, index): return self.checkStates.get(self.filePath(index), QtCore.Qt.CheckState.Checked) def flags(self, index): return super().flags(index) | QtCore.Qt.ItemFlag.ItemIsUserCheckable def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): if role == QtCore.Qt.ItemDataRole.CheckStateRole and index.column() == 0: return self.checkState(index) return super().data(index, role) def checkParent(self, parent): # verify the state of the parent according to the children states if not parent.isValid(): return childStates = [self.checkState(self.index(r, 0, parent)) for r in range(self.rowCount(parent))] newState = QtCore.Qt.CheckState.Checked if all(childStates) else QtCore.Qt.CheckState.Unchecked oldState = self.checkState(parent) if newState != oldState: self.setCheckState(parent, newState) self.dataChanged.emit(parent, parent) self.checkParent(parent.parent()) def checkAdded(self, parent, first, last): if not parent.isValid(): return if self.filePath(parent) in self.checkStates: state = self.checkState(parent) for row in range(first, last + 1): index = self.index(row, 0, parent) path = self.filePath(index) if path not in self.checkStates: self.checkStates[path] = state self.checkParent(parent) def checkRemoved(self, parent, first, last): for row in range(first, last + 1): path = self.filePath(self.index(row, 0, parent)) if path in self.checkStates: self.checkStates.pop(path) def setData(self, index, value, role, checkParent=True, emitStateChange=True): if role == QtCore.Qt.ItemDataRole.CheckStateRole and index.column() == 0: self.setCheckState(index, value, emitStateChange) for row in range(self.rowCount(index)): self.setData(self.index.child(row, 0), value, QtCore.Qt.ItemDataRole.CheckStateRole, checkParent=False, emitStateChange=False) self.dataChanged.emit(index, index) if checkParent: self.checkParent(index.parent()) return True return super().setData(index, value, role) def setCheckState(self, index, state, emitStateChange=True): path = self.filePath(index) if self.checkStates.get(path) == state: return self.checkStates[path] = state if emitStateChange: self.checkStateChanged.emit(path, bool(state)) if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) fDir = r"<FolderDirectory>" model = QFileSystemModelCheckBox() model.setRootPath(fDir) tree = QtWidgets.QTreeView() tree.setModel(model) tree.setRootIndex(model.index(fDir)) tree.resize(640, 480) tree.show() sys.exit(app.exec())
    
© www.soinside.com 2019 - 2024. All rights reserved.