为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Cats萌萌 2022-07-26 15:59:32
使用 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)      
查看完整描述

1 回答

?
侃侃尔雅

TA贡献1801条经验 获得超15个赞

每个代表不能只有一个唯一的编辑器,原因有两个:

  1. 可能有更多的编辑器的活动实例(使用 打开openPersistentEditor),例如一个表,其中一列的每一行都有一个组合框。

  2. 每次编辑器将其数据提交给模型时,如果它不是持久编辑器,它就会被销毁。考虑当一个 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 None



class 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)


查看完整回答
反对 回复 2022-07-26
  • 1 回答
  • 0 关注
  • 176 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信