<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - qabstractitemmodel</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/qabstractitemmodel.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-01-08T00:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>QTreeView with QAbstractItemModel in PyQt6 — Build hierarchical tree displays using custom models</title><link href="https://www.pythonguis.com/faq/qtreeview-tutorial/" rel="alternate"/><published>2021-01-08T00:00:00+00:00</published><updated>2021-01-08T00:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-01-08:/faq/qtreeview-tutorial/</id><summary type="html">How do I use &lt;code&gt;QTreeView&lt;/code&gt; with a custom model in PyQt6? There's no &lt;code&gt;QAbstractTreeModel&lt;/code&gt;, so how do I implement &lt;code&gt;QAbstractItemModel&lt;/code&gt; to display hierarchical (tree) data?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;How do I use &lt;code&gt;QTreeView&lt;/code&gt; with a custom model in PyQt6? There's no &lt;code&gt;QAbstractTreeModel&lt;/code&gt;, so how do I implement &lt;code&gt;QAbstractItemModel&lt;/code&gt; to display hierarchical (tree) data?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've used &lt;code&gt;QTableView&lt;/code&gt; with &lt;code&gt;QAbstractTableModel&lt;/code&gt;, you already know the basics of &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-architecture/"&gt;Qt's model/view architecture&lt;/a&gt;. But when it comes to displaying &lt;em&gt;hierarchical&lt;/em&gt; data &amp;mdash; data with parent-child relationships &amp;mdash; you need &lt;code&gt;QTreeView&lt;/code&gt;. And since Qt doesn't provide a ready-made &lt;code&gt;QAbstractTreeModel&lt;/code&gt;, you need to subclass &lt;code&gt;QAbstractItemModel&lt;/code&gt; yourself.&lt;/p&gt;
&lt;p&gt;This can feel daunting at first, but once you understand the structure, it follows a clear pattern. In this tutorial, we'll walk through everything step by step: from displaying a simple tree, to building a fully custom tree model that you can adapt to your own data.&lt;/p&gt;
&lt;h2 id="a-simple-qtreeview-with-qstandarditemmodel"&gt;A Simple QTreeView with QStandardItemModel&lt;/h2&gt;
&lt;p&gt;Before we dive into custom models, let's start with something quick and visual. Qt provides &lt;code&gt;QStandardItemModel&lt;/code&gt;, a general-purpose model that works with &lt;code&gt;QTreeView&lt;/code&gt; out of the box. This is a great way to see a tree in action without writing much code.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTreeView
)
from PyQt6.QtGui import QStandardItemModel, QStandardItem


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTreeView Example")

        tree = QTreeView(self)
        self.setCentralWidget(tree)

        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(["Name", "Description"])

        # Add some items.
        parent_item = QStandardItem("Mammals")
        parent_item_desc = QStandardItem("Warm-blooded animals")
        model.appendRow([parent_item, parent_item_desc])

        child1 = QStandardItem("Cat")
        child1_desc = QStandardItem("A small domesticated carnivore")
        parent_item.appendRow([child1, child1_desc])

        child2 = QStandardItem("Dog")
        child2_desc = QStandardItem("A loyal domesticated carnivore")
        parent_item.appendRow([child2, child2_desc])

        parent_item2 = QStandardItem("Reptiles")
        parent_item2_desc = QStandardItem("Cold-blooded animals")
        model.appendRow([parent_item2, parent_item2_desc])

        child3 = QStandardItem("Snake")
        child3_desc = QStandardItem("A legless reptile")
        parent_item2.appendRow([child3, child3_desc])

        tree.setModel(model)
        tree.expandAll()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a tree with expandable nodes, two columns, and parent-child relationships displayed visually. &lt;code&gt;QStandardItemModel&lt;/code&gt; stores data internally, so it's convenient for small or static trees.&lt;/p&gt;
