<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - qprogressbar</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/qprogressbar.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-03-24T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Change color in QProgressBar delegate — How to dynamically change QProgressBar colors in a QTableView delegate</title><link href="https://www.pythonguis.com/faq/change-color-in-qprogressbar-delegate/" rel="alternate"/><published>2021-03-24T09:00:00+00:00</published><updated>2021-03-24T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-03-24:/faq/change-color-in-qprogressbar-delegate/</id><summary type="html">How can I change the color of a QProgressBar delegate in a QTableView when a process starts or stops? I'm using a delegate to show an indeterminate progress bar (with &lt;code&gt;setRange(0, 0)&lt;/code&gt;), and I want it to be green when running and red when stopped, but replacing the delegate doesn't seem to redraw the color.</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;How can I change the color of a QProgressBar delegate in a QTableView when a process starts or stops? I'm using a delegate to show an indeterminate progress bar (with &lt;code&gt;setRange(0, 0)&lt;/code&gt;), and I want it to be green when running and red when stopped, but replacing the delegate doesn't seem to redraw the color.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The issue here comes from how delegates and persistent editors interact. When you call &lt;code&gt;openPersistentEditor&lt;/code&gt;, Qt creates the editor widget and keeps it alive. Replacing the delegate on a row doesn't automatically close and recreate those existing editors &amp;mdash; so even though you're assigning a new &lt;code&gt;ProgressBarDelegate&lt;/code&gt; with a different color, the old progress bar widget is still sitting there unchanged.&lt;/p&gt;
&lt;p&gt;The solution is to store the color information in the &lt;em&gt;model data&lt;/em&gt; and have a single delegate read that color when it creates or updates the editor. This way, when the data changes, the delegate can update the editor's stylesheet accordingly.&lt;/p&gt;
&lt;p&gt;Let's walk through how to set this up properly.&lt;/p&gt;
&lt;h2 id="store-state-in-the-model-not-the-delegate"&gt;Store state in the model, not the delegate&lt;/h2&gt;
&lt;p&gt;Rather than creating a new delegate every time a button is clicked, store the running/stopped state in the model itself. You can use a custom &lt;code&gt;Qt.ItemDataRole&lt;/code&gt; (or just &lt;code&gt;Qt.UserRole&lt;/code&gt;) to hold the color or state for each row. The delegate then reads this value when painting or configuring the editor.&lt;/p&gt;
&lt;p&gt;This keeps your architecture clean: the model holds the data, and the delegate is responsible only for presentation.&lt;/p&gt;
&lt;h2 id="use-seteditordata-to-update-the-progress-bar"&gt;Use &lt;code&gt;setEditorData&lt;/code&gt; to update the progress bar&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;setEditorData&lt;/code&gt; method on your delegate is called whenever the model data changes for an index that has a persistent editor. This is where you update the progress bar's stylesheet based on the current state.&lt;/p&gt;
&lt;p&gt;Here's how to write a &lt;code&gt;ProgressBarDelegate&lt;/code&gt; that reads a color from the 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;class ProgressBarDelegate(QtWidgets.QStyledItemDelegate):
    def paint(self, painter, option, index):
        if index.column() == 2:
            if (
                isinstance(self.parent(), QtWidgets.QAbstractItemView)
                and self.parent().model() is index.model()
            ):
                self.parent().openPersistentEditor(index)
        QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QProgressBar(parent)
        editor.setRange(0, 0)
        editor.setTextVisible(False)
        return editor

    def setEditorData(self, editor, index):
        color = index.data(QtCore.Qt.ItemDataRole.UserRole)
        if color:
            editor.setStyleSheet(
                f"QProgressBar::chunk {{background-color: {color}; "
                f"width: 20px; margin: 0.5px;}}"
            )

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The delegate no longer stores any color itself. Instead, &lt;code&gt;setEditorData&lt;/code&gt; looks up the color from &lt;code&gt;Qt.UserRole&lt;/code&gt; on the model index and applies it to the stylesheet. Every time you call &lt;code&gt;setData&lt;/code&gt; on the model with &lt;code&gt;Qt.UserRole&lt;/code&gt;, Qt will automatically call &lt;code&gt;setEditorData&lt;/code&gt; on the persistent editor for that index.&lt;/p&gt;
&lt;h2 id="update-the-model-when-the-button-is-clicked"&gt;Update the model when the button is clicked&lt;/h2&gt;
&lt;p&gt;In your button click handler, instead of creating a new delegate, just update the model data:&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_button_clicked(self, index):
    model = self.table_view.model()
    progress_index = model.index(index.row(), 2)

    current_state = progress_index.data(QtCore.Qt.ItemDataRole.DisplayRole)

    if current_state is None or current_state == 0:
        model.setData(progress_index, "green", QtCore.Qt.ItemDataRole.UserRole)
        model.setData(progress_index, 1, QtCore.Qt.ItemDataRole.DisplayRole)
    else:
        model.setData(progress_index, "red", QtCore.Qt.ItemDataRole.UserRole)
        model.setData(progress_index, 0, QtCore.Qt.ItemDataRole.DisplayRole)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;DisplayRole&lt;/code&gt; tracks the state (1 for running, 0 for stopped), and &lt;code&gt;UserRole&lt;/code&gt; holds the color string. When &lt;code&gt;setData&lt;/code&gt; is called, the model emits &lt;code&gt;dataChanged&lt;/code&gt;, which triggers &lt;code&gt;setEditorData&lt;/code&gt; on the persistent editor &amp;mdash; and the progress bar updates its color.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's the full example putting everything together. The &lt;a href="https://www.pythonguis.com/tutorials/pyside6-qtableview-modelviews-numpy-pandas/"&gt;QTableView&lt;/a&gt; shows a list of paths, each with a Start/Stop button and an indeterminate progress bar. Clicking the button toggles the progress bar between green and red using a single &lt;a href="https://www.pythonguis.com/tutorials/pyside6-modelview-architecture/"&gt;delegate&lt;/a&gt; that reads color from the 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;import sys

