I’m trying to use either TreeView or the TreeWidget to create a hierarchy that displays just a icon/string to represent a path.
I’d like a signal/slot so that double-clicking on an item opens a new window to edit the contents of that path. Currently I’m able to lookup by index the display name, but I can’t see any way to store/link hidden data (such as a key to reference a unique folder_id or node_id)
Is the common paradigm to add the key to Model and then remove those columns from the tree display?
Example Data / reason for needing to access hidden properties.
Class: Repository Properties: ID, Name
Class: Endpoint Properties: ID, Name, Repository_ID, Address, Method, etc...
Class: GeneratedDiagramTree Properties: Type, Mixed_ID, Name
I want to only see the Name in the view, but I want to be able to refer to the Type/ID in order to determine what happens when double-clicking.
When you want to save custom information for each node then you must use the roles (preferably >= Qt::UserRole since the roles can be used internally by Qt). For example, in the following code 2 roles are used to store 2 types of information.
If QTreeWidget is used then you must use the setData()
method and pass it the role and value to store the information, to obtain the information you must use the data()
method by passing the role.
from enum import Enum
import uuid
from PyQt5 import QtCore, QtWidgets
TypeRole = QtCore.Qt.UserRole + 1000
IdRole = QtCore.Qt.UserRole + 1001
class TypeItem(Enum):
ROOT = 0
CHILD = 1
class Dialog(QtWidgets.QDialog):
def __init__(self, name, type_, id_, parent=None):
super().__init__(parent)
self.name_le = QtWidgets.QLineEdit(name)
type_label = QtWidgets.QLabel(str(type_))
self.id_le = QtWidgets.QLineEdit(id_)
button_box = QtWidgets.QDialogButtonBox()
button_box.setOrientation(QtCore.Qt.Horizontal)
button_box.setStandardButtons(
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.name_le)
lay.addWidget(type_label)
lay.addWidget(self.id_le)
lay.addWidget(button_box)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
@property
def name(self):
return self.name_le.text()
@property
def id_(self):
return self.id_le.text()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.view = QtWidgets.QTreeWidget()
self.view.itemDoubleClicked.connect(self.onItemDoubleClicked)
self.setCentralWidget(self.view)
for i in range(3):
root_it = QtWidgets.QTreeWidgetItem(["tlv-{}".format(i)])
root_it.setData(0, TypeRole, TypeItem.ROOT)
root_it.setData(0, IdRole, uuid.uuid4().hex)
self.view.addTopLevelItem(root_it)
for j in range(3):
child_it = QtWidgets.QTreeWidgetItem(["it-{}{}".format(i, j)])
child_it.setData(0, TypeRole, TypeItem.CHILD)
child_it.setData(0, IdRole, uuid.uuid4().hex)
root_it.addChild(child_it)
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
def onItemDoubleClicked(self, item, column):
name = item.text(column)
type_ = item.data(column, TypeRole)
id_ = item.data(column, IdRole)
d = Dialog(name, type_, id_)
if d.exec_() == QtWidgets.QDialog.Accepted:
item.setText(column, d.name)
item.setData(column, IdRole, d.id_)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
If you use QTreeView then the handling of that role must be implemented in the model, for the following example, QStandardItemModel is used where each QStandardItem has the setData() method and data() to store and retrieve the information.
from enum import Enum
import uuid
from PyQt5 import QtCore, QtGui, QtWidgets
TypeRole = QtCore.Qt.UserRole + 1000
IdRole = QtCore.Qt.UserRole + 1001
class TypeItem(Enum):
ROOT = 0
CHILD = 1
class Dialog(QtWidgets.QDialog):
def __init__(self, name, type_, id_, parent=None):
super().__init__(parent)
self.name_le = QtWidgets.QLineEdit(name)
type_label = QtWidgets.QLabel(str(type_))
self.id_le = QtWidgets.QLineEdit(id_)
button_box = QtWidgets.QDialogButtonBox()
button_box.setOrientation(QtCore.Qt.Horizontal)
button_box.setStandardButtons(
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.name_le)
lay.addWidget(type_label)
lay.addWidget(self.id_le)
lay.addWidget(button_box)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
@property
def name(self):
return self.name_le.text()
@property
def id_(self):
return self.id_le.text()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel(self)
self.view = QtWidgets.QTreeView()
self.view.setModel(self.model)
self.view.doubleClicked.connect(self.onDoubleClicked)
self.setCentralWidget(self.view)
for i in range(3):
root_it = QtGui.QStandardItem("tlv-{}".format(i))
root_it.setData(TypeItem.ROOT, TypeRole)
root_it.setData(uuid.uuid4().hex, IdRole)
self.model.appendRow(root_it)
for j in range(3):
child_it = QtGui.QStandardItem("it-{}{}".format(i, j))
child_it.setData(TypeItem.CHILD, TypeRole)
child_it.setData(uuid.uuid4().hex, IdRole)
root_it.appendRow(child_it)
@QtCore.pyqtSlot(QtCore.QModelIndex)
def onDoubleClicked(self, index):
item = self.model.itemFromIndex(index)
name = item.text()
type_ = item.data(TypeRole)
id_ = item.data(IdRole)
d = Dialog(name, type_, id_)
if d.exec_() == QtWidgets.QDialog.Accepted:
item.setText(d.name)
item.setData(d.id_, IdRole)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())