<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - styling</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/styling.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-03-21T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Custom style for menu items — How to use QWidgetAction to create menu items with custom background colors in PyQt6</title><link href="https://www.pythonguis.com/faq/custom-style-for-menu-items/" rel="alternate"/><published>2021-03-21T09:00:00+00:00</published><updated>2021-03-21T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-03-21:/faq/custom-style-for-menu-items/</id><summary type="html">How can I create a menu item with a custom background color using QWidgetAction, while keeping the same behavior and style as regular QAction items?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;How can I create a menu item with a custom background color using QWidgetAction, while keeping the same behavior and style as regular QAction items?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When building &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-actions-toolbars-menus/"&gt;menus in PyQt6&lt;/a&gt;, you might want to visually distinguish certain menu items &amp;mdash; for example, by giving one a different background color. The standard &lt;code&gt;QAction&lt;/code&gt; class doesn't directly support styling with stylesheets, so the usual approach is to use &lt;code&gt;QWidgetAction&lt;/code&gt;, which lets you embed any widget inside a menu. The challenge is making that custom widget &lt;em&gt;look and behave&lt;/em&gt; like a normal menu item.&lt;/p&gt;
&lt;p&gt;Let's walk through how to do this properly.&lt;/p&gt;
&lt;h2 id="the-problem-with-a-basic-approach"&gt;The problem with a basic approach&lt;/h2&gt;
&lt;p&gt;A first attempt might look like embedding a &lt;code&gt;QCheckBox&lt;/code&gt; inside a &lt;code&gt;QWidgetAction&lt;/code&gt; and applying a stylesheet to it:&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;check_box = QCheckBox("My Item")
check_box.setStyleSheet("background-color: tomato;")

widget_action = QWidgetAction(self)
widget_action.setDefaultWidget(check_box)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This works partially &amp;mdash; you get a checkbox in the menu with a red background. But the styling only covers the checkbox widget itself, not the full width of the menu item. The result looks awkward: the colored area doesn't stretch across the entire row, and the hover/highlight behavior doesn't match the other items.&lt;/p&gt;
&lt;h2 id="using-a-container-widget-for-full-width-styling"&gt;Using a container widget for full-width styling&lt;/h2&gt;
&lt;p&gt;To get the background color to fill the entire menu item row, you need to wrap your widget inside a container (like a &lt;code&gt;QFrame&lt;/code&gt; or &lt;code&gt;QWidget&lt;/code&gt;) and apply the background color to that container. This way the color spans the full width of the menu.&lt;/p&gt;
&lt;p&gt;Here's a complete working example that demonstrates the approach:&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 Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QFrame,
    QHBoxLayout,
    QMainWindow,
    QWidgetAction,
)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("Custom Menu Item")
        self.resize(640, 480)

        # Standard actions
        action1 = QAction("QAction Item 1", self)

        action2 = QAction("QAction Item 2", self)
        action2.setCheckable(True)

        action3 = QAction("QAction Item 3", self)

        # Custom styled menu item using QWidgetAction
        widget_action = QWidgetAction(self)

        # Create a container frame for the full-width background
        container = QFrame()
        container.setStyleSheet("background-color: tomato;")

        layout = QHBoxLayout(container)
        layout.setContentsMargins(4, 2, 4, 2)

        check_box = QCheckBox("QWidgetAction Item")
        check_box.setStyleSheet("background-color: tomato;")
        layout.addWidget(check_box)

        widget_action.setDefaultWidget(container)

        # Build the menu
        menu = self.menuBar().addMenu("Menu")
        menu.addAction(action1)
        menu.addAction(action2)
        menu.addAction(widget_action)
        menu.addAction(action3)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With this setup, the &lt;code&gt;QFrame&lt;/code&gt; container stretches to fill the full width of the menu, and the &lt;code&gt;tomato&lt;/code&gt; background color covers the entire row.&lt;/p&gt;
