2

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)
  • What do you mean by saying "child element to each row", could you place an image of what you want to get since what you say is unclear? – eyllanesc Feb 28 '20 at 07:02
  • I am trying to add a QProgressbar as a childItem for each user. So as a user clicks on a row a child row should appear below it that will contain a progressbar – Pratik Tayshete Feb 28 '20 at 07:05
  • I do not understand what a *child row* is for you so I ask you for an image or scheme of what you want – eyllanesc Feb 28 '20 at 07:09
  • I have updated the question with the structure I am lookig for the QProgressbar as the childItem for each row. Can you please have a look at it – Pratik Tayshete Feb 28 '20 at 07:33
  • Okay, I understand what you want, one last question, what would be the width of the QProgressBar? – eyllanesc Feb 28 '20 at 07:40
  • It can be 100 as I want to display the amount of work done by each user – Pratik Tayshete Feb 28 '20 at 07:51

1 Answers1

2

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_())

enter image description here

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks a lot. Can I also add other widgets using the same process? – Pratik Tayshete Feb 28 '20 at 08:55
  • @PratikTayshete Read my update, test and experiment. You must be more precise in your comments since saying: *Can I also add other widgets* can be interpreted by adding more widgets next to the QProgressbar or using another widget instead of QProgressBar, for example a QPushButton. In the first case you can use my second delegate and place a unique widget as a container and set the widgets through a layout. In the second case, just use my second option. – eyllanesc Feb 28 '20 at 09:12