title: QTreeView with custom item delegate with Browse button
body: <p>Working with the Qt5 framework (via pyQt5 with Python), I need to create a QTreeView widget with Parameter - Value columns, where the Value items for some rows must have an internal 'Browse' button to open a file browse dialog and place the selected file into the corresponding value's field.</p>
  
  <p>Reading up the Qt manuals on item delegates, I've put together the following code:</p>
  
  <p><strong>Custom BrowseEdit class (QLineEdit + Browse action)</strong></p>
  
  <pre class="lang-py prettyprint-override"><code>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)
  </code></pre>
  
  <p><strong>Custom item delegate for the QTreeView:</strong></p>
  
  <pre class="lang-py prettyprint-override"><code>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) -&gt; 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)
  </code></pre>
  
  <p><strong>Create underlying model:</strong></p>
  
  <pre class="lang-py prettyprint-override"><code># 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('&lt;db&gt;')
  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)
  </code></pre>
  
  <p><strong>Set item delegates for browsable edit fields:</strong></p>
  
  <pre class="lang-py prettyprint-override"><code># 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)
  </code></pre>
  
  <p>The error occurs when I invoke the open file dialog by pressing on the Browse button in the editor and try to close the dialog after selecting a file. At that time, an exception is raised saying that the BrowseEdit object was deleted! </p>
  
  <p>I realize this happens because the item delegate frees the underlying editor widget (BrowseEdit in my case) when it goes out of the editing mode (which happens when the file browse dialog is launched). But how can I avoid this?</p>
  
  <p>Another thing I've tried is using the <a href="https://doc.qt.io/qt-5/qabstractitemview.html#setItemDelegateForRow" rel="nofollow noreferrer">QAbstractItemView::setItemDelegateForRow</a> method like so:</p>
  
  <pre class="lang-py prettyprint-override"><code># install BrowseEditDelegate for rows 2 and 5
  self.tv_plugins_3party.setItemDelegateForRow(2, BrowseEditDelegate())
  self.tv_plugins_3party.setItemDelegateForRow(5, BrowseEditDelegate())
  </code></pre>
  
  <p>-- but this code leads to unknown exceptions crashing the app without any traceback messages.</p>