&lt;h2 id="matching-hover-behavior"&gt;Matching hover behavior&lt;/h2&gt;
&lt;p&gt;One thing you'll notice is that &lt;code&gt;QWidgetAction&lt;/code&gt; items don't automatically get the same hover highlight as regular &lt;code&gt;QAction&lt;/code&gt; items. The mouse hover styling is handled by the menu's style engine for standard actions, but custom widgets are on their own.&lt;/p&gt;
&lt;p&gt;You can restore some of that behavior by using stylesheets on the container that respond to hover:&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;container.setStyleSheet("""
    QFrame {
        background-color: tomato;
    }
    QFrame:hover {
        background-color: #ff7f6e;
    }
""")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This gives the item a slightly lighter shade when the mouse hovers over it, providing visual feedback similar to standard menu items.&lt;/p&gt;
&lt;h2 id="a-more-polished-example"&gt;A more polished example&lt;/h2&gt;
&lt;p&gt;Here's the full example with hover behavior included:&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 Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QFrame,
    QHBoxLayout,
    QMainWindow,
    QWidgetAction,
)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("Custom Menu Item")
        self.resize(640, 480)

        # Center the window on screen
        available_geometry = self.screen().availableGeometry()
        self.move(
            int((available_geometry.width() - self.width()) / 2),
            int((available_geometry.height() - self.height()) / 2),
        )

        # Standard actions
        action1 = QAction("QAction Item 1", self)
        action1.triggered.connect(lambda: print("Item 1 triggered"))

        action2 = QAction("QAction Item 2", self)
        action2.setCheckable(True)
        action2.toggled.connect(lambda checked: print(f"Item 2 toggled: {checked}"))

        action3 = QAction("QAction Item 3", self)
        action3.triggered.connect(lambda: print("Item 3 triggered"))

        # Custom styled menu item
        widget_action = QWidgetAction(self)

        container = QFrame()
        container.setStyleSheet(
            """
            QFrame {
                background-color: tomato;
            }
            QFrame:hover {
                background-color: #ff7f6e;
            }
            """
        )

        layout = QHBoxLayout(container)
        layout.setContentsMargins(4, 2, 4, 2)

        self.custom_check_box = QCheckBox("Custom Styled Item")
        self.custom_check_box.setStyleSheet(
            """
            QCheckBox {
                background-color: transparent;
                padding: 2px;
            }
            """
        )
        self.custom_check_box.toggled.connect(
            lambda checked: print(f"Custom item toggled: {checked}")
        )
        layout.addWidget(self.custom_check_box)

        widget_action.setDefaultWidget(container)

        # Build the menu
        menu = self.menuBar().addMenu("Menu")
        menu.addAction(action1)
        menu.addAction(action2)
        menu.addAction(widget_action)
        menu.addAction(action3)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice that the checkbox itself has &lt;code&gt;background-color: transparent&lt;/code&gt; so it inherits the container's background color rather than painting its own solid block on top.&lt;/p&gt;