&lt;p&gt;But what if your data comes from somewhere else &amp;mdash; a database, an API, a file? Or what if you need more control over how data is structured and accessed? That's where a custom &lt;code&gt;QAbstractItemModel&lt;/code&gt; comes in.&lt;/p&gt;
&lt;h2 id="understanding-the-tree-model-structure"&gt;Understanding the Tree Model Structure&lt;/h2&gt;
&lt;p&gt;In a table model (&lt;code&gt;QAbstractTableModel&lt;/code&gt;), every item lives in a flat grid of rows and columns. In a tree model, items also have &lt;em&gt;parents&lt;/em&gt;. A top-level item's parent is the "root" of the model (an invalid &lt;code&gt;QModelIndex&lt;/code&gt;). A child item's parent is another item in the tree.&lt;/p&gt;
&lt;p&gt;Qt's model/view system uses &lt;code&gt;QModelIndex&lt;/code&gt; objects to refer to items. Each &lt;code&gt;QModelIndex&lt;/code&gt; carries three pieces of information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;row&lt;/strong&gt; &amp;mdash; the item's row within its parent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;column&lt;/strong&gt; &amp;mdash; the column&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;internalPointer&lt;/strong&gt; (or internalId) &amp;mdash; a reference to the underlying data object for that item&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you subclass &lt;code&gt;QAbstractItemModel&lt;/code&gt;, Qt will call methods on your model asking questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"How many rows does this parent have?"&lt;/em&gt; &amp;rarr; &lt;code&gt;rowCount(parent)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"How many columns does this parent have?"&lt;/em&gt; &amp;rarr; &lt;code&gt;columnCount(parent)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"What data is at this index?"&lt;/em&gt; &amp;rarr; &lt;code&gt;data(index, role)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Give me an index for row R, column C, under this parent"&lt;/em&gt; &amp;rarr; &lt;code&gt;index(row, column, parent)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"What is the parent of this index?"&lt;/em&gt; &amp;rarr; &lt;code&gt;parent(index)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;index()&lt;/code&gt; and &lt;code&gt;parent()&lt;/code&gt; methods are what make tree models different from table models. They define the hierarchy.&lt;/p&gt;
&lt;h2 id="the-treeitem-helper-class"&gt;The TreeItem Helper Class&lt;/h2&gt;
&lt;p&gt;To keep our model clean, we'll create a small helper class called &lt;code&gt;TreeItem&lt;/code&gt;. Each &lt;code&gt;TreeItem&lt;/code&gt; represents one node in the tree. It holds its own data, a reference to its parent item, and a list of child items.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class TreeItem:
    def __init__(self, data, parent=None):
        self.item_data = data
        self.parent_item = parent
        self.child_items = []

    def appendChild(self, child):
        self.child_items.append(child)

    def child(self, row):
        if 0 &amp;lt;= row &amp;lt; len(self.child_items):
            return self.child_items[row]
        return None

    def childCount(self):
        return len(self.child_items)

    def columnCount(self):
        return len(self.item_data)

    def data(self, column):
        if 0 &amp;lt;= column &amp;lt; len(self.item_data):
            return self.item_data[column]
        return None

    def row(self):
        if self.parent_item:
            return self.parent_item.child_items.index(self)
        return 0

    def parent(self):
        return self.parent_item
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each &lt;code&gt;TreeItem&lt;/code&gt; stores its data as a list &amp;mdash; one entry per column. For example, &lt;code&gt;["Cat", "A small domesticated carnivore"]&lt;/code&gt; for a two-column tree. The &lt;code&gt;row()&lt;/code&gt; method figures out which row this item occupies within its parent's list of children.&lt;/p&gt;
&lt;p&gt;This class has no dependency on Qt at all. It's just plain Python, which makes it easy to test and reason about.&lt;/p&gt;
&lt;h2 id="building-the-custom-tree-model"&gt;Building the Custom Tree Model&lt;/h2&gt;
&lt;p&gt;Now let's subclass &lt;code&gt;QAbstractItemModel&lt;/code&gt;. There are five methods we &lt;em&gt;must&lt;/em&gt; implement:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;index(row, column, parent)&lt;/code&gt; &amp;mdash; return a &lt;code&gt;QModelIndex&lt;/code&gt; for the given position&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent(index)&lt;/code&gt; &amp;mdash; return the parent &lt;code&gt;QModelIndex&lt;/code&gt; of the given index&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rowCount(parent)&lt;/code&gt; &amp;mdash; return the number of children under the given parent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;columnCount(parent)&lt;/code&gt; &amp;mdash; return the number of columns&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data(index, role)&lt;/code&gt; &amp;mdash; return the data for a given index and role&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's build the complete model:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtCore import QAbstractItemModel, QModelIndex, Qt


