As a Python developer who has worked extensively with GUI applications, I have found the QTreeView widget in PyQt6 to be very useful for displaying hierarchical data. Whether you’re building an application that needs to show file systems, organizational charts, or any nested data structure, QTreeView offers a flexible solution.
In this article, I will walk you through everything you need to know about using QTreeView in PyQt6. Having implemented this widget in numerous projects over the years, I can assure you that mastering QTreeView will significantly enhance your PyQt applications.
Let’s get into the QTreeView widget and explore more..
Set Up a Basic QTreeView
The first step in working with QTreeView is understanding how to set it up with a data model. Unlike simpler widgets, QTreeView follows the Model-View-Controller pattern.
Here’s how to create a basic QTreeView:
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("Basic QTreeView Example")
self.setGeometry(100, 100, 800, 600)
# Create the tree view
self.tree_view = QTreeView(self)
self.setCentralWidget(self.tree_view)
# Create the model
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Name', 'Value'])
# Add some data
root_item = QStandardItem("States")
# Add states as children
california = QStandardItem("California")
california_pop = QStandardItem("39.5M")
root_item.appendRow([california, california_pop])
texas = QStandardItem("Texas")
texas_pop = QStandardItem("29.0M")
root_item.appendRow([texas, texas_pop])
# Add cities to California
ca_cities = [("Los Angeles", "3.9M"), ("San Francisco", "0.8M")]
for city, pop in ca_cities:
city_item = QStandardItem(city)
pop_item = QStandardItem(pop)
california.appendRow([city_item, pop_item])
# Add cities to Texas
tx_cities = [("Houston", "2.3M"), ("Austin", "0.9M")]
for city, pop in tx_cities:
city_item = QStandardItem(city)
pop_item = QStandardItem(pop)
texas.appendRow([city_item, pop_item])
# Add the root item to the model
self.model.appendRow(root_item)
# Set the model for the tree view
self.tree_view.setModel(self.model)
self.tree_view.expandAll() # Expand all nodes
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())This example creates a simple hierarchical display of US states and their major cities, along with population data.
Work with Model/View Architecture
Understanding the Model/View architecture is important for effectively using QTreeView.
Use QStandardItemModel
Python QStandardItemModel is the simplest model to use with QTreeView. It’s perfect for static data that you define in your code:
# Creating a model with custom headers
model = QStandardItemModel()
model.setHorizontalHeaderLabels(['Department', 'Employees', 'Budget'])
# Adding data
engineering = QStandardItem("Engineering")
eng_employees = QStandardItem("120")
eng_budget = QStandardItem("$8.5M")
model.appendRow([engineering, eng_employees, eng_budget])
# Adding child items
frontend = QStandardItem("Frontend")
frontend_employees = QStandardItem("45")
frontend_budget = QStandardItem("$3.2M")
engineering.appendRow([frontend, frontend_employees, frontend_budget])I executed the above example code and added the screenshot below.

Use Custom Models
For more complex scenarios, you might need to create a custom model by subclassing QAbstractItemModel:
from PyQt6.QtCore import QAbstractItemModel, QModelIndex, Qt
class FileSystemModel(QAbstractItemModel):
def __init__(self, root_path):
super().__init__()
self.root_path = root_path
# Initialize your data structure here
def index(self, row, column, parent=QModelIndex()):
# Implement to create indexes for your data
pass
def parent(self, index):
# Return the parent of the model item with the given index
pass
def rowCount(self, parent=QModelIndex()):
# Return the number of rows under the given parent
pass
def columnCount(self, parent=QModelIndex()):
# Return the number of columns for the children of the given parent
pass
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
# Return the data stored under the given role for the item referred to by the index
passI executed the above example code and added the screenshot below.

Customize QTreeView Appearance
One of QTreeView’s strengths is how customizable it is. Here are some ways to enhance its appearance:
Read Create Alert Dialogs with QMessageBox in PyQt6
Method 1: Style with Stylesheets
You can enhance the visual appearance of a QTreeView in PyQt6 by using stylesheets and enabling row color alternation.
# Apply stylesheet to the tree view
self.tree_view.setStyleSheet("""
QTreeView {
background-color: #f5f5f5;
alternate-background-color: #e9e9e9;
border: 1px solid #d3d3d3;
}
QTreeView::item {
padding: 5px;
border-bottom: 1px solid #d3d3d3;
}
QTreeView::item:selected {
background-color: #0078d7;
color: white;
}
""")
# Enable alternating row colors
self.tree_view.setAlternatingRowColors(True)I executed the above example code and added the screenshot below.