&lt;h2 id="a-note-about-menu-closing-behavior"&gt;A note about menu closing behavior&lt;/h2&gt;
&lt;p&gt;One behavioral difference with &lt;code&gt;QWidgetAction&lt;/code&gt; is that clicking the embedded widget does &lt;em&gt;not&lt;/em&gt; automatically close the menu. With a standard &lt;code&gt;QAction&lt;/code&gt;, clicking it triggers the action and closes the menu. With &lt;code&gt;QWidgetAction&lt;/code&gt;, the menu stays open &amp;mdash; which can actually be useful if you want users to toggle multiple checkboxes without having to reopen the menu each time.&lt;/p&gt;
&lt;p&gt;If you do want the menu to close when the checkbox is clicked, you can connect the checkbox signal to the menu's &lt;code&gt;close()&lt;/code&gt; method:&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.custom_check_box.toggled.connect(menu.close)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add this after both the checkbox and the menu have been created. For more on how signals and connections work, see the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;Signals, Slots &amp;amp; Events tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;QWidgetAction&lt;/code&gt; with a container &lt;code&gt;QFrame&lt;/code&gt; gives you full control over the appearance of individual menu items. The container widget stretches to fill the menu width, and you can apply any stylesheet you like &amp;mdash; background colors, fonts, borders, and more. Adding hover styles to the container keeps the visual feedback consistent with standard menu items, and connecting signals from your embedded widgets lets you respond to user interaction just like you would with regular actions.&lt;/p&gt;
&lt;p&gt;If you're looking to further customize your application's appearance, you might also be interested in building &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-own-custom-widgets/"&gt;custom widgets&lt;/a&gt; or arranging your UI with &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-layouts/"&gt;layouts&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.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="menus"/><category term="styling"/><category term="qt"/><category term="qt6"/></entry><entry><title>Transparent TextBrowser — Creating a fading text effect in a QListView using custom model formatting</title><link href="https://www.pythonguis.com/faq/transparent-textbrowser/" rel="alternate"/><published>2021-02-12T09:00:00+00:00</published><updated>2021-02-12T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-02-12:/faq/transparent-textbrowser/</id><summary type="html">I'm building an object detection app and every time something gets detected, I append text to a widget. I want the most recent text to appear prominently, with older entries gradually fading out. How can I achieve this fading text effect in PyQt6?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm building an object detection app and every time something gets detected, I append text to a widget. I want the most recent text to appear prominently, with older entries gradually fading out. How can I achieve this fading text effect in PyQt6?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a great use case for Qt's &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-architecture/"&gt;Model/View architecture&lt;/a&gt;. Instead of trying to manually adjust the formatting of text inside a &lt;code&gt;QTextEdit&lt;/code&gt; or &lt;code&gt;QTextBrowser&lt;/code&gt; every time you add a new line, you can use a &lt;code&gt;QListView&lt;/code&gt; backed by a model. The model holds your data, and a custom delegate controls how each item is drawn &amp;mdash; including its color and opacity. When a new item is added, the view updates automatically, with the most recent entry appearing fully opaque and older entries fading out.&lt;/p&gt;
&lt;p&gt;Let's walk through how to set this up.&lt;/p&gt;
&lt;h2 id="why-qlistview-instead-of-qtextbrowser"&gt;Why QListView instead of QTextBrowser?&lt;/h2&gt;
&lt;p&gt;With a &lt;code&gt;QTextBrowser&lt;/code&gt; or &lt;code&gt;QTextEdit&lt;/code&gt;, you &lt;em&gt;can&lt;/em&gt; change text color using &lt;code&gt;.setTextColor()&lt;/code&gt;, but achieving a gradual fade across all previous lines means re-formatting the entire document every time you add a new entry. That gets awkward quickly.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;QListView&lt;/code&gt; with a &lt;code&gt;QStandardItemModel&lt;/code&gt; is a much better fit. Each detected item becomes a row in the model, and you can apply formatting per-row based on its position. When you add a new entry, all the existing rows shift in visual weight automatically.&lt;/p&gt;
&lt;h2 id="setting-up-the-model"&gt;Setting up the model&lt;/h2&gt;
&lt;p&gt;Start with a basic &lt;code&gt;QStandardItemModel&lt;/code&gt; connected to a &lt;code&gt;QListView&lt;/code&gt;. Each time an object is detected, you append a new &lt;code&gt;QStandardItem&lt;/code&gt; to 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;from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt6.QtWidgets import QListView

model = QStandardItemModel()
list_view = QListView()
list_view.setModel(model)

# Adding an item
item = QStandardItem("Detected: cat")
model.appendRow(item)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That gives you a working list, but all items look the same. To create the fade effect, you need to control how items are drawn depending on how far they are from the bottom of the list.&lt;/p&gt;
&lt;h2 id="applying-the-fade-with-a-custom-delegate"&gt;Applying the fade with a custom delegate&lt;/h2&gt;
&lt;p&gt;Qt's delegate system lets you customize how each item in a view is painted. By subclassing &lt;code&gt;QStyledItemDelegate&lt;/code&gt;, you can adjust the text color's alpha (transparency) based on each item's position relative to the most recent entry.&lt;/p&gt;
&lt;p&gt;The last item in the list (the newest) gets full opacity. Each item above it gets progressively more transparent, fading out over a set number of visible rows.&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.QtWidgets import QStyledItemDelegate
from PyQt6.QtGui import QColor