from PySide6 import QtCore, QtGui, QtWidgets


class ProgressBarDelegate(QtWidgets.QStyledItemDelegate):
    def paint(self, painter, option, index):
        if index.column() == 2:
            if (
                isinstance(self.parent(), QtWidgets.QAbstractItemView)
                and self.parent().model() is index.model()
            ):
                self.parent().openPersistentEditor(index)
        QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)

    def createEditor(self, parent, option, index):
        editor = QtWidgets.QProgressBar(parent)
        editor.setRange(0, 0)
        editor.setTextVisible(False)
        return editor

    def setEditorData(self, editor, index):
        color = index.data(QtCore.Qt.ItemDataRole.UserRole)
        if color:
            editor.setStyleSheet(
                f"QProgressBar::chunk {{background-color: {color}; "
                f"width: 20px; margin: 0.5px;}}"
            )

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)


class PushButtonDelegate(QtWidgets.QStyledItemDelegate):
    clicked = QtCore.Signal(QtCore.QModelIndex)

    def paint(self, painter, option, index):
        if (
            isinstance(self.parent(), QtWidgets.QAbstractItemView)
            and self.parent().model() is index.model()
        ):
            self.parent().openPersistentEditor(index)
        QtWidgets.QStyledItemDelegate.paint(self, painter, option, index)

    def createEditor(self, parent, option, index):
        button = QtWidgets.QPushButton(parent)
        button.clicked.connect(lambda *args, ix=index: self.clicked.emit(ix))
        return button

    def setEditorData(self, editor, index):
        editor.setText("Start/Stop")

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(600, 300)

        self.table_view = QtWidgets.QTableView()
        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(self.table_view)
        cw = QtWidgets.QWidget()
        cw.setLayout(layout)
        self.setCentralWidget(cw)

        self.init_model()

        self.button_delegate.clicked.connect(self.on_button_clicked)

    def init_model(self):
        headers = ["Path", "Control", "Progress"]

        self.button_delegate = PushButtonDelegate(self.table_view)
        self.progress_delegate = ProgressBarDelegate(self.table_view)

        self.model = QtGui.QStandardItemModel()
        self.model.setHorizontalHeaderLabels(headers)

        for row in range(5):
            self.model.setItem(
                row, 0, QtGui.QStandardItem(f"some_path{row}")
            )
            self.model.setItem(row, 1, QtGui.QStandardItem())
            self.model.setItem(row, 2, QtGui.QStandardItem())

        self.table_view.setModel(self.model)
        self.table_view.setItemDelegateForColumn(1, self.button_delegate)
        self.table_view.setItemDelegateForColumn(2, self.progress_delegate)
        self.table_view.resizeColumnsToContents()
        self.table_view.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.ResizeMode.Stretch
        )

    def on_button_clicked(self, index):
        model = self.table_view.model()
        progress_index = model.index(index.row(), 2)

        current_state = progress_index.data(QtCore.Qt.ItemDataRole.DisplayRole)

        if current_state is None or current_state == 0:
            model.setData(
                progress_index, "green", QtCore.Qt.ItemDataRole.UserRole
            )
            model.setData(
                progress_index, 1, QtCore.Qt.ItemDataRole.DisplayRole
            )
        else:
            model.setData(
                progress_index, "red", QtCore.Qt.ItemDataRole.UserRole
            )
            model.setData(
                progress_index, 0, QtCore.Qt.ItemDataRole.DisplayRole
            )


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, each row has a "Start/Stop" button. Clicking it the first time turns the progress bar green (running). Clicking again turns it red (stopped). Each subsequent click toggles between the two states.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="pyside"/><category term="python"/><category term="qtableview"/><category term="delegate"/><category term="qprogressbar"/><category term="qt"/><category term="qt6"/></entry><entry><title>Change the color of the ProgressBar indicator text when it exceeds 50%</title><link href="https://www.pythonguis.com/faq/change-the-color-of-the-progressbar-indicator-text-when-it-exceeds-50/" rel="alternate"/><published>2020-07-21T09:00:00+00:00</published><updated>2020-07-21T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-07-21:/faq/change-the-color-of-the-progressbar-indicator-text-when-it-exceeds-50/</id><summary type="html">How can I change the text color from black to white of the percentage indicator inside the &lt;code&gt;QProgressBar&lt;/code&gt; when it exceeds the value of 50?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;How can I change the text color from black to white of the percentage indicator inside the &lt;code&gt;QProgressBar&lt;/code&gt; when it exceeds the value of 50?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When a &lt;code&gt;QProgressBar&lt;/code&gt; fills past the halfway mark, the dark-colored chunk behind the text can make the default black percentage label hard to read. A common solution is to switch the text color to white once the value crosses 50%. Let's walk through how to do this in PyQt5 using stylesheets and a signal connection.&lt;/p&gt;
&lt;h2 id="why-the-text-becomes-hard-to-read"&gt;Why the text becomes hard to read&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;QProgressBar&lt;/code&gt; draws its percentage text on top of the bar. When the colored "chunk" is small, the text sits against a light background and black text is fine. As the chunk grows and fills most of the bar, the text ends up on top of a dark background &amp;mdash; and black-on-dark is difficult to see.&lt;/p&gt;
&lt;p&gt;The fix is straightforward: watch for value changes and update the stylesheet to swap the text color at the right moment.&lt;/p&gt;
&lt;h2 id="styling-the-qprogressbar-text"&gt;Styling the &lt;code&gt;QProgressBar&lt;/code&gt; text&lt;/h2&gt;
&lt;p&gt;The text color on a &lt;code&gt;QProgressBar&lt;/code&gt; is controlled by the &lt;code&gt;color&lt;/code&gt; property on the &lt;code&gt;QProgressBar&lt;/code&gt; selector itself &amp;mdash; not on &lt;code&gt;QProgressBar::chunk&lt;/code&gt;. The &lt;code&gt;::chunk&lt;/code&gt; sub-control only styles the filled portion of the bar (its background color, border, etc.), while &lt;code&gt;color&lt;/code&gt; on &lt;code&gt;QProgressBar&lt;/code&gt; sets the text color.&lt;/p&gt;
&lt;p&gt;Here's a quick example of setting the text to white:&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;self.progress_bar.setStyleSheet("QProgressBar { color: white; }")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And setting it back to black:&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;self.progress_bar.setStyleSheet("QProgressBar { color: black; }")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="responding-to-value-changes"&gt;Responding to value changes&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QProgressBar&lt;/code&gt; emits a &lt;code&gt;valueChanged&lt;/code&gt; signal every time its value updates. You can connect a slot to this signal that checks the current value and applies the appropriate stylesheet.&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;self.progress_bar.valueChanged.connect(self.update_progress_text_color)
&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 update_progress_text_color(self, value):
    if value &amp;gt; 50:
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                color: white;
            }
        """)
    else:
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                color: black;
            }
        """)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each time the value changes, this method checks whether it's above 50 and sets the text color accordingly.&lt;/p&gt;
