moki
moki

Reputation: 61

make treeWidget editable for specific row and column

I'm new to PyQt6 and I wanted to play around, learn and explore the possibilities.

For that I created a simple main frame with dummy data and a QTreeWidget.

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # Set up the window
        self.setWindowTitle("QTreeWidget Example")
        self.setGeometry(100, 100, 800, 600)

        # Create the QTreeWidget
        tree_widget = QTreeWidget(self)
        tree_widget.setColumnCount(6)  # Set the number of columns
        tree_widget.setHeaderLabels(["Col1", "Col2", "Col3", "Col4", "Col5", "Col6"])

        # Add some parent rows and child rows with dummy data
        root1 = QTreeWidgetItem(tree_widget, ["Root1", "Data1", "Data2", "Data3", "Data4", "Data5"])
        child1 = QTreeWidgetItem(root1, ["Child1", "DataA", "DataB", "DataC", "DataD", "DataE"])
        child2 = QTreeWidgetItem(root1, ["Child2", "DataX", "DataY", "DataZ", "DataW", "DataV"])

        root2 = QTreeWidgetItem(tree_widget, ["Root2", "Info1", "Info2", "Info3", "Info4", "Info5"])
        child3 = QTreeWidgetItem(root2, ["Child3", "DetailA", "DetailB", "DetailC", "DetailD", "DetailE"])
        child4 = QTreeWidgetItem(root2, ["Child4", "DetailX", "DetailY", "DetailZ", "DetailW", "DetailV"])

        root3 = QTreeWidgetItem(tree_widget, ["Root3", "Example1", "Example2", "Example3", "Example4", "Example5"])
        child5 = QTreeWidgetItem(root3, ["Child5", "SubDataA", "SubDataB", "SubDataC", "SubDataD", "SubDataE"])
        child6 = QTreeWidgetItem(root3, ["Child6", "SubDataX", "SubDataY", "SubDataZ", "SubDataW", "SubDataV"])

        root4 = QTreeWidgetItem(tree_widget, ["Root4", "Sample1", "Sample2", "Sample3", "Sample4", "Sample5"])
        child7 = QTreeWidgetItem(root4, ["Child7", "PartA", "PartB", "PartC", "PartD", "PartE"])
        child8 = QTreeWidgetItem(root4, ["Child8", "PartX", "PartY", "PartZ", "PartW", "PartV"])

        root5 = QTreeWidgetItem(tree_widget, ["Root5", "Test1", "Test2", "Test3", "Test4", "Test5"])
        child9 = QTreeWidgetItem(root5, ["Child9", "ItemA", "ItemB", "ItemC", "ItemD", "ItemE"])
        child10 = QTreeWidgetItem(root5, ["Child10", "ItemX", "ItemY", "ItemZ", "ItemW", "ItemV"])

        # Layout setup
        layout = QVBoxLayout()
        layout.addWidget(tree_widget)

        # Set up the central widget
        central_widget = QWidget(self)
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

# Main application entry
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

I could manage to make the QTreeWidget editable with some flags.

Is it possible to make the QTreeView items editable based on their location?

For example, for all root rows in column2 and column3, or for all child rows only the last column.

Upvotes: 1

Views: 38

Answers (1)

musicamante
musicamante

Reputation: 48444

First of all, it's important to be aware that the *Widget classes that extend Qt item views (QTreeWidget, QTableWidget and QListWidget extending QTreeView, QTableView and QListView respectively) are higher level classes that provide only basic and simple functionalities.
If you're looking for a more advanced behavior, you should at least use the base view class along with a QStandardItemModel or a custom QAbstractItemModel subclass (QAbstractListModel for QListView and QAbstractTableModel for QTableView).

The most important aspect of QTreeWidget is that it treats its items as "groups of column items". This is normally not a real problem, as QTreeWidgetItem provides access functions that also require a column reference for its getter and setter functions, but not all functions do that.

Qt item views normally access the flags() of a model index in order to know if an item can be edited and eventually begin its editing, by asking its item delegate to create an editor. If the value returned by flags() also contains the Qt.ItemFlags.ItemIsEditable enum, then editing is normally started.

Similarly to the other higher level classes (QTableWidget and QListWidget), QTreeWidget has an internal model that actually accesses the real "items" when queried: for example, when the view tries to access the data() of an index, it actually calls the data() function of the related item, considering the column of that item.
The flags() (and related setFlags()) function is among the functions mentioned above that don't take any column argument, therefore there is no direct way to define (and get) the "editable flags" based on the actual column selected by the user: you can only set if all columns of an "item" are editable or not.

Luckily, the edit() function of QAbstractItemView (from which QTreeWidget inherits, through QTreeView), is "virtual", meaning that we can create a subclass to override it, and it will be called internally by Qt when necessary.

By overriding that function, we can then return False in case we don't want to allow editing to begin with, or return the result of the default implementation.

Note that:

  • by default, QTreeWidgetItems are not editable, therefore you should manually set the ItemIsEditable flag; the common practice is to use a custom QTreeWidgetItem subclass that automatically sets that flag in its __init__;
  • QAbstractItemView also provides an identically named edit() slot, and the override should consider that, since Python doesn't provides overloads;

Based on your request (only allow editing of the second and third column of a root item, or the last column of child items), here is a possible solution:

class CustomTreeWidget(QTreeWidget):
    def edit(self, *args):
        if len(args) != 3:
            # the `edit(index)` slot is being called, just call the base 
            # implementation, which will internally call the real `edit()`
            # function (and, therefore, this override) with proper arguments.
            return super().edit(*args)
        index, trigger, event = args
        if not index.parent().isValid():
            # prevent editing of a root item if the column is not the 
            # second or the third
            if not index.column() in (1, 2):
                return False
        elif index.column() != self.columnCount() - 1:
            # prevent editing if it's not the last column of a child item
            return False
        return super().edit(*args)


class CustomTreeItem(QTreeWidgetItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setFlags(self.flags() | Qt.ItemFlag.ItemIsEditable)


class MainWindow(QMainWindow):
    def __init__(self):
        ...
        tree_widget = CustomTreeWidget(self)
        ...
        root1 = CustomTreeItem(tree_widget, ["Root1", "Data1", "Data2", "Data3", "Data4", "Data5"])
        ... # all other items must be created using CustomTreeItem

As explained above, if you need a more controlled approach (for instance based on the level within the tree), it is more appropriate to use the standard QTreeView class along with a model such as QStandardItemModel. Using a QAbstractItemModel as a base for a custom tree model is possible alternative, but its implementation is not immediate (see the Editable Tree Model Example).

Finally, I strongly suggest you to take your time and carefully read the Model/View Programming guide.

Upvotes: 0

Related Questions