class FadingDelegate(QStyledItemDelegate):
    def __init__(self, fade_steps=10, parent=None):
        super().__init__(parent)
        self.fade_steps = fade_steps

    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        model = index.model()
        total_rows = model.rowCount()
        row = index.row()

        # Distance from the last (newest) row
        distance = total_rows - 1 - row

        # Calculate alpha: 255 for the newest, fading toward 30 for older items
        min_alpha = 30
        if distance &amp;gt;= self.fade_steps:
            alpha = min_alpha
        else:
            alpha = max(
                min_alpha,
                255 - int((distance / self.fade_steps) * (255 - min_alpha)),
            )

        color = QColor(option.palette.color(option.palette.ColorRole.Text))
        color.setAlpha(alpha)
        option.palette.setColor(option.palette.ColorRole.Text, color)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;fade_steps&lt;/code&gt; parameter controls how many rows participate in the fade. Items older than &lt;code&gt;fade_steps&lt;/code&gt; rows from the bottom settle at a low alpha value (30 in this case), making them very faint without disappearing entirely.&lt;/p&gt;
&lt;h2 id="connecting-it-all-together"&gt;Connecting it all together&lt;/h2&gt;
&lt;p&gt;Set the delegate on your &lt;code&gt;QListView&lt;/code&gt;, and each time you add an item, the entire list re-renders with the correct fading applied.&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;delegate = FadingDelegate(fade_steps=10)
list_view.setItemDelegate(delegate)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Whenever the model changes (a new row is appended), the view calls the delegate's paint logic for all visible items. The newest item appears at full opacity, and everything above it gradually fades.&lt;/p&gt;
&lt;h2 id="scrolling-to-the-latest-item"&gt;Scrolling to the latest item&lt;/h2&gt;
&lt;p&gt;Since new detections go at the bottom, you'll want the view to scroll down automatically:&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;list_view.scrollToBottom()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Call this after each &lt;code&gt;model.appendRow()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full application you can copy and run. It simulates object detections arriving every second using a &lt;code&gt;QTimer&lt;/code&gt;, and displays them in a fading list.&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
import random

from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QColor, QStandardItem, QStandardItemModel
from PyQt6.QtWidgets import (
    QApplication,
    QListView,
    QMainWindow,
    QStyledItemDelegate,
    QVBoxLayout,
    QWidget,
)


class FadingDelegate(QStyledItemDelegate):
    """Custom delegate that fades older items toward transparency."""

    def __init__(self, fade_steps=10, parent=None):
        super().__init__(parent)
        self.fade_steps = fade_steps

    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        model = index.model()
        total_rows = model.rowCount()
        row = index.row()

        # How far this row is from the newest (bottom) row
        distance = total_rows - 1 - row

        # Full opacity for the newest, fading older items down to a minimum
        min_alpha = 30
        if distance &amp;gt;= self.fade_steps:
            alpha = min_alpha
        else:
            alpha = max(
                min_alpha,
                255 - int((distance / self.fade_steps) * (255 - min_alpha)),
            )

        color = QColor(option.palette.color(option.palette.ColorRole.Text))
        color.setAlpha(alpha)
        option.palette.setColor(option.palette.ColorRole.Text, color)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Fading Detection Log")
        self.resize(400, 500)

        # Set up the model and view
        self.model = QStandardItemModel()
        self.list_view = QListView()
        self.list_view.setModel(self.model)

        # Apply the fading delegate
        self.delegate = FadingDelegate(fade_steps=10)
        self.list_view.setItemDelegate(self.delegate)

        # Disable editing so it behaves like a log
        self.list_view.setEditTriggers(
            QListView.EditTrigger.NoEditTriggers
        )

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.list_view)
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Simulate detections arriving every second
        self.objects = [
            "cat", "dog", "person", "car", "bicycle",
            "bird", "chair", "bottle", "phone", "laptop",
        ]
        self.timer = QTimer()
        self.timer.timeout.connect(self.add_detection)
        self.timer.start(1000)

    def add_detection(self):
        detected = random.choice(self.objects)
        item = QStandardItem(f"Detected: {detected}")
        item.setFlags(item.flags() &amp;amp; ~Qt.ItemFlag.ItemIsEditable)
        self.model.appendRow(item)
        self.list_view.scrollToBottom()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a live log where each new detection appears at full intensity, and older entries smoothly fade out above it. The &lt;code&gt;fade_steps=10&lt;/code&gt; means the last 10 entries participate in the gradient &amp;mdash; adjust this number to control how quickly things fade.&lt;/p&gt;