class TreeModel(QAbstractItemModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.root_item = TreeItem(["Name", "Description"])
        self._setup_model_data(data, self.root_item)

    def index(self, row, column, parent=QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        child_item = parent_item.child(row)
        if child_item:
            return self.createIndex(row, column, child_item)
        return QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        child_item = index.internalPointer()
        parent_item = child_item.parent()

        if parent_item == self.root_item:
            return QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent=QModelIndex()):
        if parent.column() &amp;gt; 0:
            return 0

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        return parent_item.childCount()

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return parent.internalPointer().columnCount()
        return self.root_item.columnCount()

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None

        if role != Qt.ItemDataRole.DisplayRole:
            return None

        item = index.internalPointer()
        return item.data(index.column())

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if (
            orientation == Qt.Orientation.Horizontal
            and role == Qt.ItemDataRole.DisplayRole
        ):
            return self.root_item.data(section)
        return None

    def _setup_model_data(self, data, parent):
        for name, description, children in data:
            item = TreeItem([name, description], parent)
            parent.appendChild(item)
            if children:
                self._setup_model_data(children, item)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let's walk through the methods that matter most here.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;index()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is called by the view whenever it needs to refer to a specific cell. It takes a &lt;code&gt;row&lt;/code&gt;, &lt;code&gt;column&lt;/code&gt;, and &lt;code&gt;parent&lt;/code&gt; index. If the parent is invalid (i.e., the root), we use &lt;code&gt;self.root_item&lt;/code&gt;. Otherwise, we get the parent's &lt;code&gt;TreeItem&lt;/code&gt; via &lt;code&gt;internalPointer()&lt;/code&gt;, look up the child at the given row, and create a new &lt;code&gt;QModelIndex&lt;/code&gt; pointing to it using &lt;code&gt;createIndex()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The call to &lt;code&gt;self.hasIndex(row, column, parent)&lt;/code&gt; is a safety check &amp;mdash; it ensures the requested row and column are within valid bounds.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;parent()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is the reverse of &lt;code&gt;index()&lt;/code&gt;. Given a &lt;code&gt;QModelIndex&lt;/code&gt;, it returns the index of that item's parent. We get the &lt;code&gt;TreeItem&lt;/code&gt; from the index's &lt;code&gt;internalPointer()&lt;/code&gt;, find its parent, and create an index for it. If the parent is the root item, we return an invalid &lt;code&gt;QModelIndex()&lt;/code&gt; &amp;mdash; that's how Qt represents "no parent" (i.e., a top-level item).&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;_setup_model_data()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is our own method to populate the tree from a Python data structure. We're using a list of tuples where each tuple is &lt;code&gt;(name, description, children)&lt;/code&gt;, and &lt;code&gt;children&lt;/code&gt; is either another list of tuples or an empty list. This recursive structure maps naturally to a tree.&lt;/p&gt;
&lt;h2 id="putting-it-all-together"&gt;Putting It All Together&lt;/h2&gt;
&lt;p&gt;Here's a complete working example that combines &lt;code&gt;TreeItem&lt;/code&gt;, &lt;code&gt;TreeModel&lt;/code&gt;, and a &lt;code&gt;QTreeView&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QTreeView


class TreeItem:
    def __init__(self, data, parent=None):
        self.item_data = data
        self.parent_item = parent
        self.child_items = []

    def appendChild(self, child):
        self.child_items.append(child)

    def child(self, row):
        if 0 &amp;lt;= row &amp;lt; len(self.child_items):
            return self.child_items[row]
        return None

    def childCount(self):
        return len(self.child_items)

    def columnCount(self):
        return len(self.item_data)

    def data(self, column):
        if 0 &amp;lt;= column &amp;lt; len(self.item_data):
            return self.item_data[column]
        return None

    def row(self):
        if self.parent_item:
            return self.parent_item.child_items.index(self)
        return 0

    def parent(self):
        return self.parent_item


class TreeModel(QAbstractItemModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.root_item = TreeItem(["Name", "Description"])
        self._setup_model_data(data, self.root_item)

    def index(self, row, column, parent=QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        child_item = parent_item.child(row)
        if child_item:
            return self.createIndex(row, column, child_item)
        return QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        child_item = index.internalPointer()
        parent_item = child_item.parent()

        if parent_item == self.root_item:
            return QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent=QModelIndex()):
        if parent.column() &amp;gt; 0:
            return 0

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        return parent_item.childCount()

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return parent.internalPointer().columnCount()
        return self.root_item.columnCount()

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None

        if role != Qt.ItemDataRole.DisplayRole:
            return None

        item = index.internalPointer()
        return item.data(index.column())

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if (
            orientation == Qt.Orientation.Horizontal
            and role == Qt.ItemDataRole.DisplayRole
        ):
            return self.root_item.data(section)
        return None

    def _setup_model_data(self, data, parent):
        for name, description, children in data:
            item = TreeItem([name, description], parent)
            parent.appendChild(item)
            if children:
                self._setup_model_data(children, item)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom QTreeView Model")
        self.resize(500, 400)

        data = [
            ("Mammals", "Warm-blooded animals", [
                ("Cat", "A small domesticated carnivore", []),
                ("Dog", "A loyal domesticated carnivore", [
                    ("Labrador", "A friendly breed", []),
                    ("Poodle", "A curly-haired breed", []),
                ]),
                ("Whale", "A large marine mammal", []),
            ]),
            ("Reptiles", "Cold-blooded animals", [
                ("Snake", "A legless reptile", []),
                ("Lizard", "A four-legged reptile", [
                    ("Gecko", "A small lizard", []),
                    ("Iguana", "A large herbivorous lizard", []),
                ]),
            ]),
            ("Birds", "Feathered animals", [
                ("Eagle", "A large bird of prey", []),
                ("Parrot", "A colorful talking bird", []),
            ]),
        ]

        model = TreeModel(data)
        tree = QTreeView(self)
        tree.setModel(model)
        tree.expandAll()
        tree.setAlternatingRowColors(True)

        # Resize columns to fit content.
        for col in range(model.columnCount()):
            tree.resizeColumnToContents(col)

        self.setCentralWidget(tree)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, you'll see a tree with three top-level categories (Mammals, Reptiles, Birds), each containing children, and some of those children have children of their own. You can collapse and expand nodes, and the two columns (Name and Description) display neatly.&lt;/p&gt;
&lt;h2 id="how-the-pieces-fit-together"&gt;How the Pieces Fit Together&lt;/h2&gt;
&lt;p&gt;Here's a summary of how &lt;code&gt;QTreeView&lt;/code&gt;, &lt;code&gt;TreeModel&lt;/code&gt;, and &lt;code&gt;TreeItem&lt;/code&gt; work together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TreeItem&lt;/code&gt;&lt;/strong&gt; stores your actual data. Each item knows its parent and children. This is your data layer &amp;mdash; you can adapt it to wrap any data structure you like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;TreeModel&lt;/code&gt;&lt;/strong&gt; subclasses &lt;code&gt;QAbstractItemModel&lt;/code&gt; and translates between Qt's &lt;code&gt;QModelIndex&lt;/code&gt; system and your &lt;code&gt;TreeItem&lt;/code&gt; objects. It answers Qt's questions about the structure and content of your data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;QTreeView&lt;/code&gt;&lt;/strong&gt; is the visual widget. It asks the model for data and displays it. You never need to tell the view about your tree structure directly &amp;mdash; it discovers everything through the model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation means you can change how your data is stored (in &lt;code&gt;TreeItem&lt;/code&gt;) without touching the view, or swap the view for a different one without changing the model. If you're working with tabular data instead, the same principles apply &amp;mdash; see our guide to &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qtableview-modelviews-numpy-pandas/"&gt;using QTableView with NumPy and Pandas&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="responding-to-selection-changes"&gt;Responding to Selection Changes&lt;/h2&gt;
&lt;p&gt;You'll often want to do something when the user clicks on an item in the tree. You can connect to the view's &lt;code&gt;selectionModel()&lt;/code&gt; to respond to selection changes using &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;tree.selectionModel().currentChanged.connect(self.on_current_changed)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then define the slot:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def on_current_changed(self, current, previous):
    item = current.internalPointer()
    if item:
        print(f"Selected: {item.data(0)} - {item.data(1)}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This retrieves the &lt;code&gt;TreeItem&lt;/code&gt; from the index and accesses its data directly.&lt;/p&gt;
&lt;h2 id="making-items-editable"&gt;Making Items Editable&lt;/h2&gt;
&lt;p&gt;To make your tree editable, you need to do two things in your model: implement &lt;code&gt;setData()&lt;/code&gt; and return the right flags from &lt;code&gt;flags()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add these methods to &lt;code&gt;TreeModel&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def flags(self, index):
    if not index.isValid():
        return Qt.ItemFlag.NoItemFlags
    return (
        Qt.ItemFlag.ItemIsEnabled
        | Qt.ItemFlag.ItemIsSelectable
        | Qt.ItemFlag.ItemIsEditable
    )

def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
    if role != Qt.ItemDataRole.EditRole:
        return False

    item = index.internalPointer()
    if item:
        item.item_data[index.column()] = value
        self.dataChanged.emit(index, index, [role])
        return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You also need to update &lt;code&gt;data()&lt;/code&gt; to respond to &lt;code&gt;EditRole&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def data(self, index, role=Qt.ItemDataRole.DisplayRole):
    if not index.isValid():
        return None

    if role not in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
        return None

    item = index.internalPointer()
    return item.data(index.column())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With these changes, double-clicking any cell in the tree will open an editor, and your changes will be stored in the &lt;code&gt;TreeItem&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="adding-and-removing-items"&gt;Adding and Removing Items&lt;/h2&gt;
&lt;p&gt;To dynamically add or remove items, you need to use &lt;code&gt;beginInsertRows()&lt;/code&gt;/&lt;code&gt;endInsertRows()&lt;/code&gt; and &lt;code&gt;beginRemoveRows()&lt;/code&gt;/&lt;code&gt;endRemoveRows()&lt;/code&gt; to let the view know the model is changing. Here are two methods you can add to &lt;code&gt;TreeModel&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def addChild(self, parent_index, name, description):
    if not parent_index.isValid():
        parent_item = self.root_item
    else:
        parent_item = parent_index.internalPointer()

    row = parent_item.childCount()
    self.beginInsertRows(parent_index, row, row)
    new_item = TreeItem([name, description], parent_item)
    parent_item.appendChild(new_item)
    self.endInsertRows()

def removeChild(self, index):
    if not index.isValid():
        return

    item = index.internalPointer()
    parent_item = item.parent()

    if parent_item is None:
        return

    if parent_item == self.root_item:
        parent_index = QModelIndex()
    else:
        parent_index = self.createIndex(parent_item.row(), 0, parent_item)

    row = item.row()
    self.beginRemoveRows(parent_index, row, row)
    parent_item.child_items.pop(row)
    self.endRemoveRows()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;beginInsertRows()&lt;/code&gt; and &lt;code&gt;endInsertRows()&lt;/code&gt; calls bracket the actual data modification. This pattern tells the view exactly what's changing, so it can update itself efficiently.&lt;/p&gt;
&lt;h2 id="complete-editable-example-with-addremove"&gt;Complete Editable Example with Add/Remove&lt;/h2&gt;
&lt;p&gt;Here's a full example that brings everything together &amp;mdash; an editable tree with &lt;a href="https://www.pythonguis.com/docs/qpushbutton/"&gt;buttons&lt;/a&gt; to add and remove items:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt6.QtWidgets import (
    QApplication, QHBoxLayout, QMainWindow, QPushButton,
    QTreeView, QVBoxLayout, QWidget,
)


class TreeItem:
    def __init__(self, data, parent=None):
        self.item_data = data
        self.parent_item = parent
        self.child_items = []

    def appendChild(self, child):
        self.child_items.append(child)

    def child(self, row):
        if 0 &amp;lt;= row &amp;lt; len(self.child_items):
            return self.child_items[row]
        return None

    def childCount(self):
        return len(self.child_items)

    def columnCount(self):
        return len(self.item_data)

    def data(self, column):
        if 0 &amp;lt;= column &amp;lt; len(self.item_data):
            return self.item_data[column]
        return None

    def row(self):
        if self.parent_item:
            return self.parent_item.child_items.index(self)
        return 0

    def parent(self):
        return self.parent_item


class TreeModel(QAbstractItemModel):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.root_item = TreeItem(["Name", "Description"])
        self._setup_model_data(data, self.root_item)

    def index(self, row, column, parent=QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        child_item = parent_item.child(row)
        if child_item:
            return self.createIndex(row, column, child_item)
        return QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QModelIndex()

        child_item = index.internalPointer()
        parent_item = child_item.parent()

        if parent_item == self.root_item:
            return QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent=QModelIndex()):
        if parent.column() &amp;gt; 0:
            return 0

        if not parent.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent.internalPointer()

        return parent_item.childCount()

    def columnCount(self, parent=QModelIndex()):
        if parent.isValid():
            return parent.internalPointer().columnCount()
        return self.root_item.columnCount()

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None

        if role not in (
            Qt.ItemDataRole.DisplayRole,
            Qt.ItemDataRole.EditRole,
        ):
            return None

        item = index.internalPointer()
        return item.data(index.column())

    def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
        if role != Qt.ItemDataRole.EditRole:
            return False

        item = index.internalPointer()
        if item:
            item.item_data[index.column()] = value
            self.dataChanged.emit(index, index, [role])
            return True
        return False

    def flags(self, index):
        if not index.isValid():
            return Qt.ItemFlag.NoItemFlags
        return (
            Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEditable
        )

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if (
            orientation == Qt.Orientation.Horizontal
            and role == Qt.ItemDataRole.DisplayRole
        ):
            return self.root_item.data(section)
        return None

    def addChild(self, parent_index, name, description):
        if not parent_index.isValid():
            parent_item = self.root_item
        else:
            parent_item = parent_index.internalPointer()

        row = parent_item.childCount()
        self.beginInsertRows(parent_index, row, row)
        new_item = TreeItem([name, description], parent_item)
        parent_item.appendChild(new_item)
        self.endInsertRows()

    def removeChild(self, index):
        if not index.isValid():
            return

        item = index.internalPointer()
        parent_item = item.parent()

        if parent_item is None:
            return

        if parent_item == self.root_item:
            parent_index = QModelIndex()
        else:
            parent_index = self.createIndex(
                parent_item.row(), 0, parent_item
            )

        row = item.row()
        self.beginRemoveRows(parent_index, row, row)
        parent_item.child_items.pop(row)
        self.endRemoveRows()

    def _setup_model_data(self, data, parent):
        for name, description, children in data:
            item = TreeItem([name, description], parent)
            parent.appendChild(item)
            if children:
                self._setup_model_data(children, item)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Editable QTreeView")
        self.resize(550, 450)

        data = [
            ("Mammals", "Warm-blooded animals", [
                ("Cat", "A small domesticated carnivore", []),
                ("Dog", "A loyal domesticated carnivore", [
                    ("Labrador", "A friendly breed", []),
                    ("Poodle", "A curly-haired breed", []),
                ]),
            ]),
            ("Reptiles", "Cold-blooded animals", [
                ("Snake", "A legless reptile", []),
                ("Lizard", "A four-legged reptile", []),
            ]),
        ]

        self.model = TreeModel(data)
        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.expandAll()
        self.tree.setAlternatingRowColors(True)

        for col in range(self.model.columnCount()):
            self.tree.resizeColumnToContents(col)

        add_button = QPushButton("Add Child")
        add_button.clicked.connect(self.add_child)

        remove_button = QPushButton("Remove Selected")
        remove_button.clicked.connect(self.remove_selected)

        button_layout = QHBoxLayout()
        button_layout.addWidget(add_button)
        button_layout.addWidget(remove_button)

        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        layout.addLayout(button_layout)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def add_child(self):
        indexes = self.tree.selectedIndexes()
        if indexes:
            # Use the first selected index's row-level index (column 0).
            parent_index = indexes[0].siblingAtColumn(0)
        else:
            parent_index = QModelIndex()

        self.model.addChild(parent_index, "New Item", "Description")
        self.tree.expandAll()

    def remove_selected(self):
        indexes = self.tree.selectedIndexes()
        if indexes:
            self.model.removeChild(indexes[0])


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Expand/collapse&lt;/strong&gt; tree nodes by clicking the arrows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Double-click&lt;/strong&gt; any cell to edit it in place&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select an item&lt;/strong&gt; and click "Add Child" to add a new child under it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select an item&lt;/strong&gt; and click "Remove Selected" to delete it&lt;/li&gt;
&lt;/ul&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="python"/><category term="qtreeview"/><category term="model-view"/><category term="qabstractitemmodel"/><category term="qt"/><category term="qt6"/></entry></feed>