&lt;h2 id="preserving-other-styles"&gt;Preserving other styles&lt;/h2&gt;
&lt;p&gt;If you already have a stylesheet on your progress bar (for example, customizing the chunk color or the bar's border), you'll want to include those properties in both branches so they aren't lost when the stylesheet is replaced. Every call to &lt;code&gt;setStyleSheet()&lt;/code&gt; replaces the previous stylesheet entirely.&lt;/p&gt;
&lt;p&gt;For example:&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;BASE_STYLE = """
    QProgressBar {
        border: 1px solid grey;
        border-radius: 5px;
        text-align: center;
    }
    QProgressBar::chunk {
        background-color: #4CAF50;
        border-radius: 5px;
    }
"""

def update_progress_text_color(self, value):
    if value &amp;gt; 50:
        color_style = "QProgressBar { color: white; }"
    else:
        color_style = "QProgressBar { color: black; }"

    self.progress_bar.setStyleSheet(BASE_STYLE + color_style)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Because the &lt;code&gt;color_style&lt;/code&gt; rule comes after &lt;code&gt;BASE_STYLE&lt;/code&gt;, it overrides the text color while keeping everything else intact.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full PyQt5 application you can copy and run. It uses a &lt;code&gt;QTimer&lt;/code&gt; to increment the progress bar automatically so you can see the text color flip from black to white as the bar crosses 50%.&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 PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QProgressBar, QPushButton,
)
from PyQt5.QtCore import QTimer