&lt;h2 id="customizing-the-effect"&gt;Customizing the effect&lt;/h2&gt;
&lt;p&gt;There are several ways to adjust the look:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Change &lt;code&gt;fade_steps&lt;/code&gt;&lt;/strong&gt;: A smaller number (like 5) creates a sharper cutoff. A larger number (like 20) gives a more gradual fade.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change &lt;code&gt;min_alpha&lt;/code&gt;&lt;/strong&gt;: Setting this to 0 would make old items fully invisible. Setting it to 100 keeps them somewhat readable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add bold to the newest item&lt;/strong&gt;: In &lt;code&gt;initStyleOption&lt;/code&gt;, you could check if &lt;code&gt;distance == 0&lt;/code&gt; and set the font to bold for the latest entry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use color changes&lt;/strong&gt;: Instead of (or in addition to) transparency, you could shift the hue &amp;mdash; for example, newest items in red, fading to gray.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a quick example of making the newest entry bold:&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 initStyleOption(self, option, index):
    super().initStyleOption(option, index)
    model = index.model()
    total_rows = model.rowCount()
    row = index.row()
    distance = total_rows - 1 - row

    # Bold the newest item
    if distance == 0:
        font = option.font
        font.setBold(True)
        option.font = font

    # ... rest of alpha calculation
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Using a &lt;code&gt;QListView&lt;/code&gt; with a custom delegate gives you a clean, maintainable way to present detection results with visual emphasis on what's new. The model/view pattern keeps your data and presentation separate, so you can change the visual style without touching your detection logic at all. If you're looking to build your own reusable widgets with custom painting, take a look at &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-own-custom-widgets/"&gt;creating your own custom widgets in PyQt6&lt;/a&gt;. For more on how signals like &lt;code&gt;QTimer.timeout&lt;/code&gt; connect to slots, see the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots tutorial&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="qlistview"/><category term="model-view"/><category term="styling"/><category term="transparency"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Styling PyQt/PySide Apps Installed via Pip on Linux — How to handle Qt styling and themes when distributing your Python GUI app through pip</title><link href="https://www.pythonguis.com/faq/installation-via-pip-styling/" rel="alternate"/><published>2020-10-19T09:00:00+00:00</published><updated>2020-10-19T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-10-19:/faq/installation-via-pip-styling/</id><summary type="html">If you've built a PyQt or PySide application that looks great when you run it from your development environment, you might be surprised when you install it via pip and find it looking... not so great. On Linux especially, your app can end up with a flat, unstyled appearance &amp;mdash; missing the native desktop look you expected.</summary><content type="html">
            &lt;p&gt;If you've built a PyQt or PySide application that looks great when you run it from your development environment, you might be surprised when you install it via pip and find it looking... not so great. On Linux especially, your app can end up with a flat, unstyled appearance &amp;mdash; missing the native desktop look you expected.&lt;/p&gt;
&lt;p&gt;This is a common issue, and it has a straightforward explanation. In this article, we'll look at why this happens and walk through several practical ways to fix it.&lt;/p&gt;
&lt;h2 id="why-does-styling-break-on-linux"&gt;Why Does Styling Break on Linux?&lt;/h2&gt;
&lt;p&gt;When you run a Qt application on Linux, Qt tries to match the look and feel of your desktop environment (GNOME, KDE, etc.) by using a &lt;em&gt;platform theme plugin&lt;/em&gt;. These plugins read your system's theme settings and apply them to your app's widgets &amp;mdash; giving you native-looking buttons, scrollbars, and menus.&lt;/p&gt;
&lt;p&gt;The problem is that these platform theme plugins are separate from Qt itself. They depend on system-level Qt libraries and desktop integration packages. When your app is installed via pip into a &lt;a href="https://www.pythonguis.com/tutorials/python-virtual-environments/"&gt;virtual environment&lt;/a&gt; (or a user install), it uses the PyQt5/PyQt6/PySide6 wheels from PyPI. These wheels bundle their own copy of the Qt libraries, but they &lt;em&gt;don't&lt;/em&gt; include the platform theme plugins that integrate with your desktop environment.&lt;/p&gt;
&lt;p&gt;The result? Qt falls back to a basic, unstyled look &amp;mdash; sometimes called the "Windows 95" style.&lt;/p&gt;
&lt;h2 id="checking-your-current-style"&gt;Checking Your Current Style&lt;/h2&gt;
&lt;p&gt;Before applying fixes, it helps to see what's going on. You can check which style your app is using and what styles are available with a small script:&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

