I made a model that adds data to a QTreeView. I want to add a widget (eg: QProgressbar) as a child element to each row within that QTreeView. So that every time if we click on each row we can see the progressbar as a childItem for that row
main.py
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QHeaderView, QPushButton
from PySide2 import QtCore
from TreeViewUI import Ui_MainWindow
from custom_models.table_model import CustomTableModel
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, data):
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.table_model = CustomTableModel(data)
self.treeView.setIconSize(QtCore.QSize(360 / 4, 640 / 4))
self.treeView.header().setSectionResizeMode(QHeaderView.ResizeToContents)
self.treeView.setModel(self.table_model)
def get_data():
content_list = [
{
"user_id": 101,
"user_name": "User_1",
"user_image": "images/demo.jpg",
"user_details": "details_1",
"user_progress": 45,
},
{
"user_id": 102,
"user_name": "User_2",
"user_image": "images/demo.jpg",
"user_details": "details_2",
"user_progress": 33,
},
{
"user_id": 103,
"user_name": "User_3",
"user_image": "images/demo.jpg",
"user_details": "details_3",
"user_progress": 80,
},
]
return content_list
if __name__ == '__main__':
app = QApplication([])
data = get_data()
main_window = MainWindow(data)
main_window.showMaximized()
sys.exit(app.exec_())
table_model.py
from PySide2.QtCore import Qt, QAbstractTableModel, QModelIndex
from PySide2.QtGui import QColor, QImage, QIcon, QPixmap
class CustomTableModel(QAbstractTableModel):
def __init__(self, data):
QAbstractTableModel.__init__(self)
self.content_id = []
self.content_names = []
self.content_url = []
self.content_variant = []
self.content_progress = []
for content_data in data:
self.content_id.append(content_data["user_id"])
self.content_names.append(content_data["user_name"])
self.content_url.append(content_data["user_image"])
self.content_variant.append(content_data["user_details"])
self.content_progress.append(content_data["user_progress"])
self.row_count = len(self.content_id)
self.column_count = 4
def rowCount(self, parent=QModelIndex()):
return self.row_count
def columnCount(self, parent=QModelIndex()):
return self.column_count
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return ("ID", "NAME", "IMAGE PREVIEW", "DETAILS")[section]
def data(self, index, role=Qt.DisplayRole):
column = index.column()
row = index.row()
if role == Qt.DisplayRole:
if column == 0:
content_id = self.content_id[row]
return content_id
elif column == 1:
return self.content_names[row]
elif column == 3:
return self.content_variant[row]
elif role == Qt.DecorationRole:
if column == 2:
image_path = self.content_url[row]
image = QImage()
image.load(image_path)
icon = QIcon()
icon.addPixmap(QPixmap.fromImage(image))
return icon
elif role == Qt.TextAlignmentRole:
return Qt.AlignLeft
TreeViewUI.py
from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
QRect, QSize, QUrl, Qt)
from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont,
QFontDatabase, QIcon, QLinearGradient, QPalette, QPainter, QPixmap,
QRadialGradient)
from PySide2.QtWidgets import *
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.resize(817, 600)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.verticalLayout = QVBoxLayout()
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(self.centralwidget)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setObjectName(u"treeView")
self.verticalLayout.addWidget(self.treeView)
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"TreeView", None))
# retranslateUi
These are the files I created to test for a sample implementation for QTreeView. The basic structure of the Treeview would be as follows:
User_ID User_Name User_Image User_Details
101 ABC abc.jpg abc
QProgressBar widget for the above user(101)# QProgressbar as the childItem for each row.
102 DEF def.jpg def
QProgressBar widget for the above user(102)
103 GHI ghi.png ghi
QProgressBar widget for the above user(103)
If the structure is of tree type then the model must have that structure so that model based on QAbstractItemModel could be used but to make it simpler I have implemented it using the QStandardItemModel class where I have established the values in the appropriate roles, and so that the progressbar It is displayed in a new file so you must be a child of the item. To show the progressbar I have not set a widget but it has been painted using the QStyle through a delegate:
from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon, QStandardItem, QStandardItemModel
class UserModel(QStandardItemModel):
def __init__(self, parent=None):
super(UserModel, self).__init__(parent)
self.setColumnCount(4)
def appendUser(self, id_, name, image, details, progress):
items = []
row = self.rowCount()
for text, column in zip((id_, name, details), (0, 1, 3)):
it = QStandardItem()
it.setData(text, Qt.DisplayRole)
self.setItem(row, column, it)
items.append(it)
image_item = QStandardItem()
image_item.setData(QIcon(image), Qt.DecorationRole)
self.setItem(row, 2, image_item)
progress_item = QStandardItem()
progress_item.setData(progress, Qt.UserRole)
items[0].appendRow(progress_item)
import os
import sys
from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import (
QApplication,
QMainWindow,
QHeaderView,
QStyledItemDelegate,
QStyleOptionProgressBar,
QStyle,
)
from TreeViewUI import Ui_MainWindow
from custom_models.table_model import UserModel
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class ProgressDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
if index.parent().isValid():
r = option.rect
r.setWidth(200)
progress = index.data(Qt.UserRole)
progress_option = QStyleOptionProgressBar()
progress_option.rect = r
progress_option.minimum = 0
progress_option.maximum = 100
progress_option.progress = progress
progress_option.text = "{}%".format(progress)
progress_option.textVisible = True
QApplication.style().drawControl(
QStyle.CE_ProgressBar, progress_option, painter
)
return
super(ProgressDelegate, self).paint(painter, option, index)
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, data):
super(MainWindow, self).__init__()
self.setupUi(self)
self.model = UserModel(self)
for content_data in data:
self.model.appendUser(
content_data["user_id"],
content_data["user_name"],
os.path.join(CURRENT_DIR, content_data["user_image"]),
content_data["user_details"],
content_data["user_progress"],
)
self.treeView.setIconSize(QSize(360 / 4, 640 / 4))
self.treeView.header().setSectionResizeMode(QHeaderView.ResizeToContents)
self.treeView.setModel(self.model)
delegate = ProgressDelegate(self.treeView)
self.treeView.setItemDelegate(delegate)
def get_data():
content_list = [
{
"user_id": 101,
"user_name": "User_1",
"user_image": "images/demo.jpg",
"user_details": "details_1",
"user_progress": 45,
},
{
"user_id": 102,
"user_name": "User_2",
"user_image": "images/demo.jpg",
"user_details": "details_2",
"user_progress": 33,
},
{
"user_id": 103,
"user_name": "User_3",
"user_image": "images/demo.jpg",
"user_details": "details_3",
"user_progress": 80,
},
]
return content_list
if __name__ == "__main__":
app = QApplication([])
data = get_data()
main_window = MainWindow(data)
main_window.showMaximized()
sys.exit(app.exec_())
Another possible solution is to create a persistent editor through the delegate:
class ProgressDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
if index.parent().isValid():
view = option.widget
if isinstance(view, QTreeView) and index.model() is view.model():
view.openPersistentEditor(index)
return
super(ProgressDelegate, self).paint(painter, option, index)
def createEditor(self, parent, option, index):
if index.parent().isValid():
editor = QProgressBar(parent)
editor.setFixedWidth(200)
editor.setContentsMargins(0, 0, 0, 0)
editor.setValue(index.data(Qt.UserRole))
return editor
super(ProgressDelegate, self).createEditor(parent, option, index)
Nota: Painting a QProgressBar as I did in the first delegate is a trivial task but if you want to place more complex widgets (widgets that have more states like QPushButton) then it is better to use the second option.