BASE_STYLE = """
    QProgressBar {
        border: 1px solid grey;
        border-radius: 5px;
        text-align: center;
        font-size: 14px;
    }
    QProgressBar::chunk {
        background-color: #3874f2;
        border-radius: 5px;
    }
"""


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QProgressBar Text Color Demo")
        self.resize(400, 120)

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.progress_bar = QProgressBar()
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(100)
        self.progress_bar.setValue(0)
        self.progress_bar.setStyleSheet(BASE_STYLE + "QProgressBar { color: black; }")
        layout.addWidget(self.progress_bar)

        self.start_button = QPushButton("Start")
        self.start_button.clicked.connect(self.start_progress)
        layout.addWidget(self.start_button)

        self.progress_bar.valueChanged.connect(self.update_progress_text_color)

        self.timer = QTimer()
        self.timer.timeout.connect(self.advance_progress)

    def start_progress(self):
        self.progress_bar.setValue(0)
        self.timer.start(50)

    def advance_progress(self):
        current = self.progress_bar.value()
        if current &amp;gt;= 100:
            self.timer.stop()
            return
        self.progress_bar.setValue(current + 1)

    def update_progress_text_color(self, value):
        if value &amp;gt; 50:
            color_style = "QProgressBar { color: white; }"
        else:
            color_style = "QProgressBar { color: black; }"

        self.progress_bar.setStyleSheet(BASE_STYLE + color_style)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, click &lt;strong&gt;Start&lt;/strong&gt; and watch the percentage label. While the bar is at 50% or below, the text is black. The moment it passes 50%, the text switches to white, keeping it readable against the blue chunk.&lt;/p&gt;
&lt;p&gt;You can adjust the threshold, the colors, or even add a gradual transition by checking at multiple thresholds &amp;mdash; the same pattern applies. Connect to &lt;code&gt;valueChanged&lt;/code&gt;, check the value, and set the stylesheet accordingly.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt5 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt5"/><category term="pyqt"/><category term="qprogressbar"/><category term="stylesheets"/><category term="python"/><category term="qt"/><category term="qt5"/></entry></feed>