app = QApplication(sys.argv)

print("Available styles:", app.style().objectName())
print("All styles:", [s for s in QApplication.style().objectName()])

# List all available style keys
from PyQt6.QtWidgets import QStyleFactory
print("Style factory keys:", QStyleFactory.keys())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On a Linux system using pip-installed PyQt6, you'll typically see only a handful of styles like &lt;code&gt;Fusion&lt;/code&gt; and &lt;code&gt;Windows&lt;/code&gt;, but not platform-native ones like &lt;code&gt;gtk2&lt;/code&gt; or &lt;code&gt;Breeze&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="solution-1-use-the-fusion-style"&gt;Solution 1: Use the Fusion Style&lt;/h2&gt;
&lt;p&gt;The simplest and most portable solution is to explicitly set your app to use Qt's built-in &lt;strong&gt;Fusion&lt;/strong&gt; style. Fusion is a modern, clean style that ships with Qt itself &amp;mdash; so it's always available, regardless of how your app was installed.&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, QPushButton,
    QVBoxLayout, QWidget, QCheckBox, QSlider,
    QComboBox, QLabel
)
from PyQt6.QtCore import Qt


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

        layout = QVBoxLayout()
        layout.addWidget(QLabel("This app uses the Fusion style"))
        layout.addWidget(QPushButton("Click Me"))
        layout.addWidget(QCheckBox("Enable feature"))
        layout.addWidget(QSlider(Qt.Orientation.Horizontal))

        combo = QComboBox()
        combo.addItems(["Option A", "Option B", "Option C"])
        layout.addWidget(combo)

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


app = QApplication(sys.argv)
app.setStyle("Fusion")

window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By calling &lt;code&gt;app.setStyle("Fusion")&lt;/code&gt; before showing any windows, you guarantee a consistent, modern appearance on every platform &amp;mdash; Linux, macOS, and Windows. If you're new to building Qt applications, see our guide to &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window with PyQt6&lt;/a&gt; for the fundamentals.&lt;/p&gt;
&lt;h2 id="solution-2-apply-a-custom-qt-stylesheet-qss"&gt;Solution 2: Apply a Custom Qt Stylesheet (QSS)&lt;/h2&gt;
&lt;p&gt;If you want more control over the look of your app, you can use &lt;strong&gt;Qt Style Sheets&lt;/strong&gt; (QSS). These work similarly to CSS in web development &amp;mdash; you define rules that target widgets and set visual properties like colors, borders, padding, and fonts.&lt;/p&gt;
&lt;p&gt;This approach is completely independent of the system theme, so it works reliably everywhere.&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, QPushButton,
    QVBoxLayout, QWidget, QLabel
)


STYLESHEET = """
    QMainWindow {
        background-color: #2b2b2b;
    }
    QLabel {
        color: #ffffff;
        font-size: 14px;
        padding: 4px;
    }
    QPushButton {
        background-color: #4a86c8;
        color: white;
        border: none;
        border-radius: 4px;
        padding: 8px 16px;
        font-size: 13px;
    }
    QPushButton:hover {
        background-color: #5a96d8;
    }
    QPushButton:pressed {
        background-color: #3a76b8;
    }
"""


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Styled App")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Custom styled application"))
        layout.addWidget(QPushButton("Primary Action"))
        layout.addWidget(QPushButton("Secondary Action"))

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