Styling with stylesheets gives your QTreeView a polished, modern look while improving readability.
Check out QFontDialog in PyQt6: Create and Customize Font
Method 2: Custom Delegates
For advanced item-level customization in a QTreeView, you can implement a custom delegate using QStyledItemDelegate.
from PyQt6.QtWidgets import QStyledItemDelegate
from PyQt6.QtGui import QColor, QPainter, QFont
class CustomDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
# Customize how items are painted
if index.column() == 1 and index.data(): # Population column
painter.save()
# Fill background
if option.state & QStyleOptionViewItem.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
# Set text color
if int(index.data().replace('M', '')) > 1.0:
painter.setPen(QColor(0, 128, 0)) # Green for large cities
else:
painter.setPen(QColor(0, 0, 0)) # Black for smaller cities
# Draw text
painter.drawText(option.rect, Qt.AlignmentFlag.AlignVCenter, index.data())
painter.restore()
else:
super().paint(painter, option, index)
# Apply the delegate
self.tree_view.setItemDelegate(CustomDelegate())Custom delegates offer full control over how items are rendered, allowing dynamic styling based on data values.
Handle User Interactions
Responding to user interactions makes your QTreeView more interactive and useful.
Method 1: Connect to Signals
You can make your QTreeView interactive by connecting to key signals like selection changes and item expansion/collapse events.
# Connect to selection changed signal
self.tree_view.selectionModel().selectionChanged.connect(self.on_selection_changed)
# Connect to item expanded/collapsed signals
self.tree_view.expanded.connect(self.on_item_expanded)
self.tree_view.collapsed.connect(self.on_item_collapsed)
def on_selection_changed(self, selected, deselected):
indexes = selected.indexes()
if indexes:
item = self.model.itemFromIndex(indexes[0])
print(f"Selected: {item.text()}")
def on_item_expanded(self, index):
item = self.model.itemFromIndex(index)
print(f"Expanded: {item.text()}")
def on_item_collapsed(self, index):
item = self.model.itemFromIndex(index)
print(f"Collapsed: {item.text()}")Method 2: Implement Context Menus
Add right-click context menus to your QTreeView for user-friendly item-specific actions like view, edit, or delete.
from PyQt6.QtWidgets import QMenu
# Enable context menu
self.tree_view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.tree_view.customContextMenuRequested.connect(self.show_context_menu)
def show_context_menu(self, position):
index = self.tree_view.indexAt(position)
if not index.isValid():
return
item = self.model.itemFromIndex(index)
menu = QMenu()
view_action = menu.addAction("View Details")
edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete")
action = menu.exec(self.tree_view.viewport().mapToGlobal(position))
if action == view_action:
print(f"View details for {item.text()}")
elif action == edit_action:
print(f"Edit {item.text()}")
elif action == delete_action:
print(f"Delete {item.text()}")Read Work with QColorDialog in PyQt6
Practical Example: US County Data Viewer
This practical example showcases how to use QTreeView and QStandardItemModel to build an interactive US County Data Viewer with hierarchical data, styling, and selection feedback.
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QTreeView, QVBoxLayout,
QWidget, QLabel, QPushButton)
from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt6.QtCore import Qt
class CountyDataViewer(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("US County Data Viewer")
self.setGeometry(100, 100, 900, 700)
# Create main widget and layout
main_widget = QWidget()
layout = QVBoxLayout(main_widget)
# Add header label
header = QLabel("US States and Counties")
header.setStyleSheet("font-size: 18pt; font-weight: bold;")
layout.addWidget(header)
# Create the tree view
self.tree_view = QTreeView()
layout.addWidget(self.tree_view)
# Set up the model
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(['State/County', 'Population', 'Area (sq mi)'])
# Add sample data
self.populate_model()
# Set the model and customize the tree view
self.tree_view.setModel(self.model)
self.tree_view.setAlternatingRowColors(True)
self.tree_view.setAnimated(True)
self.tree_view.setSortingEnabled(True)
self.tree_view.setColumnWidth(0, 300)
# Add button for expanded/collapsed all
btn_layout = QVBoxLayout()
expand_btn = QPushButton("Expand All")
expand_btn.clicked.connect(self.tree_view.expandAll)
collapse_btn = QPushButton("Collapse All")
collapse_btn.clicked.connect(self.tree_view.collapseAll)
btn_layout.addWidget(expand_btn)
btn_layout.addWidget(collapse_btn)
layout.addLayout(btn_layout)
# Connect to selection changed signal
self.tree_view.selectionModel().selectionChanged.connect(self.on_selection_changed)
# Set up info display
self.info_label = QLabel("Select a state or county to view details")
layout.addWidget(self.info_label)
self.setCentralWidget(main_widget)
def populate_model(self):
# California data
ca = QStandardItem("California")
ca_pop = QStandardItem("39.5M")
ca_area = QStandardItem("163,696")
self.model.appendRow([ca, ca_pop, ca_area])
# California counties
counties = [
("Los Angeles County", "10.0M", "4,751"),
("San Diego County", "3.3M", "4,526"),
("Orange County", "3.2M", "948"),
("Riverside County", "2.4M", "7,303"),
("San Bernardino County", "2.2M", "20,105")
]
for name, pop, area in counties:
county = QStandardItem(name)
county_pop = QStandardItem(pop)
county_area = QStandardItem(area)
ca.appendRow([county, county_pop, county_area])
# Texas data
tx = QStandardItem("Texas")
tx_pop = QStandardItem("29.0M")
tx_area = QStandardItem("268,596")
self.model.appendRow([tx, tx_pop, tx_area])
# Texas counties
counties = [
("Harris County", "4.7M", "1,778"),
("Dallas County", "2.6M", "909"),
("Tarrant County", "2.1M", "902"),
("Bexar County", "2.0M", "1,256"),
("Travis County", "1.3M", "1,023")
]
for name, pop, area in counties:
county = QStandardItem(name)
county_pop = QStandardItem(pop)
county_area = QStandardItem(area)
tx.appendRow([county, county_pop, county_area])
# New York data
ny = QStandardItem("New York")
ny_pop = QStandardItem("19.8M")
ny_area = QStandardItem("54,555")
self.model.appendRow([ny, ny_pop, ny_area])
# New York counties
counties = [
("Kings County (Brooklyn)", "2.6M", "71"),
("Queens County", "2.3M", "109"),
("New York County (Manhattan)", "1.6M", "23"),
("Suffolk County", "1.5M", "912"),
("Bronx County", "1.4M", "42")
]
for name, pop, area in counties:
county = QStandardItem(name)
county_pop = QStandardItem(pop)
county_area = QStandardItem(area)
ny.appendRow([county, county_pop, county_area])
def on_selection_changed(self, selected, deselected):
indexes = selected.indexes()
if indexes:
# Get the first column's index
index = indexes[0]
row = index.row()
# Get the parent index
parent = index.parent()
# Get data from all columns
name = self.model.data(self.model.index(row, 0, parent))
population = self.model.data(self.model.index(row, 1, parent))
area = self.model.data(self.model.index(row, 2, parent))
# Update info display
if parent.isValid(): # County
state_name = self.model.data(parent)
info_text = f"<b>{name}</b> is a county in {state_name}<br>"
info_text += f"Population: {population}<br>"
info_text += f"Area: {area} square miles<br>"
# Calculate population density
pop_num = float(population.replace('M', ''))
if 'M' in population:
pop_num *= 1000000
area_num = float(area.replace(',', ''))
density = pop_num / area_num
info_text += f"Population Density: {density:.1f} people per square mile"
else: # State
info_text = f"<b>{name}</b><br>"
info_text += f"Population: {population}<br>"
info_text += f"Area: {area} square miles<br>"
info_text += f"Click on counties to see more details"
self.info_label.setText(info_text)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion") # For a consistent look across platforms
window = CountyDataViewer()
window.show()
sys.exit(app.exec())It effectively demonstrates how to organize, display, and interact with complex datasets in a user-friendly PyQt6 interface.
Best Practices for QTreeView
After years of working with QTreeView, I’ve found these practices to be crucial for creating efficient and user-friendly tree views:
- Lazy Loading: For large datasets, implement lazy loading to only load child items when a parent is expanded.
- Visual Indicators: Use icons or styling to help users understand the data hierarchy.
- Responsive UI: Keep the UI responsive by performing heavy data operations in separate threads.
- Memory Management: Be mindful of memory usage, especially when working with large datasets.
- User Preferences: Save and restore expanded states and selections to improve the user experience.
QTreeView is an incredibly versatile widget that can handle complex hierarchical data structures with ease.
By mastering its features and following best practices, you can create intuitive and useful interfaces that help users navigate even the most complex data sets.
You may read:
- Create Dynamic Tables with QTableWidget in PyQt6
- QCalendarWidget in PyQt6
- How to Use QInputDialog in PyQt6

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.