1

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • what is folder_if? – eyllanesc Feb 09 '20 at 15:12
  • @eyllanesc sorry, Autocorrect from phone. I mean “Folder_ID” – SteveRyherd Feb 09 '20 at 15:45
  • From what I understand you, you use QTreeView/QTreeWidget to show the path of files and directories so you want to rename that file/folder so why do you want an "id"? – eyllanesc Feb 09 '20 at 15:48
  • So are you using `QTreeWidget` with default `QTreeWidgetItem`s or `QTreeView` with custom `QAbstractItemModel`-implementation? – pasbi Feb 09 '20 at 16:00
  • @eyllanesc - I was trying to just use a simple example. Ideally I want to list a set of nodes and then when double-clicking on them open a seperate window to edit a large array of data related to that node. So this view is just displaying a list of element names, the next screen will have a widget showing properties of the node as well as a large widget to edit the properties of its children – SteveRyherd Feb 09 '20 at 16:04
  • @pasbi - I don’t have a preference to any type. – SteveRyherd Feb 09 '20 at 16:07
  • Use `QTreeWidget` and write your custom data using [`QTreeWidgetItem::setData`](https://doc.qt.io/qt-5/qtreewidgetitem.html#setData) and read it with [`QTreeWidgetItem::data`](https://doc.qt.io/qt-5/qtreewidgetitem.html#data), where `role` is some custom role (see [`Qt::UserRole` -- `QItemDataRole`](https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum)) – pasbi Feb 09 '20 at 16:08
  • @SteveRyherd Okay, now I understand you better – eyllanesc Feb 09 '20 at 16:09

1 Answers1

3

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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241