app = QApplication(sys.argv)
app.setStyle("Fusion")  # Use Fusion as the base style
app.setStyleSheet(STYLESHEET)

window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Setting &lt;code&gt;Fusion&lt;/code&gt; as the base style before applying your stylesheet gives you a clean, neutral starting point that your QSS rules can build on top of.&lt;/p&gt;
&lt;h2 id="solution-3-bundle-a-qss-file-with-your-package"&gt;Solution 3: Bundle a QSS File with Your Package&lt;/h2&gt;
&lt;p&gt;For larger projects, keeping your stylesheet in a separate &lt;code&gt;.qss&lt;/code&gt; file makes it easier to maintain. You can bundle this file inside your Python package and load it at runtime.&lt;/p&gt;
&lt;p&gt;Here's a typical project layout:&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;myapp/
&amp;boxvr;&amp;boxh;&amp;boxh; __init__.py
&amp;boxvr;&amp;boxh;&amp;boxh; __main__.py
&amp;boxvr;&amp;boxh;&amp;boxh; main.py
&amp;boxur;&amp;boxh;&amp;boxh; styles/
    &amp;boxur;&amp;boxh;&amp;boxh; app.qss
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Your &lt;code&gt;app.qss&lt;/code&gt; file contains the stylesheet rules:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-css"&gt;css&lt;/span&gt;
&lt;pre&gt;&lt;code class="css"&gt;QPushButton {
    background-color: #4a86c8;
    color: white;
    border: none;
    border-radius: 4px;
    padding: 8px 16px;
}

QPushButton:hover {
    background-color: #5a96d8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then in your application code, load it using &lt;code&gt;importlib.resources&lt;/code&gt; (Python 3.9+) or &lt;code&gt;pathlib&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 pathlib import Path
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget


def load_stylesheet():
    """Load the QSS stylesheet from the package's styles directory."""
    style_path = Path(__file__).parent / "styles" / "app.qss"
    if style_path.exists():
        return style_path.read_text()
    return ""


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Bundled Style App")

        layout = QVBoxLayout()
        layout.addWidget(QPushButton("Styled Button"))
        layout.addWidget(QPushButton("Another Button"))

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


def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(load_stylesheet())

    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To make sure your &lt;code&gt;.qss&lt;/code&gt; file is included when you build your pip package, add it to your &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-toml"&gt;toml&lt;/span&gt;
&lt;pre&gt;&lt;code class="toml"&gt;[tool.setuptools.package-data]
myapp = ["styles/*.qss"]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or if you're using a &lt;code&gt;MANIFEST.in&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;recursive-include myapp/styles *.qss
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This way the stylesheet travels with your package no matter how it's installed. For an alternative approach to bundling resources, you can also use the &lt;a href="https://www.pythonguis.com/tutorials/pyside6-qresource-system/"&gt;Qt Resource System&lt;/a&gt; to embed stylesheets and other assets directly into your application.&lt;/p&gt;
&lt;h2 id="solution-4-set-the-platform-theme-via-environment-variable"&gt;Solution 4: Set the Platform Theme via Environment Variable&lt;/h2&gt;
&lt;p&gt;If you want your pip-installed app to use the native system theme on Linux, you can try setting the &lt;code&gt;QT_QPA_PLATFORMTHEME&lt;/code&gt; environment variable. This tells Qt which platform theme plugin to load.&lt;/p&gt;
&lt;p&gt;For GNOME-based desktops:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;export QT_QPA_PLATFORMTHEME=gtk3
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For KDE:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;export QT_QPA_PLATFORMTHEME=kde
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also set this from within your application, &lt;strong&gt;before&lt;/strong&gt; creating the &lt;code&gt;QApplication&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
import os

os.environ["QT_QPA_PLATFORMTHEME"] = "gtk3"

from PyQt6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)
# ... rest of your app
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;However, this approach has a significant caveat: it only works if the corresponding platform theme plugin is installed on the user's system. For the &lt;code&gt;gtk3&lt;/code&gt; theme, the user would need the &lt;code&gt;qt6-gtk-platformtheme&lt;/code&gt; package (or equivalent for their distribution) installed. Since you can't guarantee this for every user, this is less reliable than the Fusion or QSS approaches for apps distributed via pip.&lt;/p&gt;
&lt;h2 id="solution-5-use-qpalette-with-fusion-for-a-custom-color-scheme"&gt;Solution 5: Use QPalette with Fusion for a Custom Color Scheme&lt;/h2&gt;
&lt;p&gt;A middle ground between the plain Fusion style and a full stylesheet is to customize Fusion's &lt;strong&gt;color palette&lt;/strong&gt;. This gives you a polished, consistent look while keeping the native widget behavior (hover effects, focus indicators, etc.) intact.&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, QPushButton,
    QVBoxLayout, QWidget, QCheckBox, QLabel,
    QSlider, QComboBox, QLineEdit
)
from PyQt6.QtGui import QPalette, QColor
from PyQt6.QtCore import Qt


