Работая с каркасом Qt5 (через pyQt5 с Python), мне нужно создать виджет QTreeView со столбцами «Параметр - Значение», где элементы «Значение» для некоторых строк должны иметь внутреннюю кнопку «Обзор», чтобы открыть просмотр файла диалоговое окно и поместите выбранный файл в поле соответствующего значения.
Читая руководства Qt для делегатов элементов, я собрал следующий код:
Пользовательский класс BrowseEdit (QLineEdit + Действие просмотра)
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)
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:
self.editor.setParent(parent)
return self.editor
else:
return super().createEditor(parent, option, index)
except Exception as err:
print(err)
return None
def setEditorData(self, editor, index: QtCore.QModelIndex):
if not index.isValid(): return
if self.model_indices and index in self.model_indices:
txt = index.model().data(index, QtCore.Qt.EditRole)
if isinstance(txt, str):
editor.setText(txt)
else:
super().setEditorData(editor, index)
def setModelData(self, editor, model: QtCore.QAbstractItemModel, index: QtCore.QModelIndex):
if self.model_indices and index in self.model_indices:
model.setData(index, editor.text(), QtCore.Qt.EditRole)
else:
super().setModelData(editor, model, index)
def updateEditorGeometry(self, editor, option: QtWidgets.QStyleOptionViewItem,
index: QtCore.QModelIndex):
editor.setGeometry(option.rect)
Создание базовой модели:
# create tree view
self.tv_plugins_3party = QtWidgets.QTreeView()
# underlying model (2 columns)
self.model_plugins_3party = QtGui.QStandardItemModel(0, 2)
self.model_plugins_3party.setHorizontalHeaderLabels([_('Plugin'), _('Value')])
# first root item and sub-items
item_git = QtGui.QStandardItem(QtGui.QIcon(f"{ICONFOLDER}/git.png"), 'Git')
item_git.setFlags(QtCore.Qt.ItemIsEnabled)
item_1 = QtGui.QStandardItem(_('Enabled'))
item_1.setFlags(QtCore.Qt.ItemIsEnabled)
item_2 = QtGui.QStandardItem('')
item_2.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
item_2.setCheckable(True)
item_2.setUserTristate(False)
item_2.setCheckState(QtCore.Qt.Checked)
item_git.appendRow([item_1, item_2])
item_1 = QtGui.QStandardItem(_('Path'))
item_1.setFlags(QtCore.Qt.ItemIsEnabled)
item_2 = QtGui.QStandardItem('')
item_2.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
item_git.appendRow([item_1, item_2])
self.model_plugins_3party.appendRow(item_git)
# second root item and sub-items
item_sqlite = QtGui.QStandardItem(QtGui.QIcon(f"{ICONFOLDER}/sqlite.png"), _('SQLite Editor'))
item_sqlite.setFlags(QtCore.Qt.ItemIsEnabled)
item_1 = QtGui.QStandardItem(_('Enabled'))
item_1.setFlags(QtCore.Qt.ItemIsEnabled)
item_2 = QtGui.QStandardItem('')
item_2.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
item_2.setCheckable(True)
item_2.setUserTristate(False)
item_2.setCheckState(QtCore.Qt.Checked)
item_sqlite.appendRow([item_1, item_2])
item_1 = QtGui.QStandardItem(_('Path'))
item_1.setFlags(QtCore.Qt.ItemIsEnabled)
item_2 = QtGui.QStandardItem('')
item_2.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
item_sqlite.appendRow([item_1, item_2])
item_1 = QtGui.QStandardItem(_('Commands'))
item_1.setFlags(QtCore.Qt.ItemIsEnabled)
item_2 = QtGui.QStandardItem('<db>')
item_2.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable)
item_sqlite.appendRow([item_1, item_2])
self.model_plugins_3party.appendRow(item_sqlite)
# set model
self.tv_plugins_3party.setModel(self.model_plugins_3party)
Задать делегатов элементов для полей редактирования, доступных для просмотра:
# import traceback
try:
indices = []
indices.append(self.model_plugins_3party.index(1, 1,
self.model_plugins_3party.indexFromItem(item_git)))
indices.append(self.model_plugins_3party.index(1, 1,
self.model_plugins_3party.indexFromItem(item_sqlite)))
self.tv_plugins_3party.setItemDelegate(BrowseEditDelegate(indices))
except:
traceback.print_exc(limit=None)
Ошибка возникает, когда я вызываю диалог открытия файла, нажимая кнопку Обзор в редакторе и попробуйте закрыть диалог после выбора файла. В это время возникает исключение, сообщающее, что объект BrowseEdit был удален!
Я понимаю, что это происходит потому, что делегат элемента освобождает базовый виджет редактора (в моем случае, BrowseEdit), когда он выходит из режима редактирования (что происходит при запуске диалога просмотра файлов). Но как я могу избежать этого?
Другая вещь, которую я попробовал, - это использование QAbstractItemView :: setItemDelegateForRow метода, подобного так:
# install BrowseEditDelegate for rows 2 and 5
self.tv_plugins_3party.setItemDelegateForRow(2, BrowseEditDelegate())
self.tv_plugins_3party.setItemDelegateForRow(5, BrowseEditDelegate())
- но этот код приводит к неизвестным исключениям, вызывающим сбой приложения без каких-либо сообщений трассировки.