带有浏览按钮的自定义项目委托的 QTreeView

使用 Qt5 框架(通过 Python 的 pyQt5),我需要创建一个带有参数 - 值列的 QTreeView 小部件,其中某些行的值项必须具有内部“浏览”按钮以打开文件浏览对话框并放置选定的文件到相应值的字段中。


阅读关于项目委托的 Qt 手册,我整理了以下代码:


自定义 BrowseEdit 类(QLineEdit + Browse 操作)


class BrowseEdit(QtWidgets.QLineEdit):


    def __init__(self, contents='', filefilters=None,

        btnicon=None, btnposition=None,

        opendialogtitle=None, opendialogdir=None, parent=None):

        super().__init__(contents, parent)

        self.filefilters = filefilters or _('All files (*.*)')

        self.btnicon = btnicon or 'folder-2.png'

        self.btnposition = btnposition or QtWidgets.QLineEdit.TrailingPosition

        self.opendialogtitle = opendialogtitle or _('Select file')

        self.opendialogdir = opendialogdir or os.getcwd()

        self.reset_action()


    def _clear_actions(self):

        for act_ in self.actions():

            self.removeAction(act_)


    def reset_action(self):

        self._clear_actions()

        self.btnaction = QtWidgets.QAction(QtGui.QIcon(f"{ICONFOLDER}/{self.btnicon}"), '')

        self.btnaction.triggered.connect(self.on_btnaction)

        self.addAction(self.btnaction, self.btnposition)

        #self.show()


    @QtCore.pyqtSlot()

    def on_btnaction(self):

        selected_path = QtWidgets.QFileDialog.getOpenFileName(self.window(), self.opendialogtitle, self.opendialogdir, self.filefilters)

        if not selected_path[0]: return

        selected_path = selected_path[0].replace('/', os.sep)

        # THIS CAUSES ERROR ('self' GETS DELETED BEFORE THIS LINE!)

        self.setText(selected_path)

QTreeView 的自定义项目委托:


class BrowseEditDelegate(QtWidgets.QStyledItemDelegate):


    def __init__(self, model_indices=None, thisparent=None, 

                **browse_edit_kwargs):

        super().__init__(thisparent)

        self.model_indices = model_indices

        self.editor = BrowseEdit(**browse_edit_kwargs)  

        self.editor.setFrame(False)      


Cats萌萌
浏览 230回答 1
1回答

侃侃尔雅

每个代表不能只有一个唯一的编辑器,原因有两个:可能有更多的编辑器的活动实例(使用 打开openPersistentEditor),例如一个表,其中一列的每一行都有一个组合框。每次编辑器将其数据提交给模型时,如果它不是持久编辑器,它就会被销毁。考虑当一个 Qt 对象被分配给一个 Python 变量/属性时,它实际上是一个指向由 Qt 创建的底层 C++ 对象的指针。这意味着虽然self.editor仍然作为 python 对象存在,但它指向一个在编辑器被委托关闭时实际删除的对象。正如函数名所说,createEditor() 创建一个编辑器,所以解决方法是每次createEditor()调用都创建一个新实例。更新但是,这里有一个重要问题:一旦您打开对话框,委托编辑器就会失去焦点。对于一个项目视图,这与单击另一个项目(更改焦点)相同,这将导致数据提交和编辑器破坏。“简单”的解决方案是在要打开对话框时阻止委托信号(最重要的closeEditor()是会调用),然后再解除阻止。destroyEditor()class BrowseEdit(QtWidgets.QLineEdit):    @QtCore.pyqtSlot()    def on_btnaction(self):        self.delegate.blockSignals(True)        selected_path = QtWidgets.QFileDialog.getOpenFileName(self.window(), self.opendialogtitle, self.opendialogdir, self.filefilters)        self.delegate.blockSignals(False)        if not selected_path[0]: return        selected_path = selected_path[0].replace('/', os.sep)        # THIS CAUSES ERROR ('self' GETS DELETED BEFORE THIS LINE!)        self.setText(selected_path)class BrowseEditDelegate(QtWidgets.QStyledItemDelegate):    # ...    def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,                    index: QtCore.QModelIndex) -> QtWidgets.QWidget:        try:            if self.model_indices and index in self.model_indices:                editor = BrowseEdit(parent=parent)                editor.delegate = self                return editor            else:                return super().createEditor(parent, option, index)        except Exception as err:            print(err)            return None也就是说,这是一个hack。虽然它有效,但不能保证它会在未来版本的 Qt 中,当可能引入其他信号或它们的行为发生变化时。更好更优雅的解决方案是创建一个在单击浏览按钮时调用的信号,然后项目视图(或其任何父项)将负责浏览,如果文件对话框结果有效则设置数据并再次开始编辑该字段:class BrowseEditDelegate(QtWidgets.QStyledItemDelegate):    browseRequested = QtCore.pyqtSignal(QtCore.QModelIndex)    # ...    def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,                    index: QtCore.QModelIndex) -> QtWidgets.QWidget:        try:            if self.model_indices and index in self.model_indices:                editor = BrowseEdit(parent=parent)                editor.btnaction.triggered.connect(                    lambda: self.browseRequested.emit(index))                return editor            else:                return super().createEditor(parent, option, index)        except Exception as err:            print(err)            return Noneclass Window(QtWidgets.QWidget):    def __init__(self):        # ...        delegate = BrowseEditDelegate(indices)        self.tv_plugins_3party.setItemDelegate(delegate)        delegate.browseRequested.connect(self.browseRequested)    def browseRequested(self, index):        selected_path = QtWidgets.QFileDialog.getOpenFileName(self.window(), 'Select file', index.data())        if selected_path[0]:            self.model_plugins_3party.setData(index, selected_path[0])        self.tv_plugins_3party.edit(index)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python