def create_dark_palette():
    """Create a dark color palette for the Fusion style."""
    palette = QPalette()

    # Base colors
    palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.WindowText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Base, QColor(35, 35, 35))
    palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(25, 25, 25))
    palette.setColor(QPalette.ColorRole.ToolTipText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Text, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.ButtonText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
    palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
    palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
    palette.setColor(QPalette.ColorRole.HighlightedText, QColor(35, 35, 35))

    # Disabled state
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.WindowText,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.Text,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.ButtonText,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.HighlightedText,
        QColor(127, 127, 127),
    )

    return palette


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dark Fusion Theme")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Dark Fusion palette example"))
        layout.addWidget(QLineEdit("Editable text"))
        layout.addWidget(QPushButton("Enabled Button"))

        disabled_btn = QPushButton("Disabled Button")
        disabled_btn.setEnabled(False)
        layout.addWidget(disabled_btn)

        layout.addWidget(QCheckBox("A checkbox option"))
        layout.addWidget(QSlider(Qt.Orientation.Horizontal))

        combo = QComboBox()
        combo.addItems(["First", "Second", "Third"])
        layout.addWidget(combo)

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


app = QApplication(sys.argv)
app.setStyle("Fusion")
app.setPalette(create_dark_palette())

window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;QPalette&lt;/code&gt; approach works well because Fusion respects the palette colors throughout all its widget rendering. You get a cohesive dark (or custom-colored) theme with proper disabled states, hover effects, and focus indicators &amp;mdash; all without writing a single line of QSS.&lt;/p&gt;
&lt;h2 id="which-approach-should-you-use"&gt;Which Approach Should You Use?&lt;/h2&gt;
&lt;p&gt;Here's a quick summary to help you decide:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fusion style&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Always available, zero config&lt;/td&gt;
&lt;td&gt;Doesn't match native desktop theme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QSS stylesheet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full visual control, portable&lt;/td&gt;
&lt;td&gt;Can be verbose for complex UIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bundled QSS file&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maintainable, separates style from logic&lt;/td&gt;
&lt;td&gt;Requires packaging setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Environment variable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can get native theme&lt;/td&gt;
&lt;td&gt;Depends on user's system packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QPalette + Fusion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clean, native-feeling widget behavior&lt;/td&gt;
&lt;td&gt;Limited to color changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For most pip-distributed applications, &lt;strong&gt;Fusion style combined with either a QPalette or a QSS stylesheet&lt;/strong&gt; is the most reliable approach. It gives you a consistent, professional appearance across all Linux distributions, as well as on macOS and Windows, without depending on anything outside your package.&lt;/p&gt;
&lt;p&gt;The reality of distributing Python GUI apps via pip on Linux is that you can't rely on the system's Qt theme integration. By taking control of your styling explicitly, you ensure your users get a polished experience regardless of their desktop environment or how they installed your app. If you're ready to package your application for distribution, see our guide on &lt;a href="https://www.pythonguis.com/tutorials/packaging-pyqt5-applications-linux-pyinstaller/"&gt;packaging PyQt6 apps for Linux with PyInstaller&lt;/a&gt; for the next steps.&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.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="pyside"/><category term="pip"/><category term="linux"/><category term="styling"/><category term="distribution"/><category term="python"/><category term="qt"/></entry></feed>