<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/all.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2026-08-05T06:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Handling Image Drag and Drop from Web Browsers in PyQt6 — Why toLocalFile() returns an empty string and how to handle remote image drops correctly</title><link href="https://www.pythonguis.com/faq/dragging-and-dropping-in-rich-text-example-app/" rel="alternate"/><published>2026-08-05T06:00:00+00:00</published><updated>2026-08-05T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-08-05:/faq/dragging-and-dropping-in-rich-text-example-app/</id><summary type="html">When dragging and dropping images from a web browser into a PyQt6 rich text editor, &lt;code&gt;toLocalFile()&lt;/code&gt; sometimes returns a blank string. It works for some images (like Google image search results) but fails for others (like images embedded directly on a webpage). Why does this happen, and how can I handle it?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;When dragging and dropping images from a web browser into a PyQt6 rich text editor, &lt;code&gt;toLocalFile()&lt;/code&gt; sometimes returns a blank string. It works for some images (like Google image search results) but fails for others (like images embedded directly on a webpage). Why does this happen, and how can I handle it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've added drag and drop support to your application, you may have noticed something frustrating: dragging an image from a browser sometimes works perfectly, and other times gives you nothing at all. The &lt;code&gt;toLocalFile()&lt;/code&gt; method returns an empty string, and your image never appears.&lt;/p&gt;
&lt;p&gt;This comes down to how browsers package image data when you start a drag operation, and what your application expects to receive. Let's walk through what's happening and how to fix it.&lt;/p&gt;
&lt;h2 id="how-drag-and-drop-mime-data-works"&gt;How drag and drop MIME data works&lt;/h2&gt;
&lt;p&gt;When you drag something &amp;mdash; a file, an image, some text &amp;mdash; the source application bundles that data into a &lt;code&gt;QMimeData&lt;/code&gt; object. This object can contain several different formats at once. For example, dragging an image might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A file URL (&lt;code&gt;text/uri-list&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Raw image data (&lt;code&gt;image/png&lt;/code&gt; or &lt;code&gt;image/jpeg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An HTML &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag (&lt;code&gt;text/html&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A plain text URL (&lt;code&gt;text/plain&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which of these formats are included depends entirely on the source application. Different browsers, and even different types of images within the same browser, behave differently.&lt;/p&gt;
&lt;h2 id="why-tolocalfile-returns-an-empty-string"&gt;Why toLocalFile() returns an empty string&lt;/h2&gt;
&lt;p&gt;The method &lt;code&gt;QUrl.toLocalFile()&lt;/code&gt; converts a URL into a local filesystem path. It only works when the URL uses the &lt;code&gt;file://&lt;/code&gt; scheme &amp;mdash; meaning the file actually exists on your computer.&lt;/p&gt;
&lt;p&gt;When you drag an image from a Google image search, the browser often creates a temporary local file and provides a &lt;code&gt;file://&lt;/code&gt; URL. That's why &lt;code&gt;toLocalFile()&lt;/code&gt; works in that case.&lt;/p&gt;
&lt;p&gt;But when you drag an image that's embedded directly in a webpage (like a screenshot in a blog post), the browser typically provides a remote &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt; URL instead. There's no local file, so &lt;code&gt;toLocalFile()&lt;/code&gt; returns an empty string. Some browsers may also provide the image as inline data or an HTML fragment with no URL at all.&lt;/p&gt;
&lt;h2 id="inspecting-what-the-browser-actually-sends"&gt;Inspecting what the browser actually sends&lt;/h2&gt;
&lt;p&gt;A good first step is to look at exactly what MIME data arrives when you drop something. This small example creates a drop target that prints out all available MIME formats and their contents:&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.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget


class DropInspector(QLabel):
    def __init__(self):
        super().__init__("Drop something here")
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setMinimumSize(400, 300)
        self.setStyleSheet(
            "background-color: #f0f0f0; border: 2px dashed #aaa; font-size: 16px;"
        )
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        event.acceptProposedAction()

    def dropEvent(self, event):
        mime_data = event.mimeData()
        print("=== Drop received ===")
        for fmt in mime_data.formats():
            data = mime_data.data(fmt)
            print(f"\nFormat: {fmt}")
            # Show first 200 bytes as text for readability.
            try:
                print(f"  Data: {bytes(data[:200]).decode('utf-8', errors='replace')}")
            except Exception:
                print(f"  Data: ({len(data)} bytes, binary)")

        if mime_data.hasUrls():
            for url in mime_data.urls():
                print(f"\nURL: {url.toString()}")
                print(f"  toLocalFile: '{url.toLocalFile()}'")
                print(f"  scheme: '{url.scheme()}'")

        event.acceptProposedAction()
        self.setText("Check console output!")


app = QApplication(sys.argv)
window = DropInspector()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Try dragging different images from your browser into this window. You'll see that some drops include &lt;code&gt;file://&lt;/code&gt; URLs while others include &lt;code&gt;https://&lt;/code&gt; URLs or even raw image data with no URL at all.&lt;/p&gt;
&lt;h2 id="handling-all-cases-in-your-drop-event"&gt;Handling all cases in your drop event&lt;/h2&gt;
&lt;p&gt;To make your application work reliably with images dragged from any source, you need to handle multiple scenarios:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Local file URL&lt;/strong&gt; &amp;mdash; use the file path directly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote URL&lt;/strong&gt; &amp;mdash; download the image&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raw image data&lt;/strong&gt; &amp;mdash; use it directly from the MIME data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTML with an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag&lt;/strong&gt; &amp;mdash; extract the image URL from the HTML&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's how to implement this step by step.&lt;/p&gt;
&lt;h3&gt;Checking for local files first&lt;/h3&gt;
&lt;p&gt;This is the simplest case and the one you likely already have working:&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 dropEvent(self, event):
    mime_data = event.mimeData()

    if mime_data.hasUrls():
        for url in mime_data.urls():
            local_path = url.toLocalFile()
            if local_path:
                # It's a local file &amp;mdash; use it directly.
                self.insert_image_from_path(local_path)
                event.acceptProposedAction()
                return
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Handling remote URLs&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;toLocalFile()&lt;/code&gt; returns an empty string but you still have a URL, it's likely a remote image. You can download it using Python's &lt;code&gt;urllib&lt;/code&gt; (or &lt;code&gt;requests&lt;/code&gt; if you prefer):&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 os
import tempfile
import urllib.request


def download_image(url_string):
    """Download an image from a URL and return the local file path."""
    try:
        # Create a temporary file to store the downloaded image.
        suffix = os.path.splitext(url_string)[-1].split("?")[0]
        if suffix not in (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"):
            suffix = ".png"
        tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
        urllib.request.urlretrieve(url_string, tmp_file.name)
        return tmp_file.name
    except Exception as e:
        print(f"Failed to download image: {e}")
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then extend your drop handler:&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;if mime_data.hasUrls():
    for url in mime_data.urls():
        local_path = url.toLocalFile()
        if local_path:
            self.insert_image_from_path(local_path)
            event.acceptProposedAction()
            return

        # No local file &amp;mdash; try downloading the remote URL.
        url_string = url.toString()
        if url_string:
            local_path = download_image(url_string)
            if local_path:
                self.insert_image_from_path(local_path)
                event.acceptProposedAction()
                return
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Handling raw image data&lt;/h3&gt;
&lt;p&gt;Sometimes the browser sends the image data directly, without any URL. You can check for this using &lt;code&gt;hasImage()&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;if mime_data.hasImage():
    image = mime_data.imageData()
    if image and not image.isNull():
        # Save the image to a temp file and insert it.
        tmp_path = tempfile.NamedTemporaryFile(
            delete=False, suffix=".png"
        ).name
        image.save(tmp_path)
        self.insert_image_from_path(tmp_path)
        event.acceptProposedAction()
        return
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Extracting URLs from HTML&lt;/h3&gt;
&lt;p&gt;As a fallback, some drops include an HTML fragment with an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag. You can parse out the &lt;code&gt;src&lt;/code&gt; attribute:&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 re


def extract_image_url_from_html(html):
    """Extract the first image URL from an HTML string."""
    match = re.search(r'&amp;lt;img[^&amp;gt;]+src=["\']([^"\']+)["\']', html)
    if match:
        return match.group(1)
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then add this as a final fallback:&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;if mime_data.hasHtml():
    html = mime_data.html()
    image_url = extract_image_url_from_html(html)
    if image_url:
        local_path = download_image(image_url)
        if local_path:
            self.insert_image_from_path(local_path)
            event.acceptProposedAction()
            return
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full, working rich text editor with robust image drag and drop support. You can copy this and run it directly:&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 os
import re
import sys
import tempfile
import urllib.request

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QImage, QTextCursor
from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget


def download_image(url_string):
    """Download an image from a URL and return the local file path."""
    try:
        suffix = os.path.splitext(url_string.split("?")[0])[-1]
        if suffix.lower() not in (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"):
            suffix = ".png"
        tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
        urllib.request.urlretrieve(url_string, tmp_file.name)
        return tmp_file.name
    except Exception as e:
        print(f"Failed to download image: {e}")
        return None


def extract_image_url_from_html(html):
    """Extract the first image URL from an HTML string."""
    match = re.search(r'&amp;lt;img[^&amp;gt;]+src=["\']([^"\']+)["\']', html)
    if match:
        return match.group(1)
    return None


class ImageDropTextEdit(QTextEdit):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

    def canInsertFromMimeData(self, source):
        if source.hasImage() or source.hasUrls() or source.hasHtml():
            return True
        return super().canInsertFromMimeData(source)

    def insertFromMimeData(self, source):
        """Handle paste and drop events with image support."""
        # Try each method in order of reliability.

        # 1. Check for direct image data.
        if source.hasImage():
            image = source.imageData()
            if isinstance(image, QImage) and not image.isNull():
                self.insert_image(image)
                return

        # 2. Check for URLs (local or remote).
        if source.hasUrls():
            for url in source.urls():
                local_path = url.toLocalFile()
                if local_path and self.is_image_file(local_path):
                    self.insert_image_from_path(local_path)
                    return

                # Try downloading remote URL.
                url_string = url.toString()
                if url_string and self.looks_like_image_url(url_string):
                    local_path = download_image(url_string)
                    if local_path:
                        self.insert_image_from_path(local_path)
                        return

        # 3. Check for HTML with embedded image tags.
        if source.hasHtml():
            image_url = extract_image_url_from_html(source.html())
            if image_url:
                if image_url.startswith("data:"):
                    # Data URI &amp;mdash; decode and insert.
                    image = self.image_from_data_uri(image_url)
                    if image and not image.isNull():
                        self.insert_image(image)
                        return
                else:
                    local_path = download_image(image_url)
                    if local_path:
                        self.insert_image_from_path(local_path)
                        return

        # Fall back to default behavior for plain text, etc.
        super().insertFromMimeData(source)

    def insert_image_from_path(self, file_path):
        """Insert an image from a local file path into the editor."""
        image = QImage(file_path)
        if image.isNull():
            print(f"Could not load image: {file_path}")
            return
        self.insert_image(image)

    def insert_image(self, image):
        """Insert a QImage into the editor at the current cursor position."""
        cursor = self.textCursor()
        document = self.document()

        # Add the image as a resource in the document.
        image_name = f"dropped_image_{id(image)}"
        document.addResource(
            document.ResourceType.ImageResource.value,
            self.create_url(image_name),
            image,
        )

        # Insert the image at the cursor.
        image_format = cursor.charFormat()
        from PyQt6.QtGui import QTextImageFormat

        img_fmt = QTextImageFormat()
        img_fmt.setName(image_name)
        img_fmt.setWidth(min(image.width(), 600))
        img_fmt.setHeight(
            int(image.height() * min(image.width(), 600) / max(image.width(), 1))
        )
        cursor.insertImage(img_fmt)

    @staticmethod
    def create_url(name):
        from PyQt6.QtCore import QUrl

        return QUrl(name)

    @staticmethod
    def is_image_file(path):
        extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg"}
        return os.path.splitext(path.lower())[-1] in extensions

    @staticmethod
    def looks_like_image_url(url_string):
        """Check if a URL looks like it points to an image."""
        clean_url = url_string.split("?")[0].lower()
        extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg"}
        return any(clean_url.endswith(ext) for ext in extensions)

    @staticmethod
    def image_from_data_uri(data_uri):
        """Decode a data: URI and return a QImage."""
        import base64

        try:
            # data:image/png;base64,iVBOR...
            header, data = data_uri.split(",", 1)
            image_data = base64.b64decode(data)
            image = QImage()
            image.loadFromData(image_data)
            return image
        except Exception as e:
            print(f"Failed to decode data URI: {e}")
            return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Rich Text Editor &amp;mdash; Image Drop Demo")
        self.setMinimumSize(700, 500)

        self.editor = ImageDropTextEdit()
        self.editor.setPlaceholderText(
            "Try dragging an image from your web browser into this editor..."
        )

        layout = QVBoxLayout()
        layout.addWidget(self.editor)

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


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="whats-happening-in-the-complete-example"&gt;What's happening in the complete example&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ImageDropTextEdit&lt;/code&gt; class overrides &lt;code&gt;insertFromMimeData&lt;/code&gt;, which Qt calls for both paste (Ctrl+V) and drag-and-drop operations. This gives you a single place to handle all image insertion.&lt;/p&gt;
&lt;p&gt;The method tries each data source in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Direct image data&lt;/strong&gt; &amp;mdash; the fastest and most reliable, when available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URLs&lt;/strong&gt; &amp;mdash; first checking for local files, then attempting to download remote URLs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTML fragments&lt;/strong&gt; &amp;mdash; parsing out &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags and fetching the referenced image, including support for &lt;code&gt;data:&lt;/code&gt; URIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fallback&lt;/strong&gt; &amp;mdash; if none of the above match, it passes control to the default &lt;code&gt;QTextEdit&lt;/code&gt; behavior, so normal text paste and drop still work.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By overriding &lt;code&gt;canInsertFromMimeData&lt;/code&gt; as well, we tell Qt's drag and drop system that our editor accepts these additional formats, which ensures the correct cursor icon appears when hovering over the editor.&lt;/p&gt;
&lt;p&gt;This approach handles the differences between browsers &amp;mdash; Chrome, Firefox, Edge &amp;mdash; and between different types of images on the web, making your rich text editor's drag and drop support much more resilient.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="drag-and-drop"/><category term="rich-text"/><category term="python"/><category term="mime-data"/><category term="qt"/><category term="qt6"/></entry><entry><title>Constantly Print Subprocess Output While Process is Running — How to stream live output from a subprocess into your PyQt6 GUI without freezing the interface</title><link href="https://www.pythonguis.com/faq/constantly-print-subprocess-output-while-process-is-running/" rel="alternate"/><published>2026-07-22T06:00:00+00:00</published><updated>2026-07-22T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-07-22:/faq/constantly-print-subprocess-output-while-process-is-running/</id><summary type="html">I need to call a legacy Bash program and display the results in a Qt window. The problem is the subprocess doesn't return each output line as it happens &amp;mdash; it waits until the entire command is finished, then dumps everything to the window at once. If the command takes a long time, the user thinks the system is frozen. How can I get live, line-by-line output from a subprocess into my Qt application?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I need to call a legacy Bash program and display the results in a Qt window. The problem is the subprocess doesn't return each output line as it happens &amp;mdash; it waits until the entire command is finished, then dumps everything to the window at once. If the command takes a long time, the user thinks the system is frozen. How can I get live, line-by-line output from a subprocess into my Qt application?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've ever launched a long-running external command from a PyQt6 application and watched your entire GUI freeze until it finishes, you've hit one of the most common pitfalls in Python GUI development: &lt;strong&gt;blocking the event loop&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When you call &lt;code&gt;subprocess.run()&lt;/code&gt;, Python stops and waits for the process to complete before moving on. While it's waiting, Qt's event loop &amp;mdash; the mechanism responsible for redrawing the window, responding to clicks, and processing signals &amp;mdash; is completely stalled. That means that the UI will not update.&lt;/p&gt;
&lt;p&gt;There are two approaches to stream subprocess output in real time in PyQt6:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code&gt;QProcess&lt;/code&gt;&lt;/strong&gt;, which is Qt's built-in way to run external programs. It integrates directly with the event loop and emits signals as output becomes available.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a background &lt;code&gt;QThread&lt;/code&gt;&lt;/strong&gt; with Python's &lt;code&gt;subprocess.Popen&lt;/code&gt; to read output line by line and send it back to the GUI via signals.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-wrong-approach"&gt;The wrong approach&lt;/h2&gt;
&lt;p&gt;First, let's see what happens when you use &lt;code&gt;subprocess&lt;/code&gt; and block the event loop.&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 subprocess
import sys

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Subprocess Demo - Blocking")

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

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

    def run_command(self):
        # This blocks the entire GUI until the command finishes!
        result = subprocess.run(
            ["bash", "-c", "for i in 1 2 3 4 5; do echo Line $i; sleep 1; done"],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        self.text_area.setPlainText(result.stdout.decode())


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Click the button, and the window becomes unresponsive for five seconds. Then all the output appears at once. The GUI didn't update during that time because &lt;code&gt;subprocess.run()&lt;/code&gt; blocked the Qt event loop until it was finished.&lt;/p&gt;
&lt;p&gt;Now, let's look at the two solutions to this problem:&lt;/p&gt;
&lt;h2 id="streaming-subprocess-output-with-qprocess"&gt;Streaming Subprocess Output with &lt;code&gt;QProcess&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QProcess&lt;/code&gt; is Qt's own class for running external programs asynchronously. It starts the process and returns immediately, letting the event loop continue. As the external program produces output, &lt;code&gt;QProcess&lt;/code&gt; emits the &lt;code&gt;readyReadStandardOutput&lt;/code&gt; signal, which you can connect to a slot that reads and displays the new data.&lt;/p&gt;
&lt;p&gt;This is the most "Qt-native" solution for displaying real-time subprocess output in PyQt6 and works well for many use cases. For a deeper dive into &lt;code&gt;QProcess&lt;/code&gt; including handling stdin, managing multiple processes, and parsing output, see the complete &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qprocess-external-programs/"&gt;QProcess tutorial&lt;/a&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 PyQt6.QtCore import QProcess
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QProcess Live Output")
        self.process = None

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

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

    def run_command(self):
        if self.process is not None:
            return  # Already running

        self.text_area.clear()
        self.button.setEnabled(False)

        self.process = QProcess(self)
        self.process.readyReadStandardOutput.connect(self.handle_stdout)
        self.process.readyReadStandardError.connect(self.handle_stderr)
        self.process.finished.connect(self.process_finished)

        # QProcess takes the program and arguments separately.
        # To run a bash command, pass "-c" and the command string as arguments.
        self.process.start(
            "bash",
            ["-c", "for i in 1 2 3 4 5; do echo \"Line $i\"; sleep 1; done"],
        )

    def handle_stdout(self):
        data = self.process.readAllStandardOutput()
        text = bytes(data).decode("utf-8")
        self.text_area.appendPlainText(text.rstrip())

    def handle_stderr(self):
        data = self.process.readAllStandardError()
        text = bytes(data).decode("utf-8")
        self.text_area.appendPlainText(text.rstrip())

    def process_finished(self):
        self.text_area.appendPlainText("--- Process finished ---")
        self.process = None
        self.button.setEnabled(True)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this, click the button, and you'll see each line appear one at a time, with the GUI remaining fully responsive throughout.&lt;/p&gt;
&lt;h3&gt;How &lt;code&gt;QProcess&lt;/code&gt; Streams Output in Real Time&lt;/h3&gt;
&lt;p&gt;When you call &lt;code&gt;self.process.start()&lt;/code&gt;, the external command begins running in the background. Qt's event loop keeps spinning, so your window stays responsive.&lt;/p&gt;
&lt;p&gt;Each time the external process writes to stdout, &lt;code&gt;QProcess&lt;/code&gt; emits &lt;code&gt;readyReadStandardOutput&lt;/code&gt;. The connected slot (&lt;code&gt;handle_stdout&lt;/code&gt;) reads the available data and appends it to the text area. The same pattern applies for stderr.&lt;/p&gt;
&lt;p&gt;When the process exits, the &lt;code&gt;finished&lt;/code&gt; signal fires, and we clean up.&lt;/p&gt;
&lt;h3&gt;Running Complex Bash Commands with &lt;code&gt;QProcess&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If your actual command involves sourcing setup files, changing directories, and running build tools &amp;mdash; like in the original question &amp;mdash; you can pass the entire sequence as a single string to &lt;code&gt;bash -c&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;command = (
    "source /path/to/setup_file -r &amp;amp;&amp;amp; "
    "cd /path/to/parent_directory &amp;amp;&amp;amp; "
    "build_project_command"
)
self.process.start("bash", ["-c", command])
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This works because &lt;code&gt;bash -c&lt;/code&gt; accepts the whole pipeline as one argument.&lt;/p&gt;
&lt;h2 id="streaming-subprocess-output-using-qthread-and-subprocesspopen"&gt;Streaming Subprocess Output Using &lt;code&gt;QThread&lt;/code&gt; and &lt;code&gt;subprocess.Popen&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes &lt;code&gt;QProcess&lt;/code&gt; doesn't quite fit your needs. For example, you might need to do additional processing on each line of output before displaying it, or you might need to integrate with Python libraries that expect a file-like object. In these cases, running &lt;code&gt;subprocess.Popen&lt;/code&gt; in a background &lt;code&gt;QThread&lt;/code&gt; is a good alternative.&lt;/p&gt;
&lt;p&gt;The idea: spin up a &lt;code&gt;QThread&lt;/code&gt; that runs the subprocess, reads its output line by line, and emits a signal for each line. The main thread receives those signals and updates the GUI safely. If you're new to threading in PyQt6, our &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;guide to multithreading with QThreadPool&lt;/a&gt; covers the fundamentals of running background tasks without freezing the GUI.&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 subprocess
import sys

from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPlainTextEdit,
    QPushButton, QVBoxLayout, QWidget,
)


class SubprocessWorker(QThread):
    """Runs a subprocess in a background thread and emits output line by line."""

    output_line = pyqtSignal(str)
    finished_signal = pyqtSignal(int)  # exit code

    def __init__(self, command):
        super().__init__()
        self.command = command

    def run(self):
        process = subprocess.Popen(
            self.command,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,  # Line-buffered
        )

        for line in process.stdout:
            self.output_line.emit(line.rstrip())

        process.wait()
        self.finished_signal.emit(process.returncode)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QThread + Subprocess Live Output")
        self.worker = None

        self.text_area = QPlainTextEdit()
        self.text_area.setReadOnly(True)

        self.button = QPushButton("Run Command")
        self.button.clicked.connect(self.run_command)

        layout = QVBoxLayout()
        layout.addWidget(self.text_area)
        layout.addWidget(self.button)

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

    def run_command(self):
        if self.worker is not None:
            return

        self.text_area.clear()
        self.button.setEnabled(False)

        self.worker = SubprocessWorker(
            ["bash", "-c", "for i in 1 2 3 4 5; do echo \"Line $i\"; sleep 1; done"]
        )
        self.worker.output_line.connect(self.on_output_line)
        self.worker.finished_signal.connect(self.on_finished)
        self.worker.start()

    def on_output_line(self, text):
        self.text_area.appendPlainText(text)

    def on_finished(self, exit_code):
        self.text_area.appendPlainText(f"--- Process finished (exit code {exit_code}) ---")
        self.worker = None
        self.button.setEnabled(True)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;How &lt;code&gt;QThread&lt;/code&gt; with &lt;code&gt;subprocess.Popen&lt;/code&gt; Works&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;subprocess.Popen&lt;/code&gt; (unlike &lt;code&gt;subprocess.run&lt;/code&gt;) starts the process and returns immediately, giving you a handle to interact with it. By iterating over &lt;code&gt;process.stdout&lt;/code&gt;, you get each line as it's produced.&lt;/p&gt;
&lt;p&gt;Because this iteration is blocking (it waits for the next line), we run it in a &lt;code&gt;QThread&lt;/code&gt; so it doesn't block the GUI. Each time a line arrives, the worker emits &lt;code&gt;output_line&lt;/code&gt;, which is safely delivered to the main thread via Qt's &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signal-slot mechanism&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;bufsize=1&lt;/code&gt; and &lt;code&gt;text=True&lt;/code&gt; enables line-buffered mode, which means output is available to read as soon as a newline character is written by the subprocess.&lt;/p&gt;
&lt;h2 id="fixing-delayed-subprocess-output-buffering-issues"&gt;Fixing Delayed Subprocess Output: Buffering Issues&lt;/h2&gt;
&lt;p&gt;Even with both approaches working correctly on the Qt side, you might still see delayed output if the external program itself buffers its stdout. Many programs buffer output differently when they detect they're writing to a pipe (which is what happens with both &lt;code&gt;QProcess&lt;/code&gt; and &lt;code&gt;subprocess.Popen&lt;/code&gt;) versus writing to a terminal.&lt;/p&gt;
&lt;p&gt;If your external program supports it, you can try:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting the &lt;code&gt;PYTHONUNBUFFERED=1&lt;/code&gt; environment variable (for Python scripts).&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;stdbuf -oL&lt;/code&gt; to force line-buffered output: &lt;code&gt;stdbuf -oL your_command&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;script&lt;/code&gt; or &lt;code&gt;unbuffer&lt;/code&gt; (from the &lt;code&gt;expect&lt;/code&gt; package) to trick the program into thinking it's connected to a terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, with the &lt;code&gt;QProcess&lt;/code&gt; 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;self.process.start(
    "bash",
    ["-c", "stdbuf -oL your_long_running_command"],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="qprocess-vs-qthread-which-approach-should-you-use"&gt;&lt;code&gt;QProcess&lt;/code&gt; vs &lt;code&gt;QThread&lt;/code&gt;: Which Approach Should You Use?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;QProcess&lt;/code&gt;&lt;/strong&gt; when you're running a simple external command and want a clean, Qt-integrated solution. It handles the event loop integration for you, supports signals for stdout, stderr, and process completion, and doesn't require managing threads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use a background &lt;code&gt;QThread&lt;/code&gt;&lt;/strong&gt; when you need more control over how you read the output &amp;mdash; for example, if you want to parse each line, filter output, or interact with the subprocess's stdin in complex ways. The thread approach also makes it straightforward to use Python's &lt;code&gt;subprocess&lt;/code&gt; module features that don't have direct equivalents in &lt;code&gt;QProcess&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Both approaches keep the GUI responsive and deliver output in real time. Pick whichever fits your situation best.&lt;/p&gt;
&lt;h2 id="complete-example-live-build-output-viewer-in-pyqt6"&gt;Complete Example: Live Build Output Viewer in PyQt6&lt;/h2&gt;
&lt;p&gt;Here's a more polished example that combines the &lt;code&gt;QProcess&lt;/code&gt; approach with a few usability improvements &amp;mdash; a scrolling output view, a status indicator, and support for running a configurable command. This example uses &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-layouts/"&gt;layouts&lt;/a&gt; and basic widgets to build the interface:&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 QProcess
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import (
    QApplication, QHBoxLayout, QLabel, QLineEdit,
    QMainWindow, QPlainTextEdit, QPushButton,
    QVBoxLayout, QWidget,
)


class BuildOutputViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Live Build Output Viewer")
        self.resize(700, 500)
        self.process = None

        # Command input
        self.command_input = QLineEdit()
        self.command_input.setPlaceholderText(
            "Enter bash command, e.g.: for i in $(seq 1 10); do echo Building step $i; sleep 0.5; done"
        )
        self.command_input.setText(
            "for i in $(seq 1 10); do echo \"Building step $i...\"; sleep 0.5; done &amp;amp;&amp;amp; echo Done!"
        )

        # Output area
        self.output_area = QPlainTextEdit()
        self.output_area.setReadOnly(True)
        self.output_area.setFont(QFont("Courier", 10))
        self.output_area.setStyleSheet(
            "QPlainTextEdit { background-color: #1e1e1e; color: #d4d4d4; }"
        )

        # Buttons and status
        self.run_button = QPushButton("Run")
        self.run_button.clicked.connect(self.start_process)

        self.stop_button = QPushButton("Stop")
        self.stop_button.clicked.connect(self.stop_process)
        self.stop_button.setEnabled(False)

        self.status_label = QLabel("Ready")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.run_button)
        button_layout.addWidget(self.stop_button)
        button_layout.addWidget(self.status_label)
        button_layout.addStretch()

        layout = QVBoxLayout()
        layout.addWidget(self.command_input)
        layout.addLayout(button_layout)
        layout.addWidget(self.output_area)

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

    def start_process(self):
        command = self.command_input.text().strip()
        if not command:
            return

        self.output_area.clear()
        self.run_button.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.status_label.setText("Running...")

        self.process = QProcess(self)
        self.process.readyReadStandardOutput.connect(self.handle_stdout)
        self.process.readyReadStandardError.connect(self.handle_stderr)
        self.process.finished.connect(self.process_finished)

        self.process.start("bash", ["-c", command])

    def stop_process(self):
        if self.process is not None:
            self.process.kill()

    def handle_stdout(self):
        data = self.process.readAllStandardOutput()
        text = bytes(data).decode("utf-8")
        self.output_area.appendPlainText(text.rstrip())

    def handle_stderr(self):
        data = self.process.readAllStandardError()
        text = bytes(data).decode("utf-8")
        self.output_area.appendPlainText(text.rstrip())

    def process_finished(self, exit_code, exit_status):
        status_text = "Finished" if exit_code == 0 else f"Exited with code {exit_code}"
        self.status_label.setText(status_text)
        self.run_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.process = None


app = QApplication(sys.argv)
window = BuildOutputViewer()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This gives you a terminal-styled output viewer where you can type in a command, run it, watch the output stream in line by line, and stop it if needed &amp;mdash; all without the GUI ever locking up.&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.pythonguis.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="subprocess"/><category term="qprocess"/><category term="qthread"/><category term="gui"/><category term="qt"/><category term="qt6"/></entry><entry><title>Why Widgets Appear as Separate Windows — Understanding widget parenting in Qt and how to fix widgets that float outside your main window</title><link href="https://www.pythonguis.com/faq/adding-new-tabs-in-tabwidget-appears-as-a-seperate-window-when-tab-is-selected/" rel="alternate"/><published>2026-07-08T06:00:00+00:00</published><updated>2026-07-08T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-07-08:/faq/adding-new-tabs-in-tabwidget-appears-as-a-seperate-window-when-tab-is-selected/</id><summary type="html">Sometimes when I dynamically add widgets to tabs in my PyQt6 application, they pop out as windows instead. What's going on?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;Sometimes when I dynamically add widgets to tabs in my PyQt6 application, they pop out as windows instead. What's going on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you're dynamically adding widgets to your PyQt6 application and finding that they pop out as separate floating windows instead of appearing neatly inside your application, you're running into one of Qt's gotchas: &lt;em&gt;widget parenting&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This problem usually shows up when widgets are added from a callback, event listener or signal handler. But there are a million different ways to screw this up. Let's look at why this happens and how to fix it.&lt;/p&gt;
&lt;h2 id="how-qt-decides-whats-a-window"&gt;How Qt decides what's a window&lt;/h2&gt;
&lt;p&gt;In Qt, every widget can optionally have a &lt;strong&gt;parent&lt;/strong&gt; widget. The parent determines where a widget lives visually &amp;mdash; a widget with a parent is drawn &lt;em&gt;inside&lt;/em&gt; that parent. A widget &lt;em&gt;without&lt;/em&gt; a parent becomes a top-level window, floating independently on your desktop.&lt;/p&gt;
&lt;p&gt;This is the root cause of widgets appearing outside your main window. When you create a widget and it doesn't have a parent &amp;mdash; either because you didn't set one, or because the parent was lost somehow &amp;mdash; Qt treats it as a standalone window.&lt;/p&gt;
&lt;h2 id="three-ways-to-get-a-parent-less-widget"&gt;Three ways to Get a Parent-less Widget&lt;/h2&gt;
&lt;p&gt;Here are the most common reasons widgets end up floating:&lt;/p&gt;
&lt;h3&gt;Creating widgets without a parent&lt;/h3&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;# This widget has no parent &amp;mdash; it will be a floating window
tabs = QTabWidget()

# This widget has a parent &amp;mdash; it will appear inside parent_widget
tabs = QTabWidget(parent_widget)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you add a widget to a &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-layouts/"&gt;layout&lt;/a&gt;, the layout assigns the parent automatically. But if something goes wrong between creation and layout insertion (like an exception, or the widget being shown prematurely), the widget stays parentless.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;safest&lt;/em&gt; approach is to pass a parent when creating widgets:&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 create_new_tab(self):
    wdg = QWidget()
    layout = QGridLayout(wdg)

    tabs = QTabWidget(wdg)  # Explicitly set parent
    tab1 = QWidget(tabs)     # Explicitly set parent
    tab2 = QWidget(tabs)     # Explicitly set parent
    tabs.addTab(tab1, "Start")
    tabs.addTab(tab2, "Profile")
    layout.addWidget(tabs)

    return wdg
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...although, honestly, I don't usually bother. If I know I'll be adding a widget to a layout immediately, I'll omit the parent assignment.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  In an window &lt;code&gt;__init__&lt;/code&gt; the &lt;em&gt;safety&lt;/em&gt; question is less relevant because, if there is an unhandled exception that blocks the adding your sub-widget to a layout, it will also block the creation of the parent window.&lt;/p&gt;
&lt;h3&gt;Accidentally recreating a widget&lt;/h3&gt;
&lt;p&gt;If you have a tab widget stored as &lt;code&gt;self.w&lt;/code&gt; and somewhere in your code you do:&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.w = QTabWidget()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...the original tab widget is replaced. If the old widget gets garbage collected, all the tabs that had it as their parent suddenly become orphans &amp;mdash; parentless widgets that float as independent windows.&lt;/p&gt;
&lt;p&gt;Be careful not to reassign widget attributes unintentionally, especially in callbacks that might run multiple times.&lt;/p&gt;
&lt;h3&gt;Losing the parent reference&lt;/h3&gt;
&lt;p&gt;If you explicitly set a widget's parent to &lt;code&gt;None&lt;/code&gt;, it becomes a top-level window:&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;widget.setParent(None)  # This widget is now a floating window
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This sometimes happens indirectly. For example, removing a widget from a layout in certain ways can clear its parent.&lt;/p&gt;
&lt;h2 id="a-clean-approach-to-dynamic-tabs"&gt;A clean approach to dynamic tabs&lt;/h2&gt;
&lt;p&gt;Here's a complete, working example that dynamically adds tabs without any floating-window issues. It demonstrates the correct way to set up a &lt;code&gt;QTabWidget&lt;/code&gt; with a "+" button that adds new tabs:&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, QTabWidget,
    QWidget, QVBoxLayout, QLabel
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dynamic Tabs")
        self.setFixedSize(600, 400)

        self.tabs = QTabWidget(self)
        self.tabs.currentChanged.connect(self.on_tab_changed)

        # Add an initial tab
        self.add_content_tab("Tab 1")

        # Add the "+" tab for creating new tabs
        self.tabs.addTab(QWidget(self.tabs), "+")

        self.setCentralWidget(self.tabs)

    def on_tab_changed(self, index):
        # Check if the "+" tab was clicked
        if self.tabs.tabText(index) == "+":
            self.add_new_tab()

    def add_new_tab(self):
        # Count existing content tabs (exclude the "+" tab)
        tab_count = self.tabs.count()  # includes "+"
        new_title = f"Tab {tab_count}"

        # Insert the new tab before the "+" tab
        new_tab = self.create_tab_content(new_title)
        insert_index = self.tabs.count() - 1
        self.tabs.insertTab(insert_index, new_tab, new_title)

        # Switch to the newly created tab (avoid retriggering)
        self.tabs.blockSignals(True)
        self.tabs.setCurrentIndex(insert_index)
        self.tabs.blockSignals(False)

    def add_content_tab(self, title):
        """Add a content tab before the + tab."""
        tab = self.create_tab_content(title)
        # Insert before the last tab if "+" exists, otherwise just add
        plus_index = None
        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "+":
                plus_index = i
                break

        if plus_index is not None:
            self.tabs.insertTab(plus_index, tab, title)
        else:
            self.tabs.addTab(tab, title)

    def create_tab_content(self, title):
        """Create the widget content for a tab."""
        widget = QWidget(self.tabs)  # Parent is the tab widget
        layout = QVBoxLayout(widget)
        label = QLabel(f"Content for {title}", widget)
        layout.addWidget(label)
        return widget


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;A few things to notice in this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The main window inherits from &lt;code&gt;QMainWindow&lt;/code&gt;, and &lt;code&gt;QApplication&lt;/code&gt; is created separately.&lt;/li&gt;
&lt;li&gt;Every widget is created with an explicit parent: &lt;code&gt;QWidget(self.tabs)&lt;/code&gt;, &lt;code&gt;QLabel(text, widget)&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockSignals(True)&lt;/code&gt; is used when programmatically changing the current tab to prevent the &lt;code&gt;currentChanged&lt;/code&gt; &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signal&lt;/a&gt; from firing recursively.&lt;/li&gt;
&lt;li&gt;New tabs are inserted &lt;em&gt;before&lt;/em&gt; the "+" tab using &lt;code&gt;insertTab&lt;/code&gt;, so the "+" always stays at the end.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Widget parenting is one of those things in Qt that works invisibly when everything is correct &amp;mdash; and causes confusing visual glitches the moment something is slightly off. The good news is that once you understand the pattern, the fix is almost always the same: make sure every widget has a  parent.&lt;/p&gt;
&lt;p&gt;If you're new to PyQt6, our guide to &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window&lt;/a&gt; covers the basics of setting up a &lt;code&gt;QMainWindow&lt;/code&gt;, while the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-widgets/"&gt;widgets tutorial&lt;/a&gt; walks through the most common widgets and how to use them correctly.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="widgets"/><category term="troubleshooting"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>How to Set Row Background Colors in a QTableView — Use Qt's BackgroundRole to color entire rows based on your data</title><link href="https://www.pythonguis.com/faq/backgroundcolor-for-row-in-qtableview/" rel="alternate"/><published>2026-06-10T06:00:00+00:00</published><updated>2026-06-10T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-06-10:/faq/backgroundcolor-for-row-in-qtableview/</id><summary type="html">I have a QTableView table showing some data about connected devices. How can I highlight rows to give a visual indicator of the current status of the device?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I have a QTableView table showing some data about connected devices. How can I highlight rows to give a visual indicator of the current status of the device?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you're working with a &lt;code&gt;QTableView&lt;/code&gt; and a custom model, it's common to want to highlight entire rows based on some condition in your data. For example, you might want to color a row blue when a device has a &lt;em&gt;connected&lt;/em&gt; status, or red when something has gone wrong.&lt;/p&gt;
&lt;h2 id="understanding-how-data-works"&gt;Understanding How &lt;code&gt;data()&lt;/code&gt; Works&lt;/h2&gt;
&lt;p&gt;In Qt's &lt;a href="https://www.pythonguis.com/tutorials/modelview-architecture/"&gt;Model/View architecture&lt;/a&gt;, the view calls your model's &lt;code&gt;data()&lt;/code&gt; method for &lt;em&gt;every cell&lt;/em&gt; in the table &amp;mdash; and for each cell, it asks about multiple &lt;strong&gt;roles&lt;/strong&gt;. One of those roles is &lt;code&gt;Qt.BackgroundRole&lt;/code&gt;, which tells the view what background color to use for that cell.&lt;/p&gt;
&lt;p&gt;The view asks for &lt;code&gt;Qt.BackgroundRole&lt;/code&gt; on every single cell, not just one column. So if your &lt;code&gt;data()&lt;/code&gt; method returns a color for &lt;code&gt;Qt.BackgroundRole&lt;/code&gt; based on the &lt;em&gt;row&lt;/em&gt; data (ignoring the column), the color will be applied to every cell in that row.&lt;/p&gt;
&lt;p&gt;Let's build a working example.&lt;/p&gt;
&lt;h2 id="a-complete-working-example"&gt;A Complete Working Example&lt;/h2&gt;
&lt;p&gt;Here's a full example you can run directly. It creates a &lt;code&gt;QTableView&lt;/code&gt; with colored rows based on the &lt;code&gt;PRESENT_STATUS&lt;/code&gt; field in each row of 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;import sys
from typing import Union

from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView


class TableModel(QAbstractTableModel):

    def __init__(self, data: Union[list, None] = None):
        super().__init__()
        self._data = data or []
        self._hdr = self._gen_hdr_data() if data else []
        self._base_color = {
            "NewConnection": QColor("blue"),
            "Registered": QColor("green"),
        }

    def _gen_hdr_data(self):
        """Build a sorted list of all unique keys across all row dicts."""
        all_keys = set()
        for d in self._data:
            all_keys.update(d.keys())
        return sorted(all_keys)

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def columnCount(self, parent=QModelIndex()):
        return len(self._hdr)

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self._hdr[section]

    def data(self, index: QModelIndex, role: int):
        if not index.isValid():
            return None

        row_dict = self._data[index.row()]
        state = row_dict.get("PRESENT_STATUS", "")

        if role == Qt.DisplayRole:
            col_key = self._hdr[index.column()]
            value = row_dict.get(col_key, "")
            return str(value) if value else ""

        if role == Qt.BackgroundRole:
            color = self._base_color.get(state)
            if color:
                return color

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Row Background Colors in QTableView")

        data = [
            {"IP": "192.168.1.10", "PRESENT_STATUS": "NewConnection"},
            {"IP": "192.168.1.108", "FORMER_STATUS": "NewConnection",
             "PRESENT_STATUS": "Registered"},
            {"IP": "192.168.1.50", "PRESENT_STATUS": "Unknown"},
        ]

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The method that Qt calls on the model is called &lt;code&gt;data&lt;/code&gt;, so in the example above, the list is stored as &lt;code&gt;self._data&lt;/code&gt; (with a leading underscore) to avoid this.&lt;/p&gt;
&lt;p&gt;Run this and you'll see three rows. The first row ("NewConnection") has a blue background, the second row ("Registered") has a green background, and the third row ("Unknown") has no special coloring because it isn't in the &lt;code&gt;_base_color&lt;/code&gt; dictionary.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QTableView with colored rows based on status values" src="images/qtableview-row-background-colors.png"/&gt;&lt;/p&gt;
&lt;h2 id="how-colors-are-set-on-rows"&gt;How Colors are Set on Rows&lt;/h2&gt;
&lt;p&gt;To understand how the color is being set to the entire row, take a look at the  &lt;code&gt;Qt.BackgroundRole&lt;/code&gt; section of &lt;code&gt;data()&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;if role == Qt.BackgroundRole:
    color = self._base_color.get(state)
    if color:
        return color
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice that &lt;code&gt;index.column()&lt;/code&gt; isn't used here at all. The color decision is based entirely on the row's &lt;code&gt;PRESENT_STATUS&lt;/code&gt; value. Since the view calls &lt;code&gt;data()&lt;/code&gt; for &lt;em&gt;every cell&lt;/em&gt; in the row &amp;mdash; column 0, column 1, column 2, etc. &amp;mdash; and each call gets the same color back, the entire row ends up painted.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;only&lt;/em&gt; wanted to color a specific column (say, just the status column), you would add a column check:&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;if role == Qt.BackgroundRole:
    # Only color the PRESENT_STATUS column
    if self._hdr[index.column()] == "PRESENT_STATUS":
        color = self._base_color.get(state)
        if color:
            return color
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="making-the-text-readable"&gt;Making the Text Readable&lt;/h2&gt;
&lt;p&gt;One thing you'll notice with a dark background color like blue is that the default black text becomes hard to read. You can fix this by also handling &lt;code&gt;Qt.ForegroundRole&lt;/code&gt; and returning a light text color when the background is dark:&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 data(self, index: QModelIndex, role: int):
    if not index.isValid():
        return None

    row_dict = self._data[index.row()]
    state = row_dict.get("PRESENT_STATUS", "")

    if role == Qt.DisplayRole:
        col_key = self._hdr[index.column()]
        value = row_dict.get(col_key, "")
        return str(value) if value else ""

    if role == Qt.BackgroundRole:
        color = self._base_color.get(state)
        if color:
            return color

    if role == Qt.ForegroundRole:
        # If this row has a background color, use white text.
        if state in self._base_color:
            return QColor("white")

    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now blue and green rows will have white text, making everything easy to read.&lt;/p&gt;
&lt;h2 id="updating-colors-dynamically"&gt;Updating Colors Dynamically&lt;/h2&gt;
&lt;p&gt;If your data changes at runtime &amp;mdash; for example, a device's status changes from &lt;code&gt;"NewConnection"&lt;/code&gt; to &lt;code&gt;"Registered"&lt;/code&gt; &amp;mdash; you need to tell the view that something has changed so it repaints. You do this by emitting the &lt;code&gt;dataChanged&lt;/code&gt; signal:&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_status(self, row, new_status):
    self._data[row]["PRESENT_STATUS"] = new_status
    # Emit dataChanged for the entire row.
    top_left = self.index(row, 0)
    bottom_right = self.index(row, self.columnCount() - 1)
    self.dataChanged.emit(top_left, bottom_right)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This tells the view to re-query &lt;code&gt;data()&lt;/code&gt; for every cell in that row, which picks up both the new display text and the new background color. For a deeper look at how signals work to keep your model and view in sync, see &lt;a href="https://www.pythonguis.com/tutorials/pyqt-signals-slots-events/"&gt;Signals, Slots &amp;amp; Events&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Once you understand how the model's &lt;code&gt;data()&lt;/code&gt; method works, coloring entire rows in a &lt;code&gt;QTableView&lt;/code&gt; is relatively straightforward. The view asks for each role on every cell, so returning a color from &lt;code&gt;Qt.BackgroundRole&lt;/code&gt; based on row-level data &amp;mdash; without filtering by column &amp;mdash; naturally paints the whole row. Pair that with &lt;code&gt;Qt.ForegroundRole&lt;/code&gt; for readable text, and you've got a clean, data-driven way to highlight rows in your table.&lt;/p&gt;
&lt;p&gt;To learn more about using &lt;code&gt;QTableView&lt;/code&gt; with custom models and data from numpy or pandas, see the &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;QTableView with numpy and pandas tutorial&lt;/a&gt;. If you want to add sorting and filtering to your table, take a look at &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-sort-filter-tables/"&gt;Sorting and Filtering Tables&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qtableview"/><category term="model-view"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Authentication and Authorization with PyQt6 or PySide6 — Secure your desktop applications with login flows, token-based auth, and role-based access control</title><link href="https://www.pythonguis.com/faq/authentication-and-authorization-with-pyqt6-or-pyside6/" rel="alternate"/><published>2026-06-03T06:00:00+00:00</published><updated>2026-06-03T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-06-03:/faq/authentication-and-authorization-with-pyqt6-or-pyside6/</id><summary type="html">How can I add authentication and authorization to a PyQt6 application? Is there something built into Qt to make this easier?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;How can I add authentication and authorization to a PyQt6 application? Is there something built into Qt to make this easier?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you build a desktop application with PyQt6 or PySide6, sooner or later you'll need to control who can use it and what they can do. Maybe your app connects to a cloud service. Maybe certain features should only be available to administrators. Either way, you need &lt;strong&gt;authentication&lt;/strong&gt; (verifying who the user is) and &lt;strong&gt;authorization&lt;/strong&gt; (deciding what they're allowed to do).&lt;/p&gt;
&lt;p&gt;Qt doesn't provide a built-in authentication framework. But that's fine. You can combine Qt's capabilities with Python's networking and security tools to build a solid auth flow for your application.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through the full process: creating a login dialog, authenticating against a remote server, handling tokens, and enabling or disabling parts of your UI based on a user's role.&lt;/p&gt;
&lt;h2 id="approaches-to-authentication-in-desktop-apps"&gt;Approaches to Authentication in Desktop Apps&lt;/h2&gt;
&lt;p&gt;Before writing any code, it helps to understand the options available when securing a desktop application. The right approach depends on how much security you need and what infrastructure you have.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Simple login check&lt;/strong&gt; Your app sends credentials to a remote server at startup. If authentication fails, you disable the UI (partially or entirely). This deters casual users, but a determined hacker could modify the client to bypass the check.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token-based unlock&lt;/strong&gt; After a successful login, the server returns a token or key that unlocks functionality in the app. Without the token, the app can't perform certain operations. This is more secure &amp;mdash; the app is genuinely non-functional without a valid token &amp;mdash; though once data is decoded into memory, it's theoretically still accessible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side execution&lt;/strong&gt; After authentication, the app sends work to the server, which performs the actual operations. The sensitive logic never runs on the client at all. This is the most secure approach, but it requires server infrastructure to handle the workload.&lt;/li&gt;
&lt;/ol&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  In the &lt;strong&gt;Server-side execution&lt;/strong&gt; model, the work done on the server doesn't necessarily need to be complex. Transforming or pre-processing some data from one format to another will be enough to deter most attempts at circumvention. However, it's common to to use this technique to hide the algorithmic "secret sauce" completely.&lt;/p&gt;
&lt;p&gt;For most applications, the middle ground &amp;mdash; authenticating against a remote API and using the returned token to gate access &amp;mdash; provides a good balance of security and simplicity. That's what we'll build here.&lt;/p&gt;
&lt;p&gt;Your app shouldn't care about the database directly. Instead, it should talk to an &lt;strong&gt;API&lt;/strong&gt; (Application Programming Interface) on your server. The API handles user lookups, password verification, and token generation. Your desktop app just sends HTTP requests and processes the responses.&lt;/p&gt;
&lt;h2 id="setting-up-a-simple-auth-server-for-testing"&gt;Setting Up a Simple Auth Server (For Testing)&lt;/h2&gt;
&lt;p&gt;To test our client application, we need something to authenticate against. We'll create a minimal Flask server that accepts login requests and returns a JSON Web Token (JWT). In a real project, this would be your existing backend, but having a self-contained example makes it easier to experiment.&lt;/p&gt;
&lt;p&gt;Install the dependencies for the server:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pip install flask pyjwt
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here's a minimal auth server:&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 datetime

import jwt
from flask import Flask, jsonify, request

app = Flask(__name__)
SECRET_KEY = "your-secret-key-change-this"

# In production, use a real database with hashed passwords.
USERS = {
    "admin": {"password": "admin123", "role": "admin"},
    "viewer": {"password": "viewer123", "role": "viewer"},
}


@app.route("/auth/login", methods=["POST"])
def login():
    data = request.get_json()
    username = data.get("username", "")
    password = data.get("password", "")

    user = USERS.get(username)
    if user and user["password"] == password:
        token = jwt.encode(
            {
                "username": username,
                "role": user["role"],
                "exp": datetime.datetime.utcnow()
                + datetime.timedelta(hours=1),
            },
            SECRET_KEY,
            algorithm="HS256",
        )
        return jsonify(
            {"token": token, "role": user["role"], "username": username}
        )

    return jsonify({"error": "Invalid credentials"}), 401


@app.route("/auth/verify", methods=["GET"])
def verify():
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return jsonify({"error": "Missing token"}), 401

    token = auth_header.split(" ", 1)[1]
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return jsonify(
            {"username": payload["username"], "role": payload["role"]}
        )
    except jwt.ExpiredSignatureError:
        return jsonify({"error": "Token expired"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"error": "Invalid token"}), 401


if __name__ == "__main__":
    app.run(port=5000, debug=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save this as &lt;code&gt;auth_server.py&lt;/code&gt; and run it in a separate terminal:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;python auth_server.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The server exposes two endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /auth/login&lt;/code&gt; &amp;mdash; accepts a JSON body with &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, returns a JWT token.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /auth/verify&lt;/code&gt; &amp;mdash; accepts an &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt; header and returns the user info if the token is valid.&lt;/li&gt;
&lt;/ul&gt;
&lt;p class="admonition admonition-important"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation"&gt;&lt;/i&gt;&lt;/span&gt;  This server stores passwords in plain text and uses a hardcoded secret key. In production, you'd hash passwords (using &lt;code&gt;bcrypt&lt;/code&gt; or similar) and store the secret key securely. &lt;strong&gt;This is purely for demonstration.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="building-the-login-dialog"&gt;Building the Login Dialog&lt;/h2&gt;
&lt;p&gt;Now let's build the PyQt6 side. We'll start with a login dialog &amp;mdash; a modal window where the user enters their credentials. If you're new to dialogs in Qt, see our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-dialogs/"&gt;creating dialogs in PyQt6&lt;/a&gt; for a thorough introduction.&lt;/p&gt;
&lt;p&gt;Install the client dependencies:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pip install PyQt6 requests
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;If you're using PySide6, replace &lt;code&gt;from PyQt6.QtWidgets import ...&lt;/code&gt; with &lt;code&gt;from PySide6.QtWidgets import ...&lt;/code&gt; (and similarly for other Qt modules). The rest of the code is identical.&lt;/p&gt;
&lt;/blockquote&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.QtCore import Qt
from PyQt6.QtWidgets import (
    QDialog,
    QFormLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QVBoxLayout,
)


class LoginDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Login")
        self.setFixedSize(350, 200)

        layout = QVBoxLayout()

        self.form_layout = QFormLayout()

        self.username_input = QLineEdit()
        self.username_input.setPlaceholderText("Enter your username")
        self.form_layout.addRow("Username:", self.username_input)

        self.password_input = QLineEdit()
        self.password_input.setPlaceholderText("Enter your password")
        self.password_input.setEchoMode(QLineEdit.Password)
        self.form_layout.addRow("Password:", self.password_input)

        layout.addLayout(self.form_layout)

        self.login_button = QPushButton("Login")
        self.login_button.clicked.connect(self.accept)
        layout.addWidget(self.login_button)

        self.status_label = QLabel("")
        self.status_label.setAlignment(Qt.AlignCenter)
        self.status_label.setStyleSheet("color: red;")
        layout.addWidget(self.status_label)

        self.setLayout(layout)

        # Allow pressing Enter to submit.
        self.password_input.returnPressed.connect(self.login_button.click)
        self.username_input.returnPressed.connect(
            self.password_input.setFocus
        )

    def get_credentials(self):
        return (
            self.username_input.text().strip(),
            self.password_input.text(),
        )

    def set_status(self, message):
        self.status_label.setText(message)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This dialog inherits from &lt;code&gt;QDialog&lt;/code&gt;, which gives us the modal behavior we need &amp;mdash; when shown with &lt;code&gt;.exec_()&lt;/code&gt;, it blocks interaction with the rest of the application until the user either logs in or closes the dialog.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;get_credentials&lt;/code&gt; method returns the entered username and password as a tuple. The &lt;code&gt;set_status&lt;/code&gt; method lets us display error messages (like "Invalid credentials") directly in the dialog.&lt;/p&gt;
&lt;h2 id="creating-an-auth-manager"&gt;Creating an Auth Manager&lt;/h2&gt;
&lt;p&gt;Rather than scattering authentication logic throughout the application, we'll encapsulate it in a dedicated class. This &lt;code&gt;AuthManager&lt;/code&gt; handles login requests, stores the token, and provides the user's role.&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 requests


class AuthManager:
    def __init__(self, base_url="http://localhost:5000"):
        self.base_url = base_url
        self.token = None
        self.username = None
        self.role = None

    def login(self, username, password):
        """
        Attempt to log in. Returns True on success, False on failure.
        Raises an exception on network errors.
        """
        response = requests.post(
            f"{self.base_url}/auth/login",
            json={"username": username, "password": password},
            timeout=10,
        )

        if response.status_code == 200:
            data = response.json()
            self.token = data["token"]
            self.username = data["username"]
            self.role = data["role"]
            return True

        return False

    def is_authenticated(self):
        return self.token is not None

    def get_auth_header(self):
        """Return headers dict with the Bearer token for API requests."""
        if self.token:
            return {"Authorization": f"Bearer {self.token}"}
        return {}

    def has_role(self, role):
        return self.role == role

    def logout(self):
        self.token = None
        self.username = None
        self.role = None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;get_auth_header&lt;/code&gt; method is especially useful. Once a user has logged in, you can include this header in any subsequent API call to prove that the request is coming from an authenticated user:&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;response = requests.get(
    "http://localhost:5000/some/protected/endpoint",
    headers=auth_manager.get_auth_header(),
    timeout=10,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="wiring-up-the-login-flow"&gt;Wiring Up the Login Flow&lt;/h2&gt;
&lt;p&gt;Now we connect the login dialog to the auth manager. The pattern is: show the dialog, grab the credentials, try to authenticate, and either proceed to the main window or show an error.&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, QMessageBox


def attempt_login(auth_manager):
    """
    Show the login dialog repeatedly until the user either
    successfully authenticates or cancels.
    Returns True on successful login, False if cancelled.
    """
    dialog = LoginDialog()

    while True:
        result = dialog.exec_()

        if result != QDialog.Accepted:
            # User closed the dialog or pressed Cancel.
            return False

        username, password = dialog.get_credentials()

        if not username or not password:
            dialog.set_status("Please enter both fields.")
            continue

        try:
            if auth_manager.login(username, password):
                return True
            else:
                dialog.set_status("Invalid username or password.")
        except requests.exceptions.ConnectionError:
            dialog.set_status("Cannot connect to server.")
        except requests.exceptions.Timeout:
            dialog.set_status("Connection timed out.")
        except requests.exceptions.RequestException as e:
            dialog.set_status(f"Error: {e}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This function keeps showing the login dialog until either the login succeeds or the user dismisses it. Network errors are caught and displayed in the dialog, so the user gets useful feedback without the app crashing.&lt;/p&gt;
&lt;h2 id="building-the-main-window-with-role-based-access"&gt;Building the Main Window with Role-Based Access&lt;/h2&gt;
&lt;p&gt;The main window of our application will show different features depending on the user's role. Admin users see everything; viewers have a restricted experience. We'll use &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-actions-toolbars-menus/"&gt;actions, toolbars, and menus&lt;/a&gt; to structure the interface.&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 (
    QAction,
    QMainWindow,
    QMenu,
    QMenuBar,
    QStatusBar,
    QTextEdit,
    QToolBar,
)


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

        self.setWindowTitle("My Application")
        self.setMinimumSize(600, 400)

        # Central widget.
        self.text_edit = QTextEdit()
        self.setCentralWidget(self.text_edit)

        # Menu bar.
        menu_bar = self.menuBar()

        file_menu = menu_bar.addMenu("&amp;amp;File")

        self.save_action = QAction("&amp;amp;Save", self)
        self.save_action.triggered.connect(self.save_document)
        file_menu.addAction(self.save_action)

        file_menu.addSeparator()

        logout_action = QAction("&amp;amp;Logout", self)
        logout_action.triggered.connect(self.handle_logout)
        file_menu.addAction(logout_action)

        quit_action = QAction("&amp;amp;Quit", self)
        quit_action.triggered.connect(self.close)
        file_menu.addAction(quit_action)

        # Admin-only menu.
        self.admin_menu = menu_bar.addMenu("&amp;amp;Admin")

        manage_users_action = QAction("&amp;amp;Manage Users", self)
        manage_users_action.triggered.connect(self.manage_users)
        self.admin_menu.addAction(manage_users_action)

        server_settings_action = QAction("&amp;amp;Server Settings", self)
        server_settings_action.triggered.connect(self.server_settings)
        self.admin_menu.addAction(server_settings_action)

        # Status bar.
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # Apply role-based restrictions.
        self.apply_permissions()

    def apply_permissions(self):
        """Enable or disable UI elements based on the user's role."""
        role = self.auth_manager.role
        username = self.auth_manager.username

        self.status_bar.showMessage(
            f"Logged in as {username} ({role})"
        )

        if role == "admin":
            # Admins get full access.
            self.admin_menu.setEnabled(True)
            self.save_action.setEnabled(True)
            self.text_edit.setReadOnly(False)
        elif role == "viewer":
            # Viewers can see content but not edit or access admin.
            self.admin_menu.setEnabled(False)
            self.save_action.setEnabled(False)
            self.text_edit.setReadOnly(True)
            self.text_edit.setPlaceholderText(
                "You have read-only access."
            )
        else:
            # Unknown role: disable everything as a safe default.
            self.admin_menu.setEnabled(False)
            self.save_action.setEnabled(False)
            self.text_edit.setReadOnly(True)

    def save_document(self):
        QMessageBox.information(
            self, "Save", "Document saved (placeholder)."
        )

    def manage_users(self):
        QMessageBox.information(
            self, "Admin", "User management (placeholder)."
        )

    def server_settings(self):
        QMessageBox.information(
            self, "Admin", "Server settings (placeholder)."
        )

    def handle_logout(self):
        self.auth_manager.logout()
        self.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;apply_permissions&lt;/code&gt; method is where authorization happens. After a successful login, we check the user's role and adjust the UI accordingly. Disabled menu items are grayed out and non-clickable, and the text editor is set to read-only for viewers.&lt;/p&gt;
&lt;p&gt;This approach &amp;mdash; enabling and disabling widgets based on roles &amp;mdash; is the standard pattern for authorization in desktop apps. You can extend it as far as you need: hide entire toolbar sections, show different pages in a stacked widget, or restrict access to specific actions.&lt;/p&gt;
&lt;h2 id="making-authenticated-api-requests"&gt;Making Authenticated API Requests&lt;/h2&gt;
&lt;p&gt;Once a user is logged in, you'll often need to make further API calls &amp;mdash; fetching data, submitting forms, etc. Each of these requests should include the authentication token so the server can verify the user. For long-running API calls, consider using &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;multithreading with QThreadPool&lt;/a&gt; to keep the UI responsive while waiting for server responses.&lt;/p&gt;
&lt;p&gt;Here's how you might fetch some protected 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 fetch_protected_data(auth_manager):
    """Example of making an authenticated API request."""
    try:
        response = requests.get(
            f"{auth_manager.base_url}/auth/verify",
            headers=auth_manager.get_auth_header(),
            timeout=10,
        )

        if response.status_code == 200:
            return response.json()
        elif response.status_code == 401:
            # Token expired or invalid &amp;mdash; user needs to log in again.
            return None
    except requests.exceptions.RequestException:
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the server responds with a &lt;code&gt;401 Unauthorized&lt;/code&gt;, that means the token has expired or been revoked. You should handle this gracefully &amp;mdash; for example, by showing the login dialog again.&lt;/p&gt;
&lt;h2 id="handling-token-expiration"&gt;Handling Token Expiration&lt;/h2&gt;
&lt;p&gt;Tokens expire. When they do, your app needs to respond appropriately rather than silently failing. A common approach is to wrap your API calls in a method that checks for 401 responses and triggers a re-login:&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 authenticated_request(auth_manager, method, url, **kwargs):
    """
    Make an HTTP request with authentication.
    Returns the response, or None if re-authentication fails.
    """
    kwargs.setdefault("headers", {})
    kwargs["headers"].update(auth_manager.get_auth_header())
    kwargs.setdefault("timeout", 10)

    try:
        response = requests.request(method, url, **kwargs)

        if response.status_code == 401:
            # Token expired &amp;mdash; try to re-authenticate.
            if attempt_login(auth_manager):
                kwargs["headers"].update(
                    auth_manager.get_auth_header()
                )
                response = requests.request(method, url, **kwargs)
            else:
                return None

        return response

    except requests.exceptions.RequestException:
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This function automatically retries the request with a new token if the first attempt gets a 401. The user sees the login dialog, re-enters their credentials, and the request proceeds as if nothing happened.&lt;/p&gt;
&lt;p&gt;To try it out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start the auth server in one terminal: &lt;code&gt;python auth_server.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the client application in another terminal: &lt;code&gt;python app.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Log in as &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;admin123&lt;/code&gt; to see full access, or &lt;code&gt;viewer&lt;/code&gt; / &lt;code&gt;viewer123&lt;/code&gt; to see restricted access.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Try logging in with the wrong password &amp;mdash; the dialog stays open and shows an error. Close the dialog without logging in and the app exits cleanly.&lt;/p&gt;
&lt;h2 id="security-considerations"&gt;Security Considerations&lt;/h2&gt;
&lt;p&gt;A few things to keep in mind when implementing auth in a desktop application:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never store passwords in the client.&lt;/strong&gt; Your app should only ever send credentials to the server and receive a token back. The token is what you store (in memory, or securely on disk if you want "remember me" functionality).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use HTTPS in production.&lt;/strong&gt; Our example uses plain HTTP because it's running locally. In a real deployment, all communication between the client and server should be encrypted with TLS. The &lt;code&gt;requests&lt;/code&gt; library handles HTTPS transparently &amp;mdash; just change the URL to &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tokens are temporary.&lt;/strong&gt; JWTs (and most authentication tokens) have an expiration time. Design your app to handle expired tokens gracefully, as shown in the token expiration section above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client-side checks are not enough.&lt;/strong&gt; Disabling a button in the UI doesn't prevent a technically savvy user from calling the underlying function. Any action that matters should be validated on the server side too. The client-side restrictions are a UX convenience, not a security boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Store tokens securely.&lt;/strong&gt; If you implement a "remember me" feature that persists the token between sessions, use your platform's secure storage &amp;mdash; &lt;code&gt;keyring&lt;/code&gt; is a good cross-platform Python library for this. Don't write tokens to plain text files. You can also use &lt;a href="https://www.pythonguis.com/faq/pyqt6-qsettings-how-to-use-qsettings/"&gt;QSettings&lt;/a&gt; to persist non-sensitive user preferences like the last-used username, but avoid storing tokens or credentials there since QSettings does not provide encryption.&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="pyside6"/><category term="pyside"/><category term="authentication"/><category term="authorization"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Fixing Missing Icons in PyInstaller-Packaged PyQt6 Applications on Windows — Why your app icon disappears after packaging and how to fix it</title><link href="https://www.pythonguis.com/faq/always-the-default-icon/" rel="alternate"/><published>2026-05-27T06:00:00+00:00</published><updated>2026-05-27T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-05-27:/faq/always-the-default-icon/</id><summary type="html">I've packaged my PyQt application with PyInstaller, but the icon isn't showing up &amp;mdash; both the executable icon and the running application icon are just the default Python/Windows icon. What's going on?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I've packaged my PyQt application with PyInstaller, but the icon isn't showing up &amp;mdash; both the executable icon and the running application icon are just the default Python/Windows icon. What's going on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a common issue when &lt;a href="https://www.pythonguis.com/tutorials/packaging-pyqt6-applications-windows-pyinstaller/"&gt;packaging PyQt6 apps with PyInstaller on Windows&lt;/a&gt;. The good news is that it usually comes down to one of two straightforward causes: Windows icon caching, and missing resource files in your packaged output.&lt;/p&gt;
&lt;h2 id="setting-the-executable-icon-with-pyinstaller"&gt;Setting the executable icon with PyInstaller&lt;/h2&gt;
&lt;p&gt;When you run PyInstaller, you can set the icon for the &lt;code&gt;.exe&lt;/code&gt; file itself using the &lt;code&gt;--icon&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pyinstaller --windowed --icon=myicon.ico myapp.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This embeds the icon into the executable, so it shows up in File Explorer and on the desktop. The icon file needs to be in &lt;code&gt;.ico&lt;/code&gt; format &amp;mdash; &lt;code&gt;.png&lt;/code&gt; or &lt;code&gt;.svg&lt;/code&gt; won't work here.&lt;/p&gt;
&lt;p&gt;After building, check the &lt;code&gt;dist/&lt;/code&gt; folder. Your &lt;code&gt;.exe&lt;/code&gt; should display the custom icon. But sometimes... it doesn't.&lt;/p&gt;
&lt;h2 id="windows-icon-caching"&gt;Windows icon caching&lt;/h2&gt;
&lt;p&gt;Windows caches icons aggressively. If you've previously built your app without a custom icon, Windows may continue to show the old default icon even after you've rebuilt the app with the correct one.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  This still catches me out, even though I know this. You'll reflexively start checking the config assuming something is wrong, and think you're going mad.&lt;/p&gt;
&lt;p&gt;There are a few ways to deal with this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rename the executable.&lt;/strong&gt; Changing the filename forces Windows to look up the icon fresh. This is the quickest way to confirm that your icon &lt;em&gt;is&lt;/em&gt; actually embedded correctly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear the Windows icon cache.&lt;/strong&gt; You can do this by restarting Windows Explorer or by deleting the icon cache files manually. To manually clear the Windows icon cache open a Command Prompt and run:&lt;/li&gt;
&lt;/ul&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;ie4uinit.exe -show
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After clearing the cache, the correct icon should appear.&lt;/p&gt;
&lt;p&gt;You can also try turning your computer off and on again, or rather restarting Windows. That will also trigger the icon cache to rebuild.&lt;/p&gt;
&lt;h2 id="missing-icon-file-at-runtime"&gt;Missing icon file at runtime&lt;/h2&gt;
&lt;p&gt;Setting the executable icon with &lt;code&gt;--icon&lt;/code&gt; only affects what shows up in File Explorer. If your application &lt;em&gt;also&lt;/em&gt; sets a window icon in code (using &lt;code&gt;setWindowIcon&lt;/code&gt;), that icon file needs to be available at runtime too.&lt;/p&gt;
&lt;p&gt;For example, if your code does this:&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 QApplication, QMainWindow
from PyQt6.QtGui import QIcon
import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My Application")


app = QApplication(sys.argv)
app.setWindowIcon(QIcon("myicon.ico"))

window = MainWindow()
window.show()

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then &lt;code&gt;myicon.ico&lt;/code&gt; needs to exist in the working directory when the packaged app runs. By default, PyInstaller doesn't include data files like &lt;code&gt;.ico&lt;/code&gt; images unless you tell it to.&lt;/p&gt;
&lt;p&gt;You can add the icon file to your build using the &lt;code&gt;--add-data&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pyinstaller --windowed --icon=myicon.ico --add-data "myicon.ico;." myapp.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On Linux or macOS, use &lt;code&gt;:&lt;/code&gt; instead of &lt;code&gt;;&lt;/code&gt; as the separator:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pyinstaller --windowed --icon=myicon.ico --add-data "myicon.ico:." myapp.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This copies &lt;code&gt;myicon.ico&lt;/code&gt; into the output directory alongside your executable (or into the temporary directory if you're using &lt;code&gt;--onefile&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;An alternative approach (not available on PyQt6) is to use the &lt;a href="https://www.pythonguis.com/tutorials/pyside6-qresource-system/"&gt;Qt Resource System&lt;/a&gt; to embed your icon directly into your application, which avoids the need to bundle separate icon files entirely.&lt;/p&gt;
&lt;h3&gt;Handling &lt;code&gt;--onefile&lt;/code&gt; builds&lt;/h3&gt;
&lt;p&gt;When you use &lt;code&gt;--onefile&lt;/code&gt;, PyInstaller extracts everything to a temporary folder at runtime. Your code needs to know how to find files relative to that temporary folder. You can handle this by detecting the base path:&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

if getattr(sys, 'frozen', False):
    # Running as a PyInstaller bundle
    basedir = sys._MEIPASS
else:
    # Running as a normal script
    basedir = os.path.dirname(__file__)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then use &lt;code&gt;basedir&lt;/code&gt; when constructing file paths:&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;app.setWindowIcon(QIcon(os.path.join(basedir, "myicon.ico")))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="taskbar-grouping-with-an-application-user-model-id"&gt;Taskbar grouping with an Application User Model ID&lt;/h2&gt;
&lt;p&gt;On Windows, the taskbar groups windows by their application identity. Without an explicit identity, Windows guesses &amp;mdash; and sometimes guesses wrong. This can cause your app to show the Python icon in the taskbar, or to group instances inconsistently depending on where they were launched from.&lt;/p&gt;
&lt;p&gt;You can fix this by setting an &lt;em&gt;Application User Model ID&lt;/em&gt; before creating your &lt;code&gt;QApplication&lt;/code&gt;. This tells Windows exactly which application this is:&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 ctypes

myappid = "com.mycompany.myapp.1.0"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The string can be anything, but it's conventional to use a reverse-domain format. The value just needs to be unique to your application.&lt;/p&gt;
&lt;p&gt;With an explicit app ID set, all instances of your app will group together in the taskbar regardless of where they were launched from &amp;mdash; whether that's your IDE, the &lt;code&gt;dist/&lt;/code&gt; folder, or a &lt;code&gt;--onefile&lt;/code&gt; build.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a complete example that handles all of the above &amp;mdash; the runtime base path, the window icon, and the application user model ID. If you're new to building PyQt6 applications, you may want to start with &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window&lt;/a&gt; before tackling packaging.&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
import ctypes

from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import Qt


# Set the app user model ID before creating QApplication (Windows only)
if sys.platform == "win32":
    myappid = "com.mycompany.myapp.1.0"
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)

# Determine the base directory for resource files
if getattr(sys, "frozen", False):
    basedir = sys._MEIPASS
else:
    basedir = os.path.dirname(__file__)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My Application")
        label = QLabel("Hello, world!")
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setCentralWidget(label)


app = QApplication(sys.argv)
app.setWindowIcon(QIcon(os.path.join(basedir, "myicon.ico")))

window = MainWindow()
window.show()

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To package this with PyInstaller:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pyinstaller --windowed --icon=myicon.ico --add-data "myicon.ico;." myapp.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyinstaller"/><category term="windows"/><category term="icons"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Adding QComboBox to a QTableView and getting/setting values after creation — Use QItemDelegate to embed combo boxes in your table views, with per-row data and value tracking</title><link href="https://www.pythonguis.com/faq/adding-qcombobox-to-a-qtableview-and-getting-setting-values-after-creation/" rel="alternate"/><published>2026-05-20T06:00:00+00:00</published><updated>2026-05-20T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-05-20:/faq/adding-qcombobox-to-a-qtableview-and-getting-setting-values-after-creation/</id><summary type="html">I'm using a QTableView to display data, and would like to limit the choices in some of the fields using a drop-down. I can use &lt;code&gt;QComboBox&lt;/code&gt; to provide a list of choices in a normal UI, but how can I do that in a table view?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm using a QTableView to display data, and would like to limit the choices in some of the fields using a drop-down. I can use &lt;code&gt;QComboBox&lt;/code&gt; to provide a list of choices in a normal UI, but how can I do that in a table view?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you're working with &lt;code&gt;QTableView&lt;/code&gt; in PyQt6, you'll sometimes want cells that offer a dropdown selection instead of plain text. A &lt;code&gt;QComboBox&lt;/code&gt; is the natural fit here &amp;mdash; but embedding one inside a table view takes a bit of wiring up.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to use a &lt;code&gt;QItemDelegate&lt;/code&gt; to place a &lt;code&gt;QComboBox&lt;/code&gt; into specific cells of a &lt;code&gt;QTableView&lt;/code&gt;. We'll also cover how to populate each combo box with different items per row, and how to retrieve the selected value so you can use it elsewhere in your application.&lt;/p&gt;
&lt;h2 id="how-delegates-work-in-qts-modelview-framework"&gt;How delegates work in Qt's Model/View framework&lt;/h2&gt;
&lt;p&gt;Qt's &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-architecture/"&gt;Model/View architecture&lt;/a&gt; separates your data (the model) from how it's displayed (the view). Between these two sits the &lt;strong&gt;delegate&lt;/strong&gt;, which controls how individual cells are rendered and edited. When you want a cell to use a widget like a combo box instead of a plain text editor, you create a custom delegate.&lt;/p&gt;
&lt;p&gt;The delegate has a few methods you'll override:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;createEditor()&lt;/code&gt; &amp;mdash; creates the widget (in our case, a &lt;code&gt;QComboBox&lt;/code&gt;) when the user starts editing a cell.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setEditorData()&lt;/code&gt; &amp;mdash; populates the editor widget with the current data from the model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setModelData()&lt;/code&gt; &amp;mdash; writes the user's selection back into the model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updateEditorGeometry()&lt;/code&gt; &amp;mdash; makes sure the widget is sized and positioned correctly inside the cell.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's build this up step by step.&lt;/p&gt;
&lt;h2 id="setting-up-the-model-and-view"&gt;Setting up the model and view&lt;/h2&gt;
&lt;p&gt;First, let's create a simple application with a &lt;code&gt;QTableView&lt;/code&gt; and a &lt;code&gt;QStandardItemModel&lt;/code&gt;. Each row will represent a software package, and one of the columns will hold a list of available versions. We'll store those version lists directly in the model data, so each row can have its own set of options.&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, QTableView, QComboBox, QItemDelegate,
)
from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt6.QtCore import Qt, QItemDataRole


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QComboBox in QTableView")

        self.table = QTableView()
        self.setCentralWidget(self.table)

        # Create a model with 3 rows and 2 columns.
        self.model = QStandardItemModel(3, 2)
        self.model.setHorizontalHeaderLabels(["Package", "Version"])

        # Each row has a package name and a list of available versions.
        packages = [
            ("Widget Library", ["1.0", "1.1", "2.0", "2.1"]),
            ("Data Toolkit", ["0.9", "1.0"]),
            ("Render Engine", ["3.0", "3.1", "3.2", "4.0"]),
        ]

        for row, (name, versions) in enumerate(packages):
            # Column 0: package name (plain text).
            self.model.setItem(row, 0, QStandardItem(name))

            # Column 1: store the version list in the item's data.
            # We use Qt.ItemDataRole.UserRole to keep the full list alongside the display text.
            item = QStandardItem(versions[-1])  # Display the latest version by default.
            item.setData(versions, Qt.ItemDataRole.UserRole)
            self.model.setItem(row, 1, item)

        self.table.setModel(self.model)

        # Apply our custom delegate to column 1.
        delegate = ComboDelegate(self.table)
        self.table.setItemDelegateForColumn(1, delegate)

        self.resize(400, 200)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice how we store the list of versions using &lt;code&gt;Qt.ItemDataRole.UserRole&lt;/code&gt;. This is a custom data role &amp;mdash; it lets us attach extra information to a model item without interfering with the text that's displayed (which uses &lt;code&gt;Qt.ItemDataRole.DisplayRole&lt;/code&gt;). Each row gets its own version list, so when the combo box opens, it will show only the versions relevant to that row.&lt;/p&gt;
&lt;h2 id="creating-the-combo-box-delegate"&gt;Creating the combo box delegate&lt;/h2&gt;
&lt;p&gt;Now let's write the &lt;code&gt;ComboDelegate&lt;/code&gt; class. This is where the combo box gets created and connected 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;class ComboDelegate(QItemDelegate):
    """
    A delegate that places a QComboBox in cells of the assigned column.
    """

    def createEditor(self, parent, option, index):
        # Create the combo box and populate it with the version list for this row.
        combo = QComboBox(parent)
        versions = index.data(Qt.ItemDataRole.UserRole)
        if versions:
            combo.addItems(versions)
        return combo

    def setEditorData(self, editor, index):
        # Set the combo box to show the currently selected value.
        current_text = index.data(Qt.ItemDataRole.DisplayRole)
        idx = editor.findText(current_text)
        if idx &amp;gt;= 0:
            editor.setCurrentIndex(idx)

    def setModelData(self, editor, model, index):
        # Write the selected value back into the model.
        model.setData(index, editor.currentText(), Qt.ItemDataRole.DisplayRole)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let's walk through each method:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;createEditor()&lt;/code&gt;&lt;/strong&gt; is called when the user double-clicks (or otherwise activates) a cell in column 1. We create a fresh &lt;code&gt;QComboBox&lt;/code&gt;, pull the version list from &lt;code&gt;Qt.ItemDataRole.UserRole&lt;/code&gt; for that specific row, and add those items to the combo box. Because each row stores its own list, different rows will show different options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;setEditorData()&lt;/code&gt;&lt;/strong&gt; makes sure the combo box starts with the right item selected. We read the current display text from the model and find the matching entry in the combo box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;setModelData()&lt;/code&gt;&lt;/strong&gt; fires when the user finishes editing (for example, by clicking away from the cell). It takes whatever the user selected in the combo box and writes it back into the model's &lt;code&gt;DisplayRole&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;updateEditorGeometry()&lt;/code&gt;&lt;/strong&gt; simply ensures the combo box fills the cell neatly.&lt;/p&gt;
&lt;h2 id="running-the-application"&gt;Running the application&lt;/h2&gt;
&lt;p&gt;Add the standard entry point at the bottom of your 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;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 the script and double-click any cell in the "Version" column. You'll see a combo box appear with the version options for that specific row. Select a value, click away, and the cell updates.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QTableView with combo box delegates showing per-row version lists" src="combo-delegate-table.png"/&gt;&lt;/p&gt;
&lt;h2 id="getting-the-selected-value"&gt;Getting the selected value&lt;/h2&gt;
&lt;p&gt;After the user makes a selection, the value is stored in the model. You can read it at any time:&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;# Read the selected version for row 0.
selected = self.model.item(0, 1).text()
print(f"Row 0 selected version: {selected}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to react immediately when a selection changes, you can connect to the model's &lt;code&gt;dataChanged&lt;/code&gt; signal. If you're new to how signals work in Qt, see our guide on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals, slots and events&lt;/a&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;self.model.dataChanged.connect(self.on_data_changed)

def on_data_changed(self, top_left, bottom_right, roles):
    if top_left.column() == 1:
        row = top_left.row()
        value = top_left.data(Qt.ItemDataRole.DisplayRole)
        print(f"Row {row} version changed to: {value}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This approach keeps things nicely separate &amp;mdash; you're working through the model rather than trying to hold references to individual combo box widgets. The combo boxes are created and destroyed as the user interacts with cells.&lt;/p&gt;
&lt;h2 id="setting-a-value-programmatically"&gt;Setting a value programmatically&lt;/h2&gt;
&lt;p&gt;To change a cell's value from code, update the model directly:&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;# Set row 2's version to "3.1".
self.model.item(2, 1).setText("3.1")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The next time the user opens the combo box on that row, the delegate's &lt;code&gt;setEditorData()&lt;/code&gt; will position the combo box on "3.1".&lt;/p&gt;
&lt;p&gt;You can also update the list of available versions for a row:&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;# Add a new version to row 1's options.
item = self.model.item(1, 1)
versions = item.data(Qt.ItemDataRole.UserRole)
versions.append("1.1")
item.setData(versions, Qt.ItemDataRole.UserRole)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="why-each-row-gets-its-own-combo-box-items"&gt;Why each row gets its own combo box items&lt;/h2&gt;
&lt;p&gt;A common stumbling block is ending up with the same items in every combo box across the column. This happens when you store the item list on the delegate itself (as a single shared list) rather than on the model. Since the delegate is shared across all rows, any list stored on it will be the same everywhere.&lt;/p&gt;
&lt;p&gt;The solution, as we've done here, is to store per-row data in the model using &lt;code&gt;Qt.ItemDataRole.UserRole&lt;/code&gt;. Each call to &lt;code&gt;createEditor()&lt;/code&gt; reads from the specific index it's given, so each row naturally gets its own set of options. This is a pattern you'll use often when different rows need different editor configurations.&lt;/p&gt;
&lt;h2 id="complete-code"&gt;Complete code&lt;/h2&gt;
&lt;p&gt;Here's the full working example in one block:&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, QTableView, QComboBox, QItemDelegate,
)
from PyQt6.QtGui import QStandardItemModel, QStandardItem
from PyQt6.QtCore import Qt


class ComboDelegate(QItemDelegate):
    """
    A delegate that places a QComboBox in cells of the assigned column.
    """

    def createEditor(self, parent, option, index):
        combo = QComboBox(parent)
        versions = index.data(Qt.ItemDataRole.UserRole)
        if versions:
            combo.addItems(versions)
        return combo

    def setEditorData(self, editor, index):
        current_text = index.data(Qt.ItemDataRole.DisplayRole)
        idx = editor.findText(current_text)
        if idx &amp;gt;= 0:
            editor.setCurrentIndex(idx)

    def setModelData(self, editor, model, index):
        model.setData(index, editor.currentText(), Qt.ItemDataRole.DisplayRole)

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


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QComboBox in QTableView")

        self.table = QTableView()
        self.setCentralWidget(self.table)

        self.model = QStandardItemModel(3, 2)
        self.model.setHorizontalHeaderLabels(["Package", "Version"])

        packages = [
            ("Widget Library", ["1.0", "1.1", "2.0", "2.1"]),
            ("Data Toolkit", ["0.9", "1.0"]),
            ("Render Engine", ["3.0", "3.1", "3.2", "4.0"]),
        ]

        for row, (name, versions) in enumerate(packages):
            self.model.setItem(row, 0, QStandardItem(name))
            item = QStandardItem(versions[-1])
            item.setData(versions, Qt.ItemDataRole.UserRole)
            self.model.setItem(row, 1, item)

        self.table.setModel(self.model)

        delegate = ComboDelegate(self.table)
        self.table.setItemDelegateForColumn(1, delegate)

        # React to changes.
        self.model.dataChanged.connect(self.on_data_changed)

        self.resize(400, 200)

    def on_data_changed(self, top_left, bottom_right, roles):
        if top_left.column() == 1:
            row = top_left.row()
            value = top_left.data(Qt.ItemDataRole.DisplayRole)
            print(f"Row {row} version changed to: {value}")


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Using a custom &lt;code&gt;QItemDelegate&lt;/code&gt; gives you full control over how cells in a &lt;code&gt;QTableView&lt;/code&gt; are edited. By storing per-row data in the model with &lt;code&gt;Qt.ItemDataRole.UserRole&lt;/code&gt;, you can give each combo box its own set of items &amp;mdash; solving the common problem of all combo boxes showing the same options.&lt;/p&gt;
&lt;p&gt;The pattern here &amp;mdash; store data in the model, read it in the delegate, write changes back to the model &amp;mdash; works well beyond combo boxes. You can use the same approach to embed spin boxes, date pickers, or any other widget into your table cells. Once you're comfortable with this flow, you'll find Qt's Model/View framework surprisingly flexible. For a deeper dive into using &lt;code&gt;QTableView&lt;/code&gt; with real-world data sources like NumPy and Pandas, see our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qtableview-modelviews-numpy-pandas/"&gt;QTableView with numpy and pandas&lt;/a&gt; tutorial. You can also explore how to &lt;a href="https://www.pythonguis.com/faq/editing-pyqt6-tableview/"&gt;make table cells editable&lt;/a&gt; for other common editing patterns.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qtableview"/><category term="qcombobox"/><category term="delegate"/><category term="model-view"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>How to Add Custom Widgets to Qt Designer — Use widget promotion to integrate your own Python widgets into Qt Designer layouts</title><link href="https://www.pythonguis.com/faq/add-widgets-in-a-kind-of-library-show-in-the-qt-designer/" rel="alternate"/><published>2026-05-13T06:00:00+00:00</published><updated>2026-05-13T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-05-13:/faq/add-widgets-in-a-kind-of-library-show-in-the-qt-designer/</id><summary type="html">Can I use custom widgets in Qt Designer?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;Can I use custom widgets in Qt Designer?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you're building Python GUI applications with PyQt6 and Qt Designer, you'll reach a point where the built-in widgets aren't enough. Maybe you've created a custom plotting widget or a specialized input control in Python, and you want to place it into your Qt Designer layouts alongside all the standard widgets.&lt;/p&gt;
&lt;p&gt;The good news is that Qt Designer supports exactly this through a feature called &lt;strong&gt;widget promotion&lt;/strong&gt;. In this tutorial, you'll learn how to take any custom Python widget and integrate it into your Qt Designer &lt;code&gt;.ui&lt;/code&gt; files, so you can position and size it visually just like any built-in widget.&lt;/p&gt;
&lt;p&gt;The bad news is that since Qt Designer is a C++ application, it can't run your Python code. That means you won't see your custom widget rendered in the Designer preview. Instead, you'll see a placeholder (the base widget type you promoted from). Once you load the &lt;code&gt;.ui&lt;/code&gt; file in your running Python application, your custom widget appears in all its glory.&lt;/p&gt;
&lt;p&gt;With that caveat aside, let's look at how we can use custom widgets in Qt Designer.&lt;/p&gt;
&lt;h2 id="what-is-widget-promotion"&gt;What is Widget Promotion?&lt;/h2&gt;
&lt;p&gt;Widget promotion is Qt Designer's way of letting you swap a standard widget for a custom one. You start by placing a regular widget on your form, a plain &lt;code&gt;QWidget&lt;/code&gt; for example, and then tell Qt Designer: "When this UI is actually used, replace this placeholder with my custom widget class instead."&lt;/p&gt;
&lt;p&gt;Behind the scenes, this adds some extra information to the &lt;code&gt;.ui&lt;/code&gt; file. When you load that file in Python using &lt;code&gt;uic.loadUi()&lt;/code&gt; or compile it with &lt;code&gt;pyuic6&lt;/code&gt;, the loader knows to import your custom class and use it in place of the base widget.&lt;/p&gt;
&lt;h2 id="creating-a-custom-widget"&gt;Creating a Custom Widget&lt;/h2&gt;
&lt;p&gt;Before we get into Qt Designer, let's create a simple custom widget in Python. We'll make a basic colored widget that draws a gradient background&amp;mdash;something you'd never get from a standard widget.&lt;/p&gt;
&lt;p&gt;Create a new file called &lt;code&gt;custom_widgets.py&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;from PyQt6.QtWidgets import QWidget
from PyQt6.QtGui import QPainter, QLinearGradient, QColor
from PyQt6.QtCore import Qt


class GradientWidget(QWidget):
    """A custom widget that displays a gradient background."""

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

    def paintEvent(self, event):
        painter = QPainter(self)
        gradient = QLinearGradient(0, 0, self.width(), self.height())
        gradient.setColorAt(0.0, QColor("#2c3e50"))
        gradient.setColorAt(1.0, QColor("#3498db"))
        painter.fillRect(self.rect(), gradient)
        painter.end()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This widget overrides &lt;code&gt;paintEvent&lt;/code&gt; to draw a diagonal gradient from dark blue to lighter blue. It's a straightforward example, but the same promotion process works for any custom widget&amp;mdash;complex plotting canvases, custom controls, or anything else you build by subclassing a Qt widget.&lt;/p&gt;
&lt;h2 id="setting-up-your-project-structure"&gt;Setting Up Your Project Structure&lt;/h2&gt;
&lt;p&gt;For widget promotion to work, the Python file containing your custom widget needs to be importable when your application runs. The simplest way to achieve this is to keep everything in the same directory:&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;my_project/
&amp;boxvr;&amp;boxh;&amp;boxh; custom_widgets.py      # Your custom widget classes
&amp;boxvr;&amp;boxh;&amp;boxh; mainwindow.ui          # Your Qt Designer file
&amp;boxur;&amp;boxh;&amp;boxh; main.py                # Your application entry point
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The file name and class name matter here&amp;mdash;you'll need to tell Qt Designer both of these during the promotion step.&lt;/p&gt;
&lt;h2 id="promoting-a-widget-in-qt-designer"&gt;Promoting a Widget in Qt Designer&lt;/h2&gt;
&lt;p&gt;Now we can open Qt Designer and set up the promotion.&lt;/p&gt;
&lt;h3&gt;Place a base widget on your form&lt;/h3&gt;
&lt;p&gt;Open Qt Designer and create a new &lt;strong&gt;Main Window&lt;/strong&gt; (or open your existing &lt;code&gt;.ui&lt;/code&gt; file). From the widget box on the left, drag a plain &lt;strong&gt;Widget&lt;/strong&gt; (&lt;code&gt;QWidget&lt;/code&gt;) onto your form. Position and resize it however you like&amp;mdash;this is where your custom widget will appear when the application runs.&lt;/p&gt;
&lt;p&gt;You can use any base widget class as your starting point. If your custom widget subclasses &lt;code&gt;QPushButton&lt;/code&gt;, promote a &lt;code&gt;QPushButton&lt;/code&gt;. If it subclasses &lt;code&gt;QLabel&lt;/code&gt;, promote a &lt;code&gt;QLabel&lt;/code&gt;. For our &lt;code&gt;GradientWidget&lt;/code&gt;, which subclasses &lt;code&gt;QWidget&lt;/code&gt;, a plain &lt;code&gt;QWidget&lt;/code&gt; is the right choice.&lt;/p&gt;
&lt;h3&gt;Open the Promote Widgets dialog&lt;/h3&gt;
&lt;p&gt;Right-click on the widget you just placed. In the context menu, select &lt;strong&gt;Promote to...&lt;/strong&gt;. This opens the &lt;strong&gt;Promoted Widgets&lt;/strong&gt; dialog.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Promote to option in Qt Designer context menu" src="/static/images/qt-designer/promote-to-context-menu.png"/&gt;&lt;/p&gt;
&lt;h3&gt;Fill in the promotion details&lt;/h3&gt;
&lt;p&gt;In the dialog, you'll see fields for three pieces of information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Base class name&lt;/strong&gt; &amp;mdash; This should already be filled in with the type of widget you right-clicked on (e.g., &lt;code&gt;QWidget&lt;/code&gt;). Leave this as is.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Promoted class name&lt;/strong&gt; &amp;mdash; Enter the name of your custom Python class. For our example, type &lt;code&gt;GradientWidget&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Header file&lt;/strong&gt; &amp;mdash; This is where Qt Designer's C++ heritage shows through. In C++, this would be a header file path. For Python, you enter the &lt;strong&gt;module import path&lt;/strong&gt; for your widget, &lt;em&gt;without&lt;/em&gt; the &lt;code&gt;.py&lt;/code&gt; extension. Since our class lives in &lt;code&gt;custom_widgets.py&lt;/code&gt;, type &lt;code&gt;custom_widgets&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Promoted Widgets dialog filled in" src="/static/images/qt-designer/promoted-widgets-dialog.png"/&gt;&lt;/p&gt;
&lt;p&gt;Leave the &lt;strong&gt;Global include&lt;/strong&gt; checkbox unchecked.&lt;/p&gt;
&lt;h3&gt;Add and promote&lt;/h3&gt;
&lt;p&gt;Click &lt;strong&gt;Add&lt;/strong&gt; to add your class to the list of known promoted widgets. Then, with your class selected in the list, click &lt;strong&gt;Promote&lt;/strong&gt;. The dialog closes, and you'll notice the widget's class name in the &lt;strong&gt;Object Inspector&lt;/strong&gt; (top-right panel) now shows &lt;code&gt;GradientWidget&lt;/code&gt; instead of &lt;code&gt;QWidget&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That's it for the Designer side. Save your &lt;code&gt;.ui&lt;/code&gt; file.&lt;/p&gt;
&lt;h3&gt;Promoting additional widgets&lt;/h3&gt;
&lt;p&gt;Once you've added a promoted class through this dialog, it becomes available for reuse. The next time you want to promote a widget to &lt;code&gt;GradientWidget&lt;/code&gt;, just right-click the widget and you'll see it listed directly in the &lt;strong&gt;Promote to&lt;/strong&gt; submenu&amp;mdash;no need to open the full dialog again.&lt;/p&gt;
&lt;h2 id="loading-the-ui-in-python"&gt;Loading the UI in Python&lt;/h2&gt;
&lt;p&gt;Now let's write the Python code to load the &lt;code&gt;.ui&lt;/code&gt; file and see our custom widget in action. Create &lt;code&gt;main.py&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 PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6 import uic


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("mainwindow.ui", self)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, &lt;code&gt;uic.loadUi()&lt;/code&gt; reads the &lt;code&gt;.ui&lt;/code&gt; file and sees that one of the widgets has been promoted to &lt;code&gt;GradientWidget&lt;/code&gt; from the &lt;code&gt;custom_widgets&lt;/code&gt; module. It automatically does the equivalent of:&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 custom_widgets import GradientWidget
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;...and creates an instance of &lt;code&gt;GradientWidget&lt;/code&gt; wherever you placed that promoted widget in your layout. Instead of a blank &lt;code&gt;QWidget&lt;/code&gt;, you'll see your gradient background.&lt;/p&gt;
&lt;h2 id="using-compiled-ui-files"&gt;Using Compiled UI Files&lt;/h2&gt;
&lt;p&gt;If you prefer to compile your &lt;code&gt;.ui&lt;/code&gt; files to Python using &lt;code&gt;pyuic6&lt;/code&gt; rather than loading them at runtime, promotion works the same way. Run:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;pyuic6 mainwindow.ui -o ui_mainwindow.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you open the generated &lt;code&gt;ui_mainwindow.py&lt;/code&gt;, you'll find an import line near the bottom:&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 custom_widgets import GradientWidget
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The compiled code creates your &lt;code&gt;GradientWidget&lt;/code&gt; instance in the right place automatically. You can then use the generated file in your application:&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
from ui_mainwindow import Ui_MainWindow


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Both approaches&amp;mdash;runtime loading and compiled files&amp;mdash;handle promoted widgets in the same way.&lt;/p&gt;
&lt;h2 id="a-more-practical-example-embedding-pyqtgraph"&gt;A More Practical Example: Embedding PyQtGraph&lt;/h2&gt;
&lt;p&gt;One of the most common reasons to promote widgets is to embed third-party plotting libraries like &lt;a href="https://www.pyqtgraph.org/"&gt;PyQtGraph&lt;/a&gt; into your Designer layouts. PyQtGraph's &lt;code&gt;PlotWidget&lt;/code&gt; is a subclass of &lt;code&gt;QGraphicsView&lt;/code&gt;, so you'd promote a &lt;code&gt;QGraphicsView&lt;/code&gt; in Designer.&lt;/p&gt;
&lt;p&gt;Here's how you'd fill in the promotion dialog for PyQtGraph:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Base class name&lt;/strong&gt;: &lt;code&gt;QGraphicsView&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promoted class name&lt;/strong&gt;: &lt;code&gt;PlotWidget&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header file&lt;/strong&gt;: &lt;code&gt;pyqtgraph&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's all it takes. When your application runs, the placeholder &lt;code&gt;QGraphicsView&lt;/code&gt; becomes a fully functional &lt;code&gt;PlotWidget&lt;/code&gt; that you can plot data on.&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
from PyQt6 import uic


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("mainwindow.ui", self)

        # self.graphWidget is the promoted PlotWidget
        # (use the objectName you set in Designer)
        self.graphWidget.plot([1, 2, 3, 4, 5], [10, 20, 15, 30, 25])


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="promoting-widgets-from-submodules"&gt;Promoting Widgets from Submodules&lt;/h2&gt;
&lt;p&gt;If your custom widget lives in a submodule or package, you can use dotted import paths in the &lt;strong&gt;Header file&lt;/strong&gt; field. For example, if your project structure looks like this:&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;my_project/
&amp;boxvr;&amp;boxh;&amp;boxh; widgets/
&amp;boxv;   &amp;boxvr;&amp;boxh;&amp;boxh; __init__.py
&amp;boxv;   &amp;boxur;&amp;boxh;&amp;boxh; gradient.py    # contains GradientWidget
&amp;boxvr;&amp;boxh;&amp;boxh; mainwindow.ui
&amp;boxur;&amp;boxh;&amp;boxh; main.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You would enter &lt;code&gt;widgets.gradient&lt;/code&gt; as the header file in the promotion dialog. The loader will then do:&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 widgets.gradient import GradientWidget
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This keeps things organized as your project grows.&lt;/p&gt;
&lt;h2 id="troubleshooting-common-issues"&gt;Troubleshooting Common Issues&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;"No module named 'custom_widgets'"&lt;/strong&gt; &amp;mdash; This means Python can't find the file containing your custom widget class. Make sure the module file is in the same directory as your script (or somewhere on your Python path), and that the name in the promotion dialog matches the file name exactly (without &lt;code&gt;.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The widget appears blank or as a plain QWidget&lt;/strong&gt; &amp;mdash; Double-check that the promoted class name matches your Python class name exactly, including capitalization. &lt;code&gt;GradientWidget&lt;/code&gt; and &lt;code&gt;gradientwidget&lt;/code&gt; are different classes as far as Python is concerned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The widget doesn't resize properly&lt;/strong&gt; &amp;mdash; Make sure you've added the promoted widget to a layout in Qt Designer. Widgets outside of layouts won't resize with the window, regardless of whether they're promoted or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Changes to your custom widget don't appear in Designer&lt;/strong&gt; &amp;mdash; Remember, Qt Designer can't render Python widgets. You'll always see the base widget type in the Designer preview. Run your application to see your custom widget.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Widget promotion is a straightforward way to bridge the gap between Qt Designer's visual layout tools and your custom Python widgets. The process is always the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Place a base widget of the appropriate type in Qt Designer.&lt;/li&gt;
&lt;li&gt;Right-click and promote it, specifying your custom class name and module path.&lt;/li&gt;
&lt;li&gt;Save the &lt;code&gt;.ui&lt;/code&gt; file and load it in your Python application.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your custom widget won't be visible in the Designer preview&amp;mdash;that's expected. But when your application runs, the promoted widget is swapped in seamlessly, giving you the best of both worlds: visual layout design with the full power of custom Python widgets.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qtdesigner"/><category term="custom-widgets"/><category term="widget-promotion"/><category term="python"/><category term="qt"/><category term="qt6"/><category term="pyqt6-custom-widgets"/></entry><entry><title>Sorting and Filtering a QTableView with QSortFilterProxyModel — Learn how to add interactive sorting and filtering to your PyQt/PySide table views without touching your underlying data</title><link href="https://www.pythonguis.com/faq/add-some-explanation-on-sorting-a-qtableview/" rel="alternate"/><published>2026-05-06T06:00:00+00:00</published><updated>2026-05-06T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-05-06:/faq/add-some-explanation-on-sorting-a-qtableview/</id><summary type="html">I'm using QTableView to show some data, which works well. But I would like to be able to sort the data by different columns. How can I do this without sorting the data manually?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I'm using QTableView to show some data, which works well. But I would like to be able to sort the data by different columns. How can I do this without sorting the data manually?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've already built a &lt;code&gt;QTableView&lt;/code&gt; with a custom model, you might be wondering how to let users sort columns by clicking headers or filter rows based on search input. The good news is that Qt provides a ready-made tool for this: &lt;code&gt;QSortFilterProxyModel&lt;/code&gt;. It sits between your model and your view, rearranging and filtering the data &lt;em&gt;without modifying the original source&lt;/em&gt;. Your data stays untouched &amp;mdash; the proxy just changes how it's presented.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll start with a simple table model and progressively add sorting, filtering, and then tackle some of the common pitfalls &amp;mdash; like working with proxy indexes correctly and avoiding crashes when updating data.&lt;/p&gt;
&lt;h2 id="a-simple-table-model"&gt;A Simple Table Model&lt;/h2&gt;
&lt;p&gt;Let's begin with a basic &lt;code&gt;QTableView&lt;/code&gt; displaying a list-of-lists. This is the same pattern used in the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-architecture/"&gt;model/view architecture tutorial&lt;/a&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 PyQt6.QtCore import Qt, QAbstractTableModel
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._headers = ["Name", "Age", "City"]

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def headerData(self, section, orientation, role):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = QTableView()

        data = [
            ["Alice", 25, "New York"],
            ["Bob", 30, "Denver"],
            ["Charlie", 35, "Austin"],
            ["Diana", 28, "Denver"],
            ["Eve", 22, "Austin"],
        ]

        self.model = TableModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)
        self.setWindowTitle("QTableView &amp;mdash; No Sorting Yet")
        self.resize(500, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a plain table. Clicking the column headers does nothing as sorting is off by default. Let's change that first.&lt;/p&gt;
&lt;h2 id="adding-sorting-with-qsortfilterproxymodel"&gt;Adding sorting with QSortFilterProxyModel&lt;/h2&gt;
&lt;p&gt;To add sorting, we insert a &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; between our &lt;code&gt;TableModel&lt;/code&gt; and the &lt;code&gt;QTableView&lt;/code&gt;. The proxy model wraps the source model and provides sorted (and later, filtered) access to the same data.&lt;/p&gt;
&lt;p&gt;Here's what changes in the &lt;code&gt;MainWindow.__init__&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;from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.table = QTableView()

        data = [
            ["Alice", 25, "New York"],
            ["Bob", 30, "Denver"],
            ["Charlie", 35, "Austin"],
            ["Diana", 28, "Denver"],
            ["Eve", 22, "Austin"],
        ]

        self.model = TableModel(data)

        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setSourceModel(self.model)

        self.table.setModel(self.proxy_model)
        self.table.setSortingEnabled(True)

        self.setCentralWidget(self.table)
        self.setWindowTitle("QTableView &amp;mdash; Sortable!")
        self.resize(500, 300)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've added the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the &lt;code&gt;QSortFilterProxyModel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tell it which source model to wrap with &lt;code&gt;setSourceModel()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give the &lt;em&gt;proxy&lt;/em&gt; model to the view (not the source model), and call &lt;code&gt;setSortingEnabled(True)&lt;/code&gt; on the view.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now when you click a column header, the rows reorder. Click again to reverse the sort direction. The little arrow indicator on the header shows you which column is currently sorted and in which direction.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sortable QTableView with proxy model &amp;mdash; clicking headers sorts the data." src="sortable-table.png"/&gt;&lt;/p&gt;
&lt;p&gt;Notice that the underlying &lt;code&gt;data&lt;/code&gt; list hasn't changed at all. The proxy model handles everything by remapping indexes.&lt;/p&gt;
&lt;h2 id="adding-filtering"&gt;Adding filtering&lt;/h2&gt;
&lt;p&gt;Filtering works through the same proxy model. You tell the proxy which column to look at and what pattern to match, and it hides rows that don't match.&lt;/p&gt;
&lt;p&gt;Let's add a &lt;code&gt;QLineEdit&lt;/code&gt; that filters rows as you type. We'll filter on the "City" column (column index 2). If you're interested in a more complete search bar implementation, see the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-widget-search-bar/"&gt;widget search bar tutorial&lt;/a&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 PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTableView,
    QVBoxLayout, QWidget, QLineEdit, QLabel,
)


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._headers = ["Name", "Age", "City"]

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def headerData(self, section, orientation, role):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        data = [
            ["Alice", 25, "New York"],
            ["Bob", 30, "Denver"],
            ["Charlie", 35, "Austin"],
            ["Diana", 28, "Denver"],
            ["Eve", 22, "Austin"],
        ]

        self.model = TableModel(data)

        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setSourceModel(self.model)
        self.proxy_model.setFilterCaseSensitivity(
            Qt.CaseSensitivity.CaseInsensitive
        )
        self.proxy_model.setFilterKeyColumn(2)  # Filter on "City" column

        self.table = QTableView()
        self.table.setModel(self.proxy_model)
        self.table.setSortingEnabled(True)

        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("Filter by city...")
        self.search_input.textChanged.connect(
            self.proxy_model.setFilterFixedString
        )

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Search:"))
        layout.addWidget(self.search_input)
        layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)
        self.setWindowTitle("QTableView &amp;mdash; Sort &amp;amp; Filter")
        self.resize(500, 400)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Type "Denver" into the search box and the table instantly filters to show only the rows where the City column matches. Type "aus" and you'll see the Austin rows (because we set case-insensitive matching).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Filtering the table by typing in the search box &amp;mdash; only matching rows are shown." src="filter-table.png"/&gt;&lt;/p&gt;
&lt;p&gt;Let's recap what's happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setFilterKeyColumn(2)&lt;/code&gt; tells the proxy to check column 2 ("City") when deciding which rows to show.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setFilterFixedString&lt;/code&gt; performs a plain substring match. If the filter string appears anywhere in the cell value, the row is shown.&lt;/li&gt;
&lt;li&gt;We connected the &lt;code&gt;textChanged&lt;/code&gt; signal from the &lt;code&gt;QLineEdit&lt;/code&gt; directly to &lt;code&gt;setFilterFixedString&lt;/code&gt; on the proxy model. Every time the user types, the filter updates automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Filtering across all columns&lt;/h3&gt;
&lt;p&gt;If you want to search across every column instead of just one, set the filter key column to &lt;code&gt;-1&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;self.proxy_model.setFilterKeyColumn(-1)  # Search all columns
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now typing "25" will match Alice's row (age 25), and typing "Den" will still match the Denver rows.&lt;/p&gt;
&lt;h3&gt;Other filter modes&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setFilterFixedString&lt;/code&gt; is the simplest option &amp;mdash; it does a plain text substring match. The proxy model also supports more powerful matching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wildcard matching&lt;/strong&gt; using &lt;code&gt;setFilterWildcard()&lt;/code&gt; &amp;mdash; supports &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; patterns, like &lt;code&gt;"D*ver"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regular expression matching&lt;/strong&gt; using &lt;code&gt;setFilterRegularExpression()&lt;/code&gt; &amp;mdash; supports full regex patterns for complex matching needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For most interactive search boxes, &lt;code&gt;setFilterFixedString&lt;/code&gt; with case-insensitive matching is exactly what you need.&lt;/p&gt;
&lt;h2 id="working-with-proxy-indexes-correctly"&gt;Working with proxy indexes correctly&lt;/h2&gt;
&lt;p&gt;When a proxy model is active, the indexes your view reports are &lt;em&gt;proxy&lt;/em&gt; indexes, not source model indexes. If you click on a row in the filtered/sorted view, the &lt;code&gt;QModelIndex&lt;/code&gt; you receive refers to the &lt;em&gt;proxy model's&lt;/em&gt; row numbering, which may not match the original data.&lt;/p&gt;
&lt;p&gt;This matters when you need to do something with the underlying data &amp;mdash; like reading values from the source model or modifying a specific row.&lt;/p&gt;
&lt;p&gt;Consider this slot connected to the table's &lt;code&gt;clicked&lt;/code&gt; signal:&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.table.clicked.connect(self.cell_clicked)

def cell_clicked(self, proxy_index):
    # This gives the row number in the proxy (filtered/sorted) view
    print(f"Proxy row: {proxy_index.row()}, column: {proxy_index.column()}")

    # To get the corresponding row in the SOURCE model:
    source_index = self.proxy_model.mapToSource(proxy_index)
    print(f"Source row: {source_index.row()}, column: {source_index.column()}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The method &lt;code&gt;mapToSource()&lt;/code&gt; translates a proxy index back to the source model's coordinate system. There's also &lt;code&gt;mapFromSource()&lt;/code&gt; for going the other direction &amp;mdash; converting a source index into the proxy's index, which is useful if you need to select or highlight a specific row in the view programmatically.&lt;/p&gt;
&lt;p&gt;If you forget this step and pass proxy indexes directly to your source model, you'll end up reading or modifying the wrong row. When filtering is active, the mismatch becomes obvious because the proxy's row 0 might correspond to row 3 in the source.&lt;/p&gt;
&lt;h3&gt;Reading data through the proxy&lt;/h3&gt;
&lt;p&gt;When you want to read data from a clicked row, you have two options:&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 cell_clicked(self, proxy_index):
    row = proxy_index.row()

    # Option 1: Read through the proxy model (uses proxy indexes)
    name = self.proxy_model.data(
        self.proxy_model.index(row, 0),
        Qt.ItemDataRole.DisplayRole,
    )

    # Option 2: Map to source and read from source model
    source_index = self.proxy_model.mapToSource(proxy_index)
    name = self.model.data(
        self.model.index(source_index.row(), 0),
        Qt.ItemDataRole.DisplayRole,
    )

    print(f"Clicked on: {name}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Both options give you the same result. Option 1 is often simpler since you're already working with proxy indexes from the view. Option 2 is necessary when you need to interact directly with the source model &amp;mdash; for example, to modify data or get the "real" row position in your data structure.&lt;/p&gt;
&lt;h2 id="avoiding-crashes-when-updating-the-source-model"&gt;Avoiding crashes when updating the source model&lt;/h2&gt;
&lt;p&gt;If you're updating the source model's data while a proxy model and view are connected, you need to properly notify Qt's model/view framework about the changes. Without these notifications, you can get segmentation faults or corrupted displays.&lt;/p&gt;
&lt;p&gt;The pattern for updating data looks like this inside your &lt;code&gt;QAbstractTableModel&lt;/code&gt; subclass:&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_data(self, new_data):
    self.layoutAboutToBeChanged.emit()
    self._data = new_data
    self.layoutChanged.emit()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;layoutAboutToBeChanged&lt;/code&gt; signal tells the proxy model (and the view) that the structure of the data is about to change. After you've made your changes, &lt;code&gt;layoutChanged&lt;/code&gt; tells everything to refresh. Skipping the &lt;code&gt;layoutAboutToBeChanged&lt;/code&gt; signal is a common cause of crashes &amp;mdash; the proxy model needs that heads-up to properly invalidate its internal mapping of indexes.&lt;/p&gt;
&lt;p&gt;For smaller changes, like updating a single cell, you can use &lt;code&gt;dataChanged&lt;/code&gt; instead:&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 set_value(self, row, col, value):
    self._data[row][col] = value
    index = self.index(row, col)
    self.dataChanged.emit(index, index)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And if you're adding or removing rows, use &lt;code&gt;beginInsertRows&lt;/code&gt;/&lt;code&gt;endInsertRows&lt;/code&gt; or &lt;code&gt;beginRemoveRows&lt;/code&gt;/&lt;code&gt;endRemoveRows&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;def add_row(self, row_data):
    row_position = len(self._data)
    self.beginInsertRows(self.index(row_position, 0).parent(), row_position, row_position)
    self._data.append(row_data)
    self.endInsertRows()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Getting these signals right is what keeps the proxy model, the view, and your data all in sync.&lt;/p&gt;
&lt;h2 id="custom-sorting-behavior"&gt;Custom sorting behavior&lt;/h2&gt;
&lt;p&gt;The default sorting provided by &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; uses &lt;code&gt;Qt.ItemDataRole.DisplayRole&lt;/code&gt; and works well for simple string and number comparisons. But sometimes you need more control &amp;mdash; for example, sorting a column of dates that are stored as strings, or sorting with a custom priority order.&lt;/p&gt;
&lt;p&gt;To customize sorting, subclass &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; and override the &lt;code&gt;lessThan&lt;/code&gt; method. This method receives two &lt;code&gt;QModelIndex&lt;/code&gt; objects (from the source model) and should return &lt;code&gt;True&lt;/code&gt; if the left value should come before the right value.&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 CustomProxyModel(QSortFilterProxyModel):
    def lessThan(self, left, right):
        left_data = self.sourceModel().data(left, Qt.ItemDataRole.DisplayRole)
        right_data = self.sourceModel().data(right, Qt.ItemDataRole.DisplayRole)

        # Example: sort numerically if both values are numbers
        try:
            return float(left_data) &amp;lt; float(right_data)
        except (ValueError, TypeError):
            # Fall back to string comparison
            return str(left_data).lower() &amp;lt; str(right_data).lower()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then use &lt;code&gt;CustomProxyModel&lt;/code&gt; instead of &lt;code&gt;QSortFilterProxyModel&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;self.proxy_model = CustomProxyModel()
self.proxy_model.setSourceModel(self.model)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is useful when your table has mixed data types or when the display representation doesn't sort the way you'd expect (like date strings in "MM/DD/YYYY" format).&lt;/p&gt;
&lt;h2 id="custom-filtering-behavior"&gt;Custom filtering behavior&lt;/h2&gt;
&lt;p&gt;Similarly, you can customize filtering by subclassing &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; and overriding &lt;code&gt;filterAcceptsRow&lt;/code&gt;. This method is called for every row in the source model, and it should return &lt;code&gt;True&lt;/code&gt; if the row should be visible.&lt;/p&gt;
&lt;p&gt;Here's an example that filters to show only rows where the "Age" column (column 1) is above a certain threshold:&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 AgeFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()
        self._min_age = 0

    def set_min_age(self, age):
        self._min_age = age
        self.invalidateFilter()  # Re-apply the filter

    def filterAcceptsRow(self, source_row, source_parent):
        index = self.sourceModel().index(source_row, 1, source_parent)
        age = self.sourceModel().data(index, Qt.ItemDataRole.DisplayRole)
        try:
            return int(age) &amp;gt;= self._min_age
        except (ValueError, TypeError):
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After changing your filter criteria, call &lt;code&gt;invalidateFilter()&lt;/code&gt; to tell the proxy model to re-evaluate which rows should be shown.&lt;/p&gt;
&lt;p&gt;You can combine this with the text-based column filtering too. If you override &lt;code&gt;filterAcceptsRow&lt;/code&gt;, you have full control &amp;mdash; you can check multiple columns, apply complex logic, or combine several filter conditions:&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 filterAcceptsRow(self, source_row, source_parent):
    model = self.sourceModel()

    # Check age filter
    age_index = model.index(source_row, 1, source_parent)
    age = model.data(age_index, Qt.ItemDataRole.DisplayRole)
    if int(age) &amp;lt; self._min_age:
        return False

    # Check text filter on city column
    city_index = model.index(source_row, 2, source_parent)
    city = model.data(city_index, Qt.ItemDataRole.DisplayRole)
    if self.filterRegularExpression().pattern():
        if not self.filterRegularExpression().match(city).hasMatch():
            return False

    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="putting-it-all-together"&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Here's a complete example that combines sorting, text filtering, and a numeric age filter using a custom proxy 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 PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTableView,
    QVBoxLayout, QHBoxLayout, QWidget,
    QLineEdit, QLabel, QSpinBox,
)


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._headers = ["Name", "Age", "City"]

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def headerData(self, section, orientation, role):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section]


class CustomFilterProxyModel(QSortFilterProxyModel):
    def __init__(self):
        super().__init__()
        self._min_age = 0
        self._city_filter = ""

    def set_min_age(self, age):
        self._min_age = age
        self.invalidateFilter()

    def set_city_filter(self, text):
        self._city_filter = text.lower()
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        model = self.sourceModel()

        # Age filter (column 1)
        age_index = model.index(source_row, 1, source_parent)
        age = model.data(age_index, Qt.ItemDataRole.DisplayRole)
        try:
            if int(age) &amp;lt; self._min_age:
                return False
        except (ValueError, TypeError):
            pass

        # City text filter (column 2)
        if self._city_filter:
            city_index = model.index(source_row, 2, source_parent)
            city = model.data(city_index, Qt.ItemDataRole.DisplayRole)
            if self._city_filter not in str(city).lower():
                return False

        return True


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        data = [
            ["Alice", 25, "New York"],
            ["Bob", 30, "Denver"],
            ["Charlie", 35, "Austin"],
            ["Diana", 28, "Denver"],
            ["Eve", 22, "Austin"],
            ["Frank", 40, "New York"],
            ["Grace", 19, "Denver"],
        ]

        self.model = TableModel(data)

        self.proxy_model = CustomFilterProxyModel()
        self.proxy_model.setSourceModel(self.model)

        self.table = QTableView()
        self.table.setModel(self.proxy_model)
        self.table.setSortingEnabled(True)
        self.table.clicked.connect(self.cell_clicked)

        # City filter
        self.city_input = QLineEdit()
        self.city_input.setPlaceholderText("Filter by city...")
        self.city_input.textChanged.connect(self.proxy_model.set_city_filter)

        # Age filter
        self.age_spin = QSpinBox()
        self.age_spin.setRange(0, 100)
        self.age_spin.setPrefix("Min age: ")
        self.age_spin.valueChanged.connect(self.proxy_model.set_min_age)

        filter_layout = QHBoxLayout()
        filter_layout.addWidget(QLabel("City:"))
        filter_layout.addWidget(self.city_input)
        filter_layout.addWidget(self.age_spin)

        layout = QVBoxLayout()
        layout.addLayout(filter_layout)
        layout.addWidget(self.table)

        self.status_label = QLabel("Click a row to see details")
        layout.addWidget(self.status_label)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)
        self.setWindowTitle("QTableView &amp;mdash; Sort &amp;amp; Custom Filter")
        self.resize(500, 400)

    def cell_clicked(self, proxy_index):
        source_index = self.proxy_model.mapToSource(proxy_index)
        row = proxy_index.row()
        name = self.proxy_model.data(
            self.proxy_model.index(row, 0),
            Qt.ItemDataRole.DisplayRole,
        )
        age = self.proxy_model.data(
            self.proxy_model.index(row, 1),
            Qt.ItemDataRole.DisplayRole,
        )
        city = self.proxy_model.data(
            self.proxy_model.index(row, 2),
            Qt.ItemDataRole.DisplayRole,
        )
        self.status_label.setText(
            f"Selected: {name}, age {age}, from {city} "
            f"(source row: {source_index.row()})"
        )


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Complete example with sorting and dual filters &amp;mdash; city text search and minimum age." src="sort-filter-complete.png"/&gt;&lt;/p&gt;
&lt;p&gt;Try playing with this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click column headers to sort by name, age, or city.&lt;/li&gt;
&lt;li&gt;Type in the city filter to narrow down results.&lt;/li&gt;
&lt;li&gt;Adjust the minimum age spinner to hide younger entries.&lt;/li&gt;
&lt;li&gt;Click on rows and notice how the source row number differs from the visible row number.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Here's a quick recap of everything we covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;QSortFilterProxyModel&lt;/code&gt;&lt;/strong&gt; sits between your model and your view. It sorts and filters data without modifying the source.&lt;/li&gt;
&lt;li&gt;Call &lt;strong&gt;&lt;code&gt;setSortingEnabled(True)&lt;/code&gt;&lt;/strong&gt; on the view to let users sort by clicking column headers.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;code&gt;setFilterKeyColumn()&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;setFilterFixedString()&lt;/code&gt;&lt;/strong&gt; for quick text filtering. Set the column to &lt;code&gt;-1&lt;/code&gt; to search all columns.&lt;/li&gt;
&lt;li&gt;Always use &lt;strong&gt;&lt;code&gt;mapToSource()&lt;/code&gt;&lt;/strong&gt; when you need to convert a proxy index back to the source model's coordinate system.&lt;/li&gt;
&lt;li&gt;Emit &lt;strong&gt;&lt;code&gt;layoutAboutToBeChanged&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;layoutChanged&lt;/code&gt;&lt;/strong&gt; when replacing data in the source model to avoid crashes.&lt;/li&gt;
&lt;li&gt;Subclass &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; and override &lt;strong&gt;&lt;code&gt;lessThan()&lt;/code&gt;&lt;/strong&gt; for custom sorting or &lt;strong&gt;&lt;code&gt;filterAcceptsRow()&lt;/code&gt;&lt;/strong&gt; for custom filtering.&lt;/li&gt;
&lt;li&gt;Call &lt;strong&gt;&lt;code&gt;invalidateFilter()&lt;/code&gt;&lt;/strong&gt; after changing filter criteria in a custom proxy model.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The proxy model pattern is one of the most powerful parts of Qt's model/view architecture. Once you have it set up, you get flexible data presentation without ever duplicating or restructuring your underlying data. To explore model/view further, see how to &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qtableview-modelviews-numpy-pandas/"&gt;display numpy and pandas data in a QTableView&lt;/a&gt; or learn about &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals, slots, and events&lt;/a&gt; that power these interactions.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="PyQt5"/><category term="PyQt6"/><category term="PySide2"/><category term="PySide6"/><category term="QTableView"/><category term="QSortFilterProxyModel"/><category term="Model-View"/></entry><entry><title>Streamlit Buttons — Making things happen with Streamlit buttons</title><link href="https://www.pythonguis.com/tutorials/streamlit-buttons/" rel="alternate"/><published>2026-05-01T06:00:00+00:00</published><updated>2026-05-01T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-05-01:/tutorials/streamlit-buttons/</id><summary type="html">Streamlit is a popular choice for creating interactive web applications in Python. With its simple syntax and intuitive interface, developers can quickly create visually appealing dashboards.</summary><content type="html">&lt;p&gt;Streamlit is a popular choice for creating interactive web applications in Python. With its simple syntax and intuitive interface, developers can quickly create visually appealing dashboards.&lt;/p&gt;
&lt;p&gt;One of the great things about Streamlit is its ability to easily handle user interaction, and dynamically update the UI in response. One of the main way for users to trigger actions in UIs is through the use of buttons. In Streamlit, the &lt;code&gt;st.button()&lt;/code&gt; method creates a button that users can click to perform an action. Each button can be associated with a different action.&lt;/p&gt;
&lt;p&gt;In this tutorial we'll look at how you can use buttons to add interactivity to your Streamlit apps.&lt;/p&gt;
&lt;h2 id="creating-buttons-in-streamlit"&gt;Creating Buttons in Streamlit&lt;/h2&gt;
&lt;p&gt;To create a button in Streamlit, you use the &lt;code&gt;st.button()&lt;/code&gt; function, which takes an optional label as an argument. When the button is clicked, it returns &lt;code&gt;True&lt;/code&gt;, which you can use to control subsequent actions.&lt;/p&gt;
&lt;h3&gt;Basic Button Syntax&lt;/h3&gt;
&lt;p&gt;Here's a simple example of a button in Streamlit:&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 streamlit as st

if st.button('Click Me'):
    st.write("Button clicked!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Simple Streamlit app with a single button" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-buttons/streamlit-button.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button.png?tr=w-600 600w" loading="lazy" width="1372" height="786"/&gt;
&lt;em&gt;Simple Streamlit app with a single button&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.button('Click Me')&lt;/code&gt; creates a button labeled &lt;em&gt;Click Me&lt;/em&gt;. When the button is clicked, it returns &lt;code&gt;True&lt;/code&gt; and the &lt;code&gt;if&lt;/code&gt; evaluates to &lt;em&gt;true&lt;/em&gt; running the nested code underneath -- displaying the message "Button clicked!"&lt;/p&gt;
&lt;p&gt;This basic structure is the foundation of working with buttons in Streamlit. Through this simple mechanism you can build quite complex interactivity.&lt;/p&gt;
&lt;h2 id="multiple-buttons-for-different-actions"&gt;Multiple Buttons for Different Actions&lt;/h2&gt;
&lt;p&gt;Building on the basic button structure, you can create multiple buttons within your Streamlit app, each associated with different actions. For instance, let's create buttons that display different messages based on which is clicked.&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 streamlit as st

if st.button('Show Greeting'):
    st.write("Hello, welcome to the app!")

if st.button('Show Goodbye'):
    st.write("Goodbye! See you soon.")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Simple Streamlit app with two buttons" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-buttons/streamlit-two-buttons.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-two-buttons.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-two-buttons.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-two-buttons.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-two-buttons.png?tr=w-600 600w" loading="lazy" width="1325" height="1008"/&gt;
&lt;em&gt;Simple Streamlit app with two buttons&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Each button is wrapped in a conditional statement. When a button is pressed, the corresponding action is executed. Depending on the button pressed, different messages are displayed, providing immediate feedback to the user.&lt;/p&gt;
&lt;p&gt;This structure is versatile and can be expanded to include more buttons and actions.&lt;/p&gt;
&lt;h2 id="displaying-dynamic-content-based-on-button-clicks"&gt;Displaying Dynamic Content Based on Button Clicks&lt;/h2&gt;
&lt;p&gt;Buttons can be used to display all types of content dynamically, including text, images, and charts.  For example, below is a similar example but displaying images.&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 streamlit as st

img_url_1 = "https://placehold.co/150/FF0000"
img_url_2 = "https://placehold.co/150/8ACE00"

if st.button('Show Red Image'):
    st.image(img_url_1, caption="This is a red image")

if st.button('Show Green Image'):
    st.image(img_url_2, caption="This is a green image")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Simple Streamlit app with two buttons showing images" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-buttons/streamlit-button-images.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button-images.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button-images.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button-images.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-button-images.png?tr=w-600 600w" loading="lazy" width="955" height="819"/&gt;
&lt;em&gt;Simple Streamlit app with two buttons showing images&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When the &lt;em&gt;Show Red Image&lt;/em&gt; button is pressed, a red image is displayed. The same goes for the &lt;em&gt;Show Green Image&lt;/em&gt; button. This setup allows users to switch between different images based on their preferences.&lt;/p&gt;
&lt;p&gt;Note that the state isn't persisted between each interaction. When you click on the "Show Red Image" the green image will disappear, and vice versa. This isn't a &lt;em&gt;toggle&lt;/em&gt; but a natural consequence of how Streamlit works: the code of the script is executed on each interaction, so only one button can be in a "clicked" state at any time.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  To persist state between runs of the script, you can use Streamlit's state management features. We'll cover this in a future tutorial.&lt;/p&gt;
&lt;h2 id="dynamic-forms-based-on-button-press"&gt;Dynamic Forms Based on Button Press&lt;/h2&gt;
&lt;p&gt;Dynamic forms allow users to provide input in a structured way, which can vary based on user actions. This is particularly useful for collecting information without overwhelming users with multiple fields.&lt;/p&gt;
&lt;p&gt;Here's a quick example where users can input their name and age based on button presses:&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 streamlit as st

# Title
st.title("Dynamic Forms Based on Button Press")

# Name Input Field
if st.button('Enter Name'):
    name = st.text_input('What is your name?')
    if name:
        st.write(f"Hello, {name}\!")

# Age Input Field

if st.button('Enter Age'):
    age = st.number_input('What is your age?', min_value=1, max_value=120)
    if age:
        st.write(f"Your age is {age}.")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The button &lt;code&gt;Enter Name&lt;/code&gt; triggers a text input field when clicked, allowing users to enter their names. The button &lt;code&gt;Enter Age&lt;/code&gt; displays a number input field for users to enter their age. The app provides immediate feedback based on user input.&lt;/p&gt;
&lt;h3&gt;Handling Form Submission&lt;/h3&gt;
&lt;p&gt;For more complex collections of inputs that you want to work together, consider using &lt;code&gt;st.form()&lt;/code&gt; to group inputs, allowing users to submit all inputs at once:&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 streamlit as st

# Title
st.title("Dynamic Forms Based on Button Press")

with st.form("my_form"):
    name = st.text_input('What is your name?')
    age = st.number_input('What is your age?', min_value=1, max_value=120)
    submitted = st.form_submit_button("Submit")

    if submitted:
        st.write(f"Hello, {name}\! Your age is {age}.")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit form with submit button" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-buttons/streamlit-form-submit.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-form-submit.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-form-submit.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-form-submit.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-buttons/streamlit-form-submit.png?tr=w-600 600w" loading="lazy" width="1100" height="477"/&gt;
&lt;em&gt;Streamlit form with submit button&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, we explored how to make things happen in Streamlit using buttons. We learned how to create multiple buttons and display dynamic content based on user interaction.&lt;/p&gt;
&lt;p&gt;Now that you have a basic understanding of buttons in Streamlit, you can add basic interaction to your Streamlit applications.&lt;/p&gt;</content><category term="streamlit"/><category term="foundation"/><category term="buttons"/><category term="streamlit-foundation"/></entry><entry><title>Actions in one thread changing data in another — How to communicate between threads and windows in PyQt6</title><link href="https://www.pythonguis.com/faq/actions-in-one-thread-changing-data-in-another/" rel="alternate"/><published>2026-04-29T06:00:00+00:00</published><updated>2026-04-29T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-04-29:/faq/actions-in-one-thread-changing-data-in-another/</id><summary type="html">I have a main window that starts background threads (e.g., handling GPIO data). From the main window I open secondary windows using buttons. When I press a button in a secondary window, I can't change anything in the background threads. But if I press a button in the main window, everything works. How do I communicate between a secondary window and a thread that was started from the main window?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I have a main window that starts background threads (e.g., handling GPIO data). From the main window I open secondary windows using buttons. When I press a button in a secondary window, I can't change anything in the background threads. But if I press a button in the main window, everything works. How do I communicate between a secondary window and a thread that was started from the main window?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a common problem when building PyQt6 applications with multiple windows and background threads. The good news is that Qt's signal and slot system is designed to handle this and it works safely across threads.&lt;/p&gt;
&lt;p&gt;The core idea is that your secondary window doesn't need direct access to the thread or the worker object. Instead the secondary window and the worker just need access to the same signals, and can then use them to communicate with one another. Qt handles the cross-thread communication automatically.&lt;/p&gt;
&lt;h2 id="why-doesnt-direct-access-work"&gt;Why doesn't direct access work?&lt;/h2&gt;
&lt;p&gt;When you create a background thread from the main window, you'll often store a reference to that thread on the main window. If that main window then creates a sub-window, it doesn't have any access to the objects on it's parent. Even if it &lt;em&gt;did&lt;/em&gt; calling the methods on the thread directly is not usually the right approach.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You &lt;em&gt;can&lt;/em&gt; access the attributes of a parent window using &lt;code&gt;.parent()&lt;/code&gt; but this is a bad habit, because it tightly couples the parts of your application together. If you modify the structure of the parent window, you now need to also edit the sub-window. There are better ways that keep things nicely isolated.&lt;/p&gt;
&lt;p&gt;The solution is to avoid calling methods directly across threads. Instead, use &lt;strong&gt;&lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt;&lt;/strong&gt;. When a signal is emitted in one thread and connected to a slot in another, Qt automatically queues the call and delivers it safely.&lt;/p&gt;
&lt;h2 id="setting-up-a-background-worker"&gt;Setting up a background worker&lt;/h2&gt;
&lt;p&gt;First, let's create a simple worker class that runs in a background thread. This worker simulates handling incoming data (like GPIO data) and also accepts commands from the GUI.&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.QtCore import QObject, pyqtSignal, pyqtSlot
import time


class Worker(QObject):
    """A worker that runs in a background thread."""
    data_updated = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.running = True
        self.current_value = 0

    @pyqtSlot()
    def run(self):
        """Simulate continuous data handling."""
        while self.running:
            self.current_value += 1
            self.data_updated.emit(f"Data: {self.current_value}")
            time.sleep(1)

    @pyqtSlot(int)
    def set_value(self, value):
        """Receive a new value from the GUI."""
        self.current_value = value
        self.data_updated.emit(f"Value set to: {self.current_value}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;set_value&lt;/code&gt; slot is what we'll trigger from the secondary window. Because it's a slot connected via a signal, Qt will deliver the call on the correct thread.&lt;/p&gt;
&lt;h2 id="creating-the-secondary-window"&gt;Creating the secondary window&lt;/h2&gt;
&lt;p&gt;The secondary window has a button and a spin box. When the user clicks the button, the window emits a signal carrying the new value. The secondary window doesn't know anything about the worker &amp;mdash; it just emits a signal.&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 QWidget, QVBoxLayout, QPushButton, QSpinBox, QLabel
from PyQt6.QtCore import pyqtSignal


class SecondaryWindow(QWidget):
    """A secondary window that emits a signal when the user sets a value."""
    value_changed = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Secondary Window")

        layout = QVBoxLayout()

        self.label = QLabel("Set a new value for the worker:")
        layout.addWidget(self.label)

        self.spinbox = QSpinBox()
        self.spinbox.setRange(0, 1000)
        layout.addWidget(self.spinbox)

        self.button = QPushButton("Send to Worker")
        self.button.clicked.connect(self.send_value)
        layout.addWidget(self.button)

        self.setLayout(layout)

    def send_value(self):
        self.value_changed.emit(self.spinbox.value())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;value_changed&lt;/code&gt; signal is the only interface this window exposes. This keeps things clean and decoupled.&lt;/p&gt;
&lt;h2 id="wiring-everything-together-in-the-main-window"&gt;Wiring everything together in the main window&lt;/h2&gt;
&lt;p&gt;The main window is where all the connections happen. It creates the worker, starts the thread, opens the secondary window, and connects the secondary window's signal to the worker's 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;from PyQt6.QtWidgets import QMainWindow, QVBoxLayout, QPushButton, QLabel, QWidget
from PyQt6.QtCore import QThread


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Main Window")

        # Set up the UI
        layout = QVBoxLayout()

        self.status_label = QLabel("Waiting for data...")
        layout.addWidget(self.status_label)

        self.open_button = QPushButton("Open Secondary Window")
        self.open_button.clicked.connect(self.open_secondary)
        layout.addWidget(self.open_button)

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

        # Keep a reference to the secondary window
        self.secondary_window = None

        # Set up the background thread and worker
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)

        # Connect signals
        self.thread.started.connect(self.worker.run)
        self.worker.data_updated.connect(self.update_status)

        # Start the thread
        self.thread.start()

    def update_status(self, text):
        self.status_label.setText(text)

    def open_secondary(self):
        if self.secondary_window is None:
            self.secondary_window = SecondaryWindow()

            # Connect the secondary window's signal to the worker's slot.
            # This is the connection that makes cross-window,
            # cross-thread communication work.
            self.secondary_window.value_changed.connect(self.worker.set_value)

        self.secondary_window.show()

    def closeEvent(self, event):
        self.worker.running = False
        self.thread.quit()
        self.thread.wait()
        super().closeEvent(event)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The line that connects everything together is:&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.secondary_window.value_changed.connect(self.worker.set_value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This connects a signal from the secondary window (running in the main/GUI thread) to a slot on the worker (which has been moved to a background thread). Qt sees that the sender and receiver live in different threads, so it automatically uses a &lt;strong&gt;queued connection&lt;/strong&gt;. The slot call is placed into the background thread's event queue and executed there.&lt;/p&gt;
&lt;h2 id="understanding-why-the-main-window-worked-but-the-secondary-didnt"&gt;Understanding why the main window worked but the secondary didn't&lt;/h2&gt;
&lt;p&gt;In the original question, buttons in the main window could affect the background threads, but buttons in a secondary window could not. This usually happens because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The main window had direct signal-slot connections to the worker (set up when both the worker and the connections were created).&lt;/li&gt;
&lt;li&gt;The secondary window was created later, and its signals were never connected to the worker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To solution is to connect its signals to the appropriate worker slots, when you create the secondary window, just as you would for the main window. The worker doesn't care &lt;em&gt;where&lt;/em&gt; the signal comes from &amp;mdash; it just responds to whatever signals are connected to its slots. For more on managing multiple windows in PyQt6, see our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-multiple-windows/"&gt;creating multiple windows&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="a-note-about-qthreadpool-vs-qthread"&gt;A note about QThreadPool vs QThread&lt;/h2&gt;
&lt;p&gt;The original question mentions using &lt;code&gt;QThreadPool&lt;/code&gt;. If you're using &lt;code&gt;QRunnable&lt;/code&gt; with a &lt;code&gt;QThreadPool&lt;/code&gt;, the pattern is slightly different because &lt;code&gt;QRunnable&lt;/code&gt; doesn't inherit from &lt;code&gt;QObject&lt;/code&gt; and can't have slots directly. In that case, you typically create a separate &lt;code&gt;QObject&lt;/code&gt;-based signals class and attach it to your runnable. For a detailed walkthrough of that approach, see &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;Multithreading PyQt6 applications with QThreadPool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, for long-running background tasks that need two-way communication with the GUI (like GPIO handling), &lt;code&gt;QThread&lt;/code&gt; with &lt;code&gt;moveToThread()&lt;/code&gt; is usually a better fit. It gives you a proper event loop in the background thread, which means signals and slots work naturally in both directions.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's everything in a single file you can copy, run, and experiment with. If you're new to PyQt6, you may want to start with &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window&lt;/a&gt; before diving in.&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 time

from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QVBoxLayout,
    QWidget,
)


class Worker(QObject):
    """A worker that runs in a background thread."""

    data_updated = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.running = True
        self.current_value = 0

    @pyqtSlot()
    def run(self):
        """Simulate continuous data handling."""
        while self.running:
            self.current_value += 1
            self.data_updated.emit(f"Data: {self.current_value}")
            time.sleep(1)

    @pyqtSlot(int)
    def set_value(self, value):
        """Receive a new value from the GUI."""
        self.current_value = value
        self.data_updated.emit(f"Value set to: {self.current_value}")


class SecondaryWindow(QWidget):
    """A secondary window that emits a signal when the user sets a value."""

    value_changed = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Secondary Window")

        layout = QVBoxLayout()

        self.label = QLabel("Set a new value for the worker:")
        layout.addWidget(self.label)

        self.spinbox = QSpinBox()
        self.spinbox.setRange(0, 1000)
        layout.addWidget(self.spinbox)

        self.button = QPushButton("Send to Worker")
        self.button.clicked.connect(self.send_value)
        layout.addWidget(self.button)

        self.setLayout(layout)

    def send_value(self):
        self.value_changed.emit(self.spinbox.value())


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Main Window")

        # Set up the UI
        layout = QVBoxLayout()

        self.status_label = QLabel("Waiting for data...")
        layout.addWidget(self.status_label)

        self.open_button = QPushButton("Open Secondary Window")
        self.open_button.clicked.connect(self.open_secondary)
        layout.addWidget(self.open_button)

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

        # Keep a reference to the secondary window
        self.secondary_window = None

        # Set up the background thread and worker
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)

        # Connect signals
        self.thread.started.connect(self.worker.run)
        self.worker.data_updated.connect(self.update_status)

        # Start the thread
        self.thread.start()

    def update_status(self, text):
        self.status_label.setText(text)

    def open_secondary(self):
        if self.secondary_window is None:
            self.secondary_window = SecondaryWindow()
            # Connect the secondary window's signal to the worker's slot
            self.secondary_window.value_changed.connect(
                self.worker.set_value
            )
        self.secondary_window.show()

    def closeEvent(self, event):
        self.worker.running = False
        self.thread.quit()
        self.thread.wait()
        super().closeEvent(event)


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, you'll see the main window counting up once per second. Click "Open Secondary Window", enter a number, and click "Send to Worker" &amp;mdash; the worker's counter will jump to your chosen value and continue counting from there.&lt;/p&gt;
&lt;p&gt;The secondary window communicates with the background thread entirely through signals and slots, with no direct method calls across threads. This pattern scales well &amp;mdash; you can connect as many windows as you like to the same worker, or connect one window to multiple workers. As long as you use signals and slots for cross-thread communication, Qt handles the thread safety for you.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="multithreading"/><category term="signals"/><category term="qthread"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Streamlit Widgets — An Overview of Commonly Used Widgets in Streamlit</title><link href="https://www.pythonguis.com/tutorials/streamlit-widgets/" rel="alternate"/><published>2026-04-24T06:00:00+00:00</published><updated>2026-04-24T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-04-24:/tutorials/streamlit-widgets/</id><summary type="html">Streamlit is a powerful Python library designed to build interactive web apps with minimal code. One of its core features is an extensive collection of widgets that allow users to interact with the app in various ways, such as providing inputs, triggering actions, or visualizing data. Streamlit makes it easy to create these elements with simple, intuitive syntax.</summary><content type="html">&lt;p&gt;Streamlit is a powerful Python library designed to build interactive web apps with minimal code. One of its core features is an extensive collection of widgets that allow users to interact with the app in various ways, such as providing inputs, triggering actions, or visualizing data. Streamlit makes it easy to create these elements with simple, intuitive syntax.&lt;/p&gt;
&lt;p&gt;In &lt;a href="/tutorials/getting-started-with-streamlit"&gt;the previous tutorial&lt;/a&gt;, we saw how to get started with Streamlit and run it on your local host. Here, we'll cover the main widgets available in Streamlit, explaining how they work, and how to customize them using various examples.&lt;/p&gt;
&lt;h2 id="getting-started-with-widgets-streamlit"&gt;Getting started with Widgets Streamlit&lt;/h2&gt;
&lt;p&gt;Widgets in Streamlit are simple yet customizable. By combining multiple widgets together in different layouts, you can create interactive dashboards, data visualizations, and forms. Whether you want to include buttons, sliders, checkboxes, or display tables and plots, Streamlit offers a wide range of widgets that cater to different needs. Adding a widget to your Streamlit app is as easy as calling a single function, and customizing its behavior requires only minimal code.&lt;/p&gt;
&lt;p&gt;In this guide, we will explore the various widgets that Streamlit offers, from basic input elements like text boxes and radio buttons to more complex visual components like plots and tables. We'll also dive into how to customize the behavior of these widgets to suit your app's specific requirements, such as adjusting slider ranges, modifying button text, and adding captions to images. By the end of this article, you'll have a solid understanding of how to leverage Streamlit widgets to enhance the interactivity and functionality of your applications.&lt;/p&gt;
&lt;p&gt;Let's start by exploring the basic widgets Streamlit provides and how you can easily integrate them into your app. Make sure you have installed the streamlit module on your system and imported it on your Python file.&lt;/p&gt;
&lt;h2 id="buttons-in-streamlit"&gt;Buttons in Streamlit&lt;/h2&gt;
&lt;p&gt;Buttons are one of the most essential and commonly used components in any interactive application. In Streamlit, the &lt;code&gt;st.button()&lt;/code&gt; widget provides an easy and effective way to allow users to trigger actions, interact with your app, or make decisions. With just a few lines of Python code, you can integrate buttons into your Streamlit app to perform tasks like data processing, changing app state, or displaying content.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.button()&lt;/code&gt; widget creates a clickable button on the interface. When clicked, it returns &lt;code&gt;True&lt;/code&gt;, which can be used to trigger specific actions. If the button is not clicked, it returns &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The basic syntax for creating a button in Streamlit is given below:&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 streamlit as st
if st.button('Click Me'):
    st.write("Button clicked!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="A Streamlit button widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/button.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button.png?tr=w-600 600w" loading="lazy" width="645" height="189"/&gt;
&lt;em&gt;A Streamlit button widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this simple example, the button's label is "Click Me". When clicked, the app prints "Button clicked!" to the interface.&lt;/p&gt;
&lt;p&gt;The most common customization for a button is its label &amp;mdash; the text that appears on the button. The label is the first argument you pass to the &lt;code&gt;st.button()&lt;/code&gt; function. For example, we can change the "Click Me" to a "Submit" button.&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 streamlit as st
if st.button('Submit'):
    st.write("Form submitted successfully!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="A Streamlit button widget with a different label" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/button-label.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-label.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-label.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-label.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-label.png?tr=w-600 600w" loading="lazy" width="648" height="168"/&gt;
&lt;em&gt;A Streamlit button widget with a different label&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can customize this label to suit the action you want to convey to the user. Whether it's Submit, Cancel, Run, or any custom text, Streamlit will display it as the button text&lt;/p&gt;
&lt;p&gt;Moreover, Streamlit makes it simple to handle multiple buttons on the same page. You can define multiple &lt;code&gt;st.button()&lt;/code&gt; widgets, each with its own label and action. Here is an example of how we can add multiple buttons.&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 streamlit as st
if st.button('Button A'):
    st.write("Button A clicked!")

if st.button('Button B'):
    st.write("Button B clicked!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Multiple Streamlit buttons" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/button-multiple.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-multiple.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-multiple.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-multiple.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/button-multiple.png?tr=w-600 600w" loading="lazy" width="650" height="227"/&gt;
&lt;em&gt;Multiple Streamlit buttons&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Similarly, you can add as many buttons as you wish.&lt;/p&gt;
&lt;h2 id="checkboxes-in-streamlit"&gt;Checkboxes in Streamlit&lt;/h2&gt;
&lt;p&gt;Checkboxes are a fundamental widget in Streamlit that allows users to toggle between two states: checked &lt;code&gt;True&lt;/code&gt; or unchecked &lt;code&gt;False&lt;/code&gt;. They are ideal for scenarios where you want users to make binary choices, such as showing or hiding content, enabling or disabling features, or making Yes/No decisions. It can also be used to select multiple options as the same time as well. The &lt;code&gt;st.checkbox()&lt;/code&gt; widget is incredibly versatile and easy to implement, making it a key component in creating interactive applications.&lt;/p&gt;
&lt;p&gt;As mentioned, the &lt;code&gt;st.checkbox()&lt;/code&gt; widget creates a simple checkbox in your Streamlit app. When a user checks the box, it returns &lt;code&gt;True&lt;/code&gt;, and when the box is unchecked, it returns &lt;code&gt;False&lt;/code&gt;. Here is a basic example of checkboxes in Streamlit.&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;show_text = st.checkbox('Show text')
if show_text:
    st.write("You checked the box!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit checkbox" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/checkbox.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox.png?tr=w-600 600w" loading="lazy" width="642" height="158"/&gt;
&lt;em&gt;Streamlit checkbox&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the checkbox is labeled "Show text". When the user checks the box, the app displays "You checked the box!" on the interface.&lt;/p&gt;
&lt;p&gt;The most common customization for a checkbox is its label, which appears next to the checkbox itself. This label should clearly indicate what action will occur when the checkbox is checked.&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;subscribe = st.checkbox('Subscribe to our newsletter')
if subscribe:
    st.write("Thanks for subscribing!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit checkbox with custom label" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-600 600w" loading="lazy" width="648" height="292" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-600 600w" loading="lazy" width="648" height="292"/&gt;
&lt;em&gt;Streamlit checkbox with custom label&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here, the checkbox label is customized to "Subscribe to our newsletter", and when the user checks it, a thank-you message is displayed.&lt;/p&gt;
&lt;p&gt;By default, checkboxes in Streamlit are unchecked (&lt;code&gt;False&lt;/code&gt;). However, you can change this by setting the value parameter to &lt;code&gt;True&lt;/code&gt;, so that the checkbox is pre-checked when the app loads.&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;subscribe = st.checkbox('Subscribe to our newsletter', value=True)
if subscribe:
    st.write("Thanks for subscribing!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this case, the checkbox is checked by default, and the welcome message is shown immediately when the app starts.&lt;/p&gt;
&lt;p&gt;Furthermore, Streamlit makes it easy to handle multiple checkboxes, each controlling different parts of your app. You can create several checkboxes, and based on their states, you can conditionally display content or execute logic.&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;option1 = st.checkbox('Enable Feature 1')
option2 = st.checkbox('Enable Feature 2')

if option1:
    st.write("Feature 1 is enabled!")
if option2:
    st.write("Feature 2 is enabled!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Multiple Streamlit checkboxes" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-600 600w" loading="lazy" width="648" height="292" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-label.png?tr=w-600 600w" loading="lazy" width="648" height="292"/&gt;
&lt;em&gt;Multiple Streamlit checkboxes&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, you may need to generate checkboxes dynamically, especially if the number of checkboxes depends on user input or the result of some computation.&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;options = ['Apple', 'Banana', 'Cherry']
selected = []

for fruit in options:
    if st.checkbox(fruit):
        selected.append(fruit)

st.write(f'Selected fruits: {", ".join(selected)}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Getting Streamlit checkboxes selection state" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/checkbox-selected.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-selected.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-selected.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-selected.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/checkbox-selected.png?tr=w-600 600w" loading="lazy" width="641" height="243"/&gt;
&lt;em&gt;Getting Streamlit checkboxes selection state&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example a list of fruit names is used to dynamically create a set of checkboxes. When a user checks a box, the corresponding fruit is added to a list, and the selected fruits are displayed.&lt;/p&gt;
&lt;h2 id="radio-buttons-in-streamlit"&gt;Radio buttons in Streamlit&lt;/h2&gt;
&lt;p&gt;Radio buttons are an essential widget in Streamlit that allow users to select a single option from a predefined list of choices. They are perfect for scenarios where only one selection can be made at a time, such as selecting a category, choosing between modes, or answering questions. The &lt;code&gt;st.radio()&lt;/code&gt; widget provides an intuitive and simple way for users to interact with your Streamlit application.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.radio()&lt;/code&gt; widget creates a list of radio buttons where the user can select only one option at a time. It returns the selected option, which can be used to drive various actions in the app. Let us have a look at a very simple example of a radio button.&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;choice = st.radio('Choose an option:', ['Option 1', 'Option 2', 'Option 3'])
st.write(f'You selected: {choice}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit radio buttons" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/radio.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-600 600w" loading="lazy" width="648" height="242" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-600 600w" loading="lazy" width="648" height="242"/&gt;
&lt;em&gt;Streamlit radio buttons&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A label "Choose an option:" is provided.&lt;/li&gt;
&lt;li&gt;The user can select one of three options: "Option 1", "Option 2", or "Option 3".&lt;/li&gt;
&lt;li&gt;The app displays the selected option below the radio buttons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, the first option in the list of radio buttons is selected. However, you can customize this by setting the index parameter, which specifies which option should be selected when the app loads.&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;travel_mode = st.radio('Preferred mode of travel:', ['Car', 'Bike', 'Plane'], index=2)
st.write(f'You selected: {travel_mode}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit radio buttons with default selection" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/radio.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-600 600w" loading="lazy" width="648" height="242" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/radio.png?tr=w-600 600w" loading="lazy" width="648" height="242"/&gt;
&lt;em&gt;Streamlit radio buttons with default selection&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;index=2&lt;/code&gt; means the third option ("Plane") is selected by default when the app is loaded.&lt;/p&gt;
&lt;h2 id="select-box-in-streamlit"&gt;Select box in Streamlit&lt;/h2&gt;
&lt;p&gt;A select box in Streamlit is a widget that lets users choose a single option from a dropdown list. This widget is perfect for situations where you have a predefined list of options but want to save space on your interface by not displaying all the options upfront. The &lt;code&gt;st.selectbox()&lt;/code&gt; widget is highly customizable and can be used for anything from simple selections to dynamically populated lists. It's ideal for situations where you want to offer multiple choices, but only display the currently selected item.
Let us create a simple dropdown menu with three options.&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;option = st.selectbox('Choose an option:', ['Option 1', 'Option 2', 'Option 3'])
st.write(f'You selected: {option}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit select box" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/select.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/select.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/select.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/select.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/select.png?tr=w-600 600w" loading="lazy" width="676" height="322"/&gt;
&lt;em&gt;Streamlit select box&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A label "Choose an option:" is provided.&lt;/li&gt;
&lt;li&gt;The user can select one of the options from the dropdown list: "Option 1", "Option 2", or "Option 3".&lt;/li&gt;
&lt;li&gt;The app displays the selected option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, the first item in the list of options is selected in a select box. However, you can specify a different default selection by setting the index parameter. The index corresponds to the zero-based position of the option in the 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;city = st.selectbox('Select your city:', ['New York', 'London', 'Paris', 'Tokyo'], index=2)
st.write(f'You selected: {city}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this code, you will notice that, Paris will be selected as city because it is at index 2.&lt;/p&gt;
&lt;h2 id="slider-in-streamlit"&gt;Slider in Streamlit&lt;/h2&gt;
&lt;p&gt;Sliders are a popular widget in Streamlit that allows users to select values by dragging a handle across a range. They are perfect for collecting numerical inputs or setting parameters like dates, time, and ranges. Streamlit's &lt;code&gt;st.slider()&lt;/code&gt; widget provides a flexible and easy-to-use interface for adding sliders to your app, enabling users to interactively choose values with precision. It can handle integers, floats, dates, and times, making it highly versatile for various use cases.&lt;/p&gt;
&lt;p&gt;Let us create a simple slider where a user can select any number from 0 to 100.&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;value = st.slider('Select a value:', 0, 100)
st.write(f'You selected: {value}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit slider widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider.png?tr=w-600 600w" loading="lazy" width="678" height="220"/&gt;
&lt;em&gt;Streamlit slider widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By default, the slider handle is set to the minimum value of the range, but you can specify a default value by providing a value argument. This is useful when you want the slider to start at a specific position.&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;temperature = st.slider('Set the temperature:', -50, 50, value=20)
st.write(f'Temperature set to: {temperature}&amp;deg;C')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit slider widget with default value" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider-default.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-default.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-default.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-default.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-default.png?tr=w-600 600w" loading="lazy" width="675" height="216"/&gt;
&lt;em&gt;Streamlit slider widget with default value&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;By default, the 20 will be selected as set temperature.&lt;/p&gt;
&lt;p&gt;Sliders in Streamlit can handle both integers and floating-point numbers. To use floating-point values, you simply specify a range with float values. You can also control the step size between values using the step parameter.&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;price = st.slider('Select a price:', 0.0, 1000.0, step=0.5)
st.write(f'Price selected: ${price}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit float slider widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider-float.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-float.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-float.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-float.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-float.png?tr=w-600 600w" loading="lazy" width="678" height="224"/&gt;
&lt;em&gt;Streamlit float slider widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;slider&lt;/code&gt; lets the user select a price between 0.0 and 1000.0.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;step=0.5&lt;/code&gt; ensures that the slider increments or decrements by 0.5 units.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Streamlit's slider widget also allows users to select a range of values, which is particularly useful when you need two values (e.g., a start and end date, or a minimum and maximum range). To do this, provide a tuple as the value argument.&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;salary_range = st.slider('Select a salary range:', 20000, 100000, (30000, 80000))
st.write(f'Selected salary range: ${salary_range[0]} - ${salary_range[1]}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit range slider widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider-range.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-range.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-range.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-range.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-range.png?tr=w-600 600w" loading="lazy" width="675" height="218"/&gt;
&lt;em&gt;Streamlit range slider widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here, the slider has a range from 20000 to 100000. The user can select a minimum and maximum value for the salary range (initially set between 30000 and 80000).&lt;/p&gt;
&lt;p&gt;Apart from that, Streamlit sliders can also handle date and time values, which is especially useful when users need to select a specific day or time range. You can create sliders with datetime.date and datetime.time objects to allow users to make date-based selections.&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 streamlit as st
import datetime

date = st.slider('Select a date:', datetime.date(2020, 1, 1), datetime.date(2024, 12, 31), value=datetime.date(2023, 1, 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit date slider" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider-date.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-date.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-date.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-date.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-date.png?tr=w-600 600w" loading="lazy" width="677" height="221"/&gt;
&lt;em&gt;Streamlit date slider&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the user can select a date between January 1, 2020, and December 31, 2024. The default value is set to January 1, 2023.&lt;/p&gt;
&lt;p&gt;You can use sliders with any other widgets or functions to make it more dynamic and interactive. For example, we can combine them with conditional logic to adjust the behavior or content of an app based on the selected value. This is particularly useful for creating dynamic and interactive experiences.&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;rating = st.slider('Rate our service:', 1, 5)

if rating &amp;lt;= 2:
    st.write('We are sorry to hear that. Please let us know how we can improve.')
else:
    st.write('Thank you for your feedback!')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit slider interactivity" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/slider-interactive.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-interactive.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-interactive.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-interactive.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/slider-interactive.png?tr=w-600 600w" loading="lazy" width="672" height="214"/&gt;
&lt;em&gt;Streamlit slider interactivity&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The slider allows users to rate a service between 1 and 5. Based on the rating, different messages are displayed.&lt;/p&gt;
&lt;h2 id="different-input-options-in-streamlit"&gt;Different input options in Streamlit&lt;/h2&gt;
&lt;p&gt;In Streamlit, input options are essential for creating interactive applications, allowing users to interact with data and visualizations dynamically. Streamlit offers a variety of input widgets that cater to different types of data and user interactions. Below are the main input options that we will be discussing in this section:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text Input: The &lt;code&gt;st.text_input()&lt;/code&gt; widget allows users to enter single-line text data. It's useful for collecting short information like names, email addresses, or any single-line text input.&lt;/li&gt;
&lt;li&gt;Text Area: For multi-line input, &lt;code&gt;st.text_area()&lt;/code&gt; is the preferred choice. It provides users with a larger space for entering longer content, like descriptions, notes, or code snippets.&lt;/li&gt;
&lt;li&gt;Number Input: Streamlit provides the &lt;code&gt;st.number_input()&lt;/code&gt; widget for numerical inputs. Users can specify integer or float values within a defined range and step size, which makes it suitable for settings like entering age, prices, or percentages.&lt;/li&gt;
&lt;li&gt;Date and Time Input: For handling date and time, Streamlit provides the &lt;code&gt;st.date_input()&lt;/code&gt; and &lt;code&gt;st.time_input()&lt;/code&gt; widgets, allowing users to pick dates and times easily. This is useful for scheduling or filtering data by time ranges.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let us discuss each of these options in details by taking examples.&lt;/p&gt;
&lt;h3&gt;Date and Time Input&lt;/h3&gt;
&lt;p&gt;Among its many features, Streamlit provides robust support for handling date and time inputs, allowing developers to easily integrate date and time pickers into their applications. This is useful in a wide variety of contexts, such as scheduling applications, time series analysis, filtering data based on time ranges, or tracking events.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.date_input()&lt;/code&gt; widget in Streamlit provides a simple and intuitive interface for users to select dates. This widget can be used to input a single date or a range of dates. The basic syntax of &lt;code&gt;date_input()&lt;/code&gt; function is given below will possible parameter values.&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;st.date_input("Date", value=None, min_value=None, max_value=None, key=None, help=None, on_change=None)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit date input" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/date.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date.png?tr=w-600 600w" loading="lazy" width="797" height="503"/&gt;
&lt;em&gt;Streamlit date input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let us explore the parameters in turn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt;: The label to display alongside the widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt;: The default date(s) to show in the widget. This can be a single date or a tuple of two dates for range selection. Defaults to today's date.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_value&lt;/code&gt;: The earliest date that can be selected. Defaults to no minimum.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_value&lt;/code&gt;: The latest date that can be selected. Defaults to no maximum.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;key&lt;/code&gt;: An optional key that uniquely identifies this widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;help&lt;/code&gt;: A tooltip that displays when the user hovers over the widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on_change&lt;/code&gt;: A callback function that runs when the input changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to that, you can also use &lt;code&gt;st.date_input()&lt;/code&gt; to allow users to pick a range of dates by passing a tuple of two datetime.date objects as the default value.&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 streamlit as st
import datetime

# Date range input
start_date = datetime.date(2023, 9, 1)
end_date = datetime.date(2023, 9, 30)
date_range = st.date_input("Select a date range", (start_date, end_date))
st.write(f"Start date: {date_range[0]}")
st.write(f"End date: {date_range[1]}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit date range input" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/date-range.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-600 600w" loading="lazy" width="795" height="266" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-600 600w" loading="lazy" width="795" height="266"/&gt;
&lt;em&gt;Streamlit date range input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this case, the user can select a range of dates. The widget will return a tuple containing the start and end dates.&lt;/p&gt;
&lt;p&gt;On the other hand, the &lt;code&gt;st.time_input()&lt;/code&gt; widget allows users to select a specific time. This widget is useful in scenarios like scheduling events or setting alarms. Here is the simple syntax off &lt;code&gt;time_input()&lt;/code&gt; function with its possible parameter values.&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;st.time_input(label="Time", value=None, key=None, help=None, on_change=None)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit time input" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/date-range.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-600 600w" loading="lazy" width="795" height="266" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-range.png?tr=w-600 600w" loading="lazy" width="795" height="266"/&gt;
&lt;em&gt;Streamlit time input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The parameters in the &lt;code&gt;time_input&lt;/code&gt; functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt;: The label displayed next to the widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt;: The default time shown in the widget. This can be a datetime.time object. Defaults to the current time.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;key&lt;/code&gt;: An optional key that uniquely identifies the widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;help&lt;/code&gt;: Tooltip displayed when the user hovers over the widget.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on_change&lt;/code&gt;: A callback function that runs when the input changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In many applications, you'll need both date and time inputs together. Although Streamlit doesn't provide a single widget for selecting both date and time, you can combine the &lt;code&gt;st.date_input()&lt;/code&gt; and &lt;code&gt;st.time_input()&lt;/code&gt; widgets to achieve this functionality.&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 streamlit as st
import datetime

# Date input
date = st.date_input("Pick a date", datetime.date.today())
# Time input
time = st.time_input("Pick a time", datetime.time(9, 00))

# Combine date and time
selected_datetime = datetime.datetime.combine(date, time)
st.write(f"Selected date and time: {selected_datetime}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Combining Streamlit widgets for selecting date and time" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/date-time-combined.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-time-combined.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-time-combined.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-time-combined.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/date-time-combined.png?tr=w-600 600w" loading="lazy" width="797" height="305"/&gt;
&lt;em&gt;Combining Streamlit widgets for selecting date and time&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This code lets the user select both a date and a time, then combines them into a single &lt;code&gt;datetime.datetime&lt;/code&gt; object.&lt;/p&gt;
&lt;h3&gt;Text and area input&lt;/h3&gt;
&lt;p&gt;One of the essential features of any web app is gathering user input, and Streamlit provides several widgets to capture user input easily. Among them, text input widgets play a crucial role in allowing users to input free-form text data. In this article, we will dive into Streamlit's text input widgets and explore their capabilities, practical applications, and customization options.&lt;/p&gt;
&lt;p&gt;Streamlit offers two primary widgets for capturing text input:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single-Line Text Input: Captured using the &lt;code&gt;st.text_input()&lt;/code&gt; widget.&lt;/li&gt;
&lt;li&gt;Multi-Line Text Area: Captured using the &lt;code&gt;st.text_area()&lt;/code&gt; widget.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both widgets are used to gather text-based input from users but differ in the amount of text they are designed to handle. Let's take a closer look at each of these widgets.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.text_input()&lt;/code&gt; widget allows users to input a single line of text. This is ideal for cases where you want to gather short responses such as names, email addresses, usernames, search queries, or small pieces of 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;# Single-line text input
name = st.text_input("Enter your name")
st.write(f"Hello, {name}!")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit text input widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/input.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input.png?tr=w-600 600w" loading="lazy" width="797" height="196"/&gt;
&lt;em&gt;Streamlit text input widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This example creates a simple input box where users can enter their names. The app then displays a message using the entered text.&lt;/p&gt;
&lt;p&gt;In some cases, we may need to limit the total number of characters entered by the user. So, we can use the &lt;code&gt;max_chars&lt;/code&gt;parameter as shown below:&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;# Text input with character limit
username = st.text_input("Enter your username", max_chars=15)
st.write(f"Your username is: {username}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is similar to the previous example, but this time the user is not allowed to enter characters more than 15. In the &lt;code&gt;text_input()&lt;/code&gt; function, we can also specify the type of text we want the user to enter. For example, if we want a user to enter an email and password, we can specify those as shown below:&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;email = st.text_input("Enter your email")
st.write(f"Email entered: {email}")
password = st.text_input("Enter your password", type='password')
st.write(f"Password length: {len(password)} characters")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit password input widget" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/input-password.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input-password.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input-password.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input-password.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/input-password.png?tr=w-600 600w" loading="lazy" width="797" height="330"/&gt;
&lt;em&gt;Streamlit password input widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As shown above, when we enter the password, it will not be visible.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;st.text_area()&lt;/code&gt; widget is ideal when you need to capture longer inputs or multi-line text, such as feedback, code snippets, or detailed descriptions. It provides users with a resizable text area where they can enter more extensive information&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;# Multi-line text area
feedback = st.text_area("Your feedback", "Enter your comments here...")
st.write(f"Your feedback: {feedback}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit text area for text input" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/textarea.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/textarea.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/textarea.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/textarea.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/textarea.png?tr=w-600 600w" loading="lazy" width="800" height="275"/&gt;
&lt;em&gt;Streamlit text area for text input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the text area allows the user to enter multiple lines of text. The entered feedback is then displayed. In a similar way as we did before, you can limit the max number of characters by assigning a value to the &lt;code&gt;max_chars&lt;/code&gt; parameter.&lt;/p&gt;
&lt;h3&gt;Number Input in Streamlit&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;st.number_input()&lt;/code&gt; widget allows users to input numbers, offering several customization options like setting minimum and maximum values, adjusting step increments, and choosing between integers and floating-point numbers. This widget is particularly useful for inputs like prices, quantities, percentages, or any scenario where numerical precision is required.&lt;/p&gt;
&lt;p&gt;The simplest form of the &lt;code&gt;st.number_input()&lt;/code&gt; widget involves asking the user to input a number without setting any constraints like minimum or maximum values.&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;age = st.number_input("Enter your age")
st.write(f"Your age is: {age}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Streamlit numeric input" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/number.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number.png?tr=w-600 600w" loading="lazy" width="800" height="227"/&gt;
&lt;em&gt;Streamlit numeric input&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the widget accepts any number, and the user's input is displayed back on the screen.&lt;/p&gt;
&lt;p&gt;You can restrict the input to a certain range by specifying the &lt;code&gt;min_value&lt;/code&gt; and &lt;code&gt;max_value&lt;/code&gt; parameters. This is particularly useful when you need to validate the input against specific boundaries.&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;# Number input with a range
rating = st.number_input("Rate your experience", min_value=1, max_value=5)
st.write(f"Your rating is: {rating}")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Numeric input with validation" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/number-valid.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-valid.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-valid.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-valid.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-valid.png?tr=w-600 600w" loading="lazy" width="803" height="225"/&gt;
&lt;em&gt;Numeric input with validation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the user can only select a rating between 1 and 5, ensuring valid input.
You can set a default value that appears in the input box when the app first loads, and you can also define how much the value should increase or decrease when the user interacts with the widget.&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;# Number input with default value and step size
quantity = st.number_input("Select quantity", min_value=0, max_value=100, value=10, step=5)
st.write(f"You have selected: {quantity} units")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Numeric input with validation and step size" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/number-step.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-step.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-step.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-step.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/number-step.png?tr=w-600 600w" loading="lazy" width="792" height="240"/&gt;
&lt;em&gt;Numeric input with validation and step size&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the widget starts with a default value of 10, and the user can adjust the value in increments of 5, from 0 to 100.&lt;/p&gt;
&lt;h2 id="file-uploader-in-streamlit"&gt;File uploader in Streamlit&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;st.file_uploader()&lt;/code&gt; widget, which allows users to upload files directly into your Streamlit app. This functionality is vital in many applications, such as data analysis tools, machine learning models, and document processing systems. This widget is a convenient tool for enabling users to upload files of various types, such as text files, CSVs, images, PDFs, and more. Once uploaded, the files can be processed or analyzed directly within the Streamlit app.&lt;/p&gt;
&lt;p&gt;The simplest use case of &lt;code&gt;st.file_uploader()&lt;/code&gt; is to upload a single file of a specific type.&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 pandas as pd

# Single file uploader for CSV files
uploaded_file = st.file_uploader("Upload a CSV file", type="csv")

if uploaded_file is not None:
    df = pd.read_csv(uploaded_file)
    st.write(df)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Upload CSV files" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/upload-csv-file.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-csv-file.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-csv-file.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-csv-file.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-csv-file.png?tr=w-600 600w" loading="lazy" width="803" height="518"/&gt;
&lt;em&gt;Upload CSV files&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the user can upload a CSV file, and the app reads and displays the contents using the pandas library.&lt;/p&gt;
&lt;p&gt;We can also upload images and show them in our web app. For images, we need to specify the type of images as shown in the example below:&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 streamlit as st
from PIL import Image

# Upload an image file
uploaded_image = st.file_uploader("Upload an image", type=["png", "jpg", "jpeg"])

if uploaded_image is not None:
    image = Image.open(uploaded_image)
    st.image(image, caption="Uploaded Image", use_column_width=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Upload image files" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/upload-image.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-image.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-image.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-image.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-image.png?tr=w-600 600w" loading="lazy" width="788" height="740"/&gt;
&lt;em&gt;Upload image files&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here, users can upload image files in formats like PNG, JPG, or JPEG. Once uploaded, the app uses the Pillow library to open and display the image.&lt;/p&gt;
&lt;p&gt;Furthermore, you can use the accept_multiple_files parameter to allow users to upload several files at once. This is useful when handling bulk uploads or cases where multiple files need to be processed together.&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;# Multiple file uploader for text files
uploaded_files = st.file_uploader("Upload multiple text files", type="txt", accept_multiple_files=True)

if uploaded_files:
    for uploaded_file in uploaded_files:
        st.write(f"File name: {uploaded_file.name}")
        content = uploaded_file.read().decode("utf-8")
        st.write(content)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Uploading multiple files" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/upload-multiple-files.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-multiple-files.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-multiple-files.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-multiple-files.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-multiple-files.png?tr=w-600 600w" loading="lazy" width="802" height="468"/&gt;
&lt;em&gt;Uploading multiple files&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, the user can upload multiple text files. The app reads and displays the contents of each file.&lt;/p&gt;
&lt;p&gt;Streamlit also allows you to validate uploaded files based on specific conditions, such as file size, format, or content.&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;uploaded_file = st.file_uploader("Upload a file")

if uploaded_file is not None:
    # Check file size (less than 2 MB)
    file_size = uploaded_file.size
    if file_size &amp;gt; 2 * 1024 * 1024:
        st.error("File size exceeds 2 MB limit!")
    else:
        st.success("File uploaded successfully.")
        st.write(f"File size: {file_size} bytes")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Uploading file too large" src="https://www.pythonguis.com/static/tutorials/streamlit/streamlit-widgets/upload-file-too-large.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-file-too-large.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-file-too-large.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-file-too-large.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/streamlit/streamlit-widgets/upload-file-too-large.png?tr=w-600 600w" loading="lazy" width="798" height="333"/&gt;
&lt;em&gt;Uploading file too large&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the file was not uploaded because it exceeds the max size.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Streamlit's widget system is a powerful and intuitive way to add interactivity to web applications. From simple text and number inputs to more complex file uploaders and sliders, Streamlit provides a wide range of widgets that allow users to seamlessly interact with your app. These widgets can be easily integrated into data-driven applications, enabling users to input data, upload files, and adjust parameters in real-time.&lt;/p&gt;
&lt;p&gt;Streamlit widgets are designed to be highly customizable, offering various configuration options like minimum and maximum values, step sizes, file type restrictions, and dynamic callbacks. With minimal code, developers can create sophisticated, interactive applications that enhance user experience and make complex workflows more accessible.&lt;/p&gt;</content><category term="streamlit"/><category term="foundation"/><category term="streamlit-foundation"/></entry><entry><title>Checkboxes in Table Views with a Custom Model — Show checkboxes for boolean values in PyQt/PySide table views</title><link href="https://www.pythonguis.com/faq/abstract-table-model-question/" rel="alternate"/><published>2026-04-22T09:00:00+00:00</published><updated>2026-04-22T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-04-22:/faq/abstract-table-model-question/</id><summary type="html">I have a QTableView with a custom QAbstractTableModel, and I want to add a column of checkboxes. Should I create a custom delegate class for the checkbox, or is there a simpler way to do this?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I have a QTableView with a custom QAbstractTableModel, and I want to add a column of checkboxes. Should I create a custom delegate class for the checkbox, or is there a simpler way to do this?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; use a custom delegate to draw a checkbox widget, but you don't have to. Qt provides a built-in mechanism for this: &lt;code&gt;Qt.CheckStateRole&lt;/code&gt;. By returning &lt;code&gt;Qt.Checked&lt;/code&gt; or &lt;code&gt;Qt.Unchecked&lt;/code&gt; from your model's &lt;code&gt;data()&lt;/code&gt; method, Qt will render a checkbox automatically &amp;mdash; no delegate required.&lt;/p&gt;
&lt;p&gt;Let's walk through how this works, starting with a simple display and then adding some interactivity.&lt;/p&gt;
&lt;h2 id="displaying-checkboxes-using-qtcheckstaterole"&gt;Displaying checkboxes using Qt.CheckStateRole&lt;/h2&gt;
&lt;p&gt;The simplest way to add checkboxes to a &lt;code&gt;QTableView&lt;/code&gt; is to handle &lt;code&gt;Qt.CheckStateRole&lt;/code&gt; in your model's &lt;code&gt;data()&lt;/code&gt; method. When Qt asks your model for data with this role, returning &lt;code&gt;Qt.Checked&lt;/code&gt; or &lt;code&gt;Qt.Unchecked&lt;/code&gt; tells Qt to draw a checkbox in that cell.&lt;/p&gt;
&lt;p&gt;Here's a minimal example that shows a checked checkbox in every cell:&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 data(self, index, role):
    if role == Qt.DisplayRole:
        value = self._data[index.row()][index.column()]
        return str(value)

    if role == Qt.CheckStateRole:
        return Qt.Checked
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This produces a table where every cell has both text and a checked checkbox:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Checkboxes displayed as checked in a QTableView using Qt.CheckStateRole" src="https://www.pythonguis.com/static/faq/abstract-table-model-question/jEZMMnIU6wRy1ORY0NHQmtmVuBA.jpeg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/abstract-table-model-question/jEZMMnIU6wRy1ORY0NHQmtmVuBA.jpeg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/abstract-table-model-question/jEZMMnIU6wRy1ORY0NHQmtmVuBA.jpeg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/abstract-table-model-question/jEZMMnIU6wRy1ORY0NHQmtmVuBA.jpeg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/abstract-table-model-question/jEZMMnIU6wRy1ORY0NHQmtmVuBA.jpeg?tr=w-600 600w" loading="lazy" width="690" height="277"/&gt;&lt;/p&gt;
&lt;p&gt;In a real application, you would return &lt;code&gt;Qt.Checked&lt;/code&gt; or &lt;code&gt;Qt.Unchecked&lt;/code&gt; based on actual boolean values in your data. You might also restrict checkboxes to a specific column &amp;mdash; for example, one that holds &lt;code&gt;True&lt;/code&gt;/&lt;code&gt;False&lt;/code&gt; values &amp;mdash; rather than showing them everywhere.&lt;/p&gt;
&lt;h2 id="making-checkboxes-toggleable"&gt;Making checkboxes toggleable&lt;/h2&gt;
&lt;p&gt;Displaying checkboxes is a good start, but users will expect to be able to click them. To make checkboxes interactive, you need three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A data store for the check state&lt;/strong&gt; &amp;mdash; a list (or column) that tracks which items are checked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Qt.ItemIsUserCheckable&lt;/code&gt; returned from &lt;code&gt;flags()&lt;/code&gt;&lt;/strong&gt; &amp;mdash; this tells Qt that the cell supports toggling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A &lt;code&gt;setData()&lt;/code&gt; implementation for &lt;code&gt;Qt.CheckStateRole&lt;/code&gt;&lt;/strong&gt; &amp;mdash; this stores the updated state when the user clicks a checkbox.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's put all of this together in a complete 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;import sys

from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import Qt


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data, checked):
        super().__init__()
        self._data = data
        self._checked = checked

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            value = self._data[index.row()][index.column()]
            return str(value)

        if role == Qt.ItemDataRole.CheckStateRole:
            checked = self._checked[index.row()][index.column()]
            if checked:
                return Qt.CheckState.Checked
            return Qt.CheckState.Unchecked

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.CheckStateRole:
            checked = value == Qt.CheckState.Checked.value
            self._checked[index.row()][index.column()] = checked
            self.dataChanged.emit(index, index, [role])
            return True
        return False

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsUserCheckable
        )


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.table = QtWidgets.QTableView()

        data = [
            [1, 9, 2],
            [1, 0, -1],
            [3, 5, 2],
            [3, 3, 2],
            [5, 8, 9],
        ]

        checked = [
            [True, True, True],
            [False, False, False],
            [True, False, False],
            [True, False, True],
            [False, True, True],
        ]

        self.model = TableModel(data, checked)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a table with checkboxes next to every value. Clicking any checkbox toggles it on and off, and the underlying &lt;code&gt;checked&lt;/code&gt; list is updated accordingly.&lt;/p&gt;
&lt;h3&gt;Storing check state separately&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;checked&lt;/code&gt; list mirrors the structure of the &lt;code&gt;data&lt;/code&gt; list &amp;mdash; each cell has a corresponding &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt; value. This keeps the boolean check state separate from the data.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You could store it in the same data structure, as a [&lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;data_value&lt;/code&gt;] nested list, or tuple if you like.&lt;/p&gt;
&lt;h3&gt;Returning the check state in &lt;code&gt;data()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When Qt asks for &lt;code&gt;Qt.ItemDataRole.CheckStateRole&lt;/code&gt;, we look up the boolean value for that cell and return either &lt;code&gt;Qt.CheckState.Checked&lt;/code&gt; or &lt;code&gt;Qt.CheckState.Unchecked&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;if role == Qt.ItemDataRole.CheckStateRole:
    checked = self._checked[index.row()][index.column()]
    if checked:
        return Qt.CheckState.Checked
    return Qt.CheckState.Unchecked
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For these &lt;em&gt;return X if True, otherwise return Y&lt;/em&gt; type returns you can also use and &lt;code&gt;X if bool else Y&lt;/code&gt; expression.&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;if role == Qt.ItemDataRole.CheckStateRole:
    checked = self._checked[index.row()][index.column()]
    return Qt.CheckState.Checked if checked else Qt.CheckState.Unchecked
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Handling user clicks in &lt;code&gt;setData()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When the user clicks a checkbox, Qt calls &lt;code&gt;setData()&lt;/code&gt; with the new value and the &lt;code&gt;Qt.ItemDataRole.CheckStateRole&lt;/code&gt; role. We compare the incoming value to &lt;code&gt;Qt.CheckState.Checked.value&lt;/code&gt; to determine whether the box was checked or unchecked, then store the result:&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 setData(self, index, value, role):
    if role == Qt.ItemDataRole.CheckStateRole:
        checked = value == Qt.CheckState.Checked.value
        self._checked[index.row()][index.column()] = checked
        self.dataChanged.emit(index, index, [role])
        return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice the &lt;code&gt;self.dataChanged.emit(...)&lt;/code&gt; call &amp;mdash; this notifies the view that the data has changed so it can redraw the cell. Always emit this signal when you modify data in &lt;code&gt;setData()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Enabling user interaction with &lt;code&gt;flags()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;flags()&lt;/code&gt; method tells Qt what the user can do with each cell. Including &lt;code&gt;Qt.ItemFlag.ItemIsUserCheckable&lt;/code&gt; is what makes the checkbox clickable:&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 flags(self, index):
    return (
        Qt.ItemFlag.ItemIsSelectable
        | Qt.ItemFlag.ItemIsEnabled
        | Qt.ItemFlag.ItemIsUserCheckable
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Without this flag, the checkbox will still appear (because you're returning data for &lt;code&gt;CheckStateRole&lt;/code&gt;), but the user won't be able to toggle it.&lt;/p&gt;
&lt;h2 id="showing-checkboxes-in-only-one-column"&gt;Showing checkboxes in only one column&lt;/h2&gt;
&lt;p&gt;In many applications, you only want checkboxes in a specific column. You can achieve this by checking &lt;code&gt;index.column()&lt;/code&gt; in your &lt;code&gt;data()&lt;/code&gt; and &lt;code&gt;flags()&lt;/code&gt; methods. For example, to show checkboxes only in column 2:&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 data(self, index, role):
    if role == Qt.ItemDataRole.DisplayRole:
        value = self._data[index.row()][index.column()]
        return str(value)

    if role == Qt.ItemDataRole.CheckStateRole:
        if index.column() == 2:
            checked = self._checked[index.row()]
            if checked:
                return Qt.CheckState.Checked
            return Qt.CheckState.Unchecked

def flags(self, index):
    flags = Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled
    if index.column() == 2:
        flags |= Qt.ItemFlag.ItemIsUserCheckable
    return flags
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this case, &lt;code&gt;self._checked&lt;/code&gt; would be a simple one-dimensional list (one boolean per row) rather than a 2D list.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;To add checkboxes to a &lt;code&gt;QTableView&lt;/code&gt; with a custom &lt;code&gt;QAbstractTableModel&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handle &lt;code&gt;Qt.ItemDataRole.CheckStateRole&lt;/code&gt; in &lt;code&gt;data()&lt;/code&gt; to display checkboxes based on boolean values.&lt;/li&gt;
&lt;li&gt;Return &lt;code&gt;Qt.ItemFlag.ItemIsUserCheckable&lt;/code&gt; from &lt;code&gt;flags()&lt;/code&gt; to make checkboxes interactive.&lt;/li&gt;
&lt;li&gt;Implement &lt;code&gt;setData()&lt;/code&gt; for &lt;code&gt;Qt.ItemDataRole.CheckStateRole&lt;/code&gt; to store the updated state when the user clicks, and emit &lt;code&gt;dataChanged&lt;/code&gt; to keep the view in sync.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach works natively with Qt's model/view architecture and avoids the complexity of writing a custom delegate. For a more complete guide to displaying data in table views &amp;mdash; including using numpy and pandas data sources &amp;mdash; see our &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;QTableView with ModelViews tutorial&lt;/a&gt;. If you want to show only an icon without text in specific cells, see &lt;a href="https://www.pythonguis.com/faq/show-only-icon-in-qtableview-cell/"&gt;how to show only an icon in a QTableView cell&lt;/a&gt;. You can also learn how to &lt;a href="https://www.pythonguis.com/tutorials/creating-your-own-custom-widgets/"&gt;create your own custom widgets&lt;/a&gt; for more advanced UI needs.&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.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="pyside6"/><category term="pyside"/><category term="qtableview"/><category term="qabstracttablemodel"/><category term="checkbox"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>What does @pyqtSlot() do? — Is the pyqtSlot decorator even necessary?</title><link href="https://www.pythonguis.com/faq/what-does-pyqtslot-do/" rel="alternate"/><published>2026-01-12T06:00:00+00:00</published><updated>2026-01-12T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2026-01-12:/faq/what-does-pyqtslot-do/</id><summary type="html">When working with Qt slots and signals in PyQt6 you will discover the &lt;code&gt;@pyqtSlot&lt;/code&gt; decorator. This decorator is used to &lt;em&gt;mark&lt;/em&gt; a Python function or method as a &lt;em&gt;slot&lt;/em&gt; to which a Qt signal can be connected. However, as you can see in our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots tutorials&lt;/a&gt; you don't &lt;em&gt;have&lt;/em&gt; to use this. Any Python function or method can be used, normally, as a slot for Qt signals. But elsewhere, in our &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;threading tutorials&lt;/a&gt; we &lt;em&gt;do&lt;/em&gt; use it.</summary><content type="html">
            &lt;p&gt;When working with Qt slots and signals in PyQt6 you will discover the &lt;code&gt;@pyqtSlot&lt;/code&gt; decorator. This decorator is used to &lt;em&gt;mark&lt;/em&gt; a Python function or method as a &lt;em&gt;slot&lt;/em&gt; to which a Qt signal can be connected. However, as you can see in our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots tutorials&lt;/a&gt; you don't &lt;em&gt;have&lt;/em&gt; to use this. Any Python function or method can be used, normally, as a slot for Qt signals. But elsewhere, in our &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;threading tutorials&lt;/a&gt; we &lt;em&gt;do&lt;/em&gt; use it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What's going on here?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why do you sometimes use &lt;code&gt;@pyqtSlot&lt;/code&gt; but usually not?&lt;/li&gt;
&lt;li&gt;What happens when you omit the &lt;code&gt;@pyqtSlot&lt;/code&gt; decorator?&lt;/li&gt;
&lt;li&gt;Are there times when &lt;code&gt;@pyqtSlot&lt;/code&gt; is &lt;em&gt;required&lt;/em&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-does-the-pyqt6-documentation-say-about-pyqtslot"&gt;What does the PyQt6 documentation say about @pyqtSlot?&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html#the-pyqtslot-decorator"&gt;PyQt6 documentation&lt;/a&gt; has a good explanation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Although PyQt6 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt6 provides the &lt;code&gt;pyqtSlot()&lt;/code&gt; function decorator to do this.&lt;/p&gt;
&lt;p&gt;Connecting a signal to a decorated Python method has the advantage of reducing the amount of memory used and is slightly faster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From the above we see that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any Python callable can be used as a slot when connecting signals.&lt;/li&gt;
&lt;li&gt;It is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it.&lt;/li&gt;
&lt;li&gt;There is a side-benefit in that marking a function or method with &lt;code&gt;pyqtSlot()&lt;/code&gt; reduces the amount of memory used, and makes the slot faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-is-pyqtslot-necessary"&gt;When is @pyqtSlot necessary?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Sometimes necessary&lt;/em&gt; is a bit vague. In practice the only situation where you &lt;em&gt;need&lt;/em&gt; to use &lt;code&gt;@pyqtSlot&lt;/code&gt; decorators is when working with threads. This is because of a difference in how signal connections are handled in decorated vs. undecorated slots.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you decorate a method with &lt;code&gt;@pyqtSlot&lt;/code&gt; then that slot is created as a native Qt slot, and behaves identically to a C++ slot.&lt;/li&gt;
&lt;li&gt;If you don't decorate the method then PyQt6 will create a "proxy" object wrapper which provides a native slot to Qt.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In normal use this is fine, aside from the performance impact (see below). But when working with threads, there is a complication: is the proxy object created on the GUI thread or on the runner thread? If it ends up on the wrong thread, this can lead to segmentation faults. Using the &lt;code&gt;@pyqtSlot&lt;/code&gt; decorator side-steps this issue, because no proxy is created.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  When updating my &lt;a href="/pyqt6-book/"&gt;PyQt6 book&lt;/a&gt; I wondered -- &lt;em&gt;is this still necessary?!&lt;/em&gt; -- and tested removing it from the examples. Many examples continue to work, but some failed. To be safe, always use &lt;code&gt;@pyqtSlot&lt;/code&gt; decorators on your &lt;code&gt;QRunnable.run&lt;/code&gt; methods.&lt;/p&gt;
&lt;h2 id="pyqtslot-performance-does-it-make-a-difference"&gt;@pyqtSlot performance: does it make a difference?&lt;/h2&gt;
&lt;p&gt;The PyQt6 documentation notes that using native slots "has the advantage of reducing the amount of memory used and is slightly faster". But how much faster is it really, and does decorating slots actually save much memory?&lt;/p&gt;
&lt;p&gt;We can test this directly by using &lt;a href="https://github.com/schollii/sandals/blob/master/pyqt5_connections_mem_speed.py"&gt;this script from Oliver L Schoenborn&lt;/a&gt;. Updating for PyQt6 (replace &lt;code&gt;PyQt5&lt;/code&gt; with &lt;code&gt;PyQt6&lt;/code&gt; and it will work as-is) and running this we get the following results:&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  See the &lt;a href="https://www.codeproject.com/articles/1123088/pyqt-signal-slot-connection-performance"&gt;original results for PyQt5&lt;/a&gt; for comparison.&lt;/p&gt;
&lt;p&gt;First the results for the speed of emitting signals when connected to a decorated slot, vs. a non-decorated 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;Raw slot mean, stddev:  0.578 0.024
Pyqt slot mean, stddev: 0.587 0.021
Percent gain with pyqtSlot: -2 %
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The result shows &lt;code&gt;@pyqtSlot&lt;/code&gt; as 2% slower, but this is negligible (the original data on PyQt5 also showed no difference). So, using &lt;code&gt;@pyqtSlot&lt;/code&gt; will have no noticeable impact on the speed of signal handling in your applications.&lt;/p&gt;
&lt;p&gt;Next are the results for establishing connections. This shows the speed and memory usage of connecting to decorated vs. non-decorated slots.&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;Comparing mem and time required to create 10000000 connections, 1000 times

Measuring for 1000000 connections
              # connects     mem (bytes)          time (sec)
Raw         :   1000000      949186560 (905MB)    9.02
Pyqt Slot   :   1000000       48500736 ( 46MB)    1.52
Ratios      :                       20               6

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The results show that decorated slots are about 6x faster to connect to. This sounds like a big difference, but it would only be noticeable if an application was connecting a considerable number of signals. Based on these numbers, if you connected 100 signals the total execution time difference would be 0.9 ms vs. 0.15 ms. This is negligible, not to mention imperceptible.&lt;/p&gt;
&lt;p&gt;Perhaps more significant is that using raw connections uses 20x the memory of decorated connections. Again though, bear in mind that for a more realistic upper limit of connections (100) the actual difference here is 0.09MB vs. 0.004MB.&lt;/p&gt;
&lt;p&gt;The bottom line: don't expect any dramatic improvements in performance or memory usage from using &lt;code&gt;@pyqtSlot&lt;/code&gt; decorators, unless you're working with insanely large numbers of signals or making regular connections you won't see any difference at all. That said, decorating your slots is an easy win if you need it.&lt;/p&gt;
&lt;h2 id="using-pyqtslot-to-overload-signal-types"&gt;Using @pyqtSlot to overload signal types&lt;/h2&gt;
&lt;p&gt;In Qt, signals can be used to transmit more than one type of data by &lt;a href="https://doc.qt.io/qtforpython-6/tutorials/basictutorial/signals_and_slots.html#overloading-signals-and-slots"&gt;overloading signals and slots with different types&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, with the following code the &lt;code&gt;my_slot_fn&lt;/code&gt; will &lt;em&gt;only&lt;/em&gt; receive signals which match the signature of two &lt;code&gt;int&lt;/code&gt; values.&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;@pyqtSlot(int, int)
def my_slot_fn(a, b):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is a legacy of Qt5 and not recommended in new code. In Qt6 all of these signals have been replaced with separate signals with distinct names for different types. I recommend you follow the same approach in your own code for the sake of simplicity.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;@pyqtSlot&lt;/code&gt; decorator can be used to mark Python functions or methods as Qt slots. This decorator is only required on slots which may be connected to across threads, for example the &lt;code&gt;run&lt;/code&gt; method of &lt;code&gt;QRunnable&lt;/code&gt; objects. For all other slots it can be omitted. There is a very small performance benefit to using it, which you may want to consider when your application makes a large number of signal-slot connections.&lt;/p&gt;
&lt;p&gt;To summarize when to use &lt;code&gt;@pyqtSlot&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Threading&lt;/strong&gt; &amp;mdash; Always use &lt;code&gt;@pyqtSlot&lt;/code&gt; on &lt;code&gt;QRunnable.run&lt;/code&gt; methods and any slot called across threads to avoid segmentation faults.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt; &amp;mdash; For most applications the performance gain is negligible, but it can help if you have thousands of signal connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signal overloading&lt;/strong&gt; &amp;mdash; You can use &lt;code&gt;@pyqtSlot&lt;/code&gt; to restrict a slot to a specific type signature, though this pattern is discouraged in Qt6.&lt;/li&gt;
&lt;/ul&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt5"/><category term="pyqt"/><category term="threading"/><category term="threads"/><category term="signals"/><category term="slots"/><category term="python"/><category term="qt"/><category term="qt6"/><category term="qt5"/></entry><entry><title>Getting Started With Flet for GUI Development — Your First Steps With the Flet Library for Desktop and Web Python GUIs</title><link href="https://www.pythonguis.com/tutorials/getting-started-flet/" rel="alternate"/><published>2025-12-15T06:00:00+00:00</published><updated>2025-12-15T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2025-12-15:/tutorials/getting-started-flet/</id><summary type="html">Getting started with a new GUI framework can feel daunting. This guide walks you through the essentials of Flet, from installation and a first app to widgets, layouts, and event handling.</summary><content type="html">
            &lt;p&gt;Getting started with a new GUI framework can feel daunting. This guide walks you through the essentials of Flet, from installation and a first app to widgets, layouts, and event handling.&lt;/p&gt;
&lt;p&gt;With Flet, you can quickly build modern, high‑performance desktop, web, and mobile interfaces using Python.&lt;/p&gt;
&lt;h2 id="what-is-flet-a-cross-platform-python-gui-framework"&gt;What Is Flet? A Cross-Platform Python GUI Framework&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://flet.dev"&gt;Flet&lt;/a&gt; is a cross-platform GUI framework for Python. It enables the development of interactive applications that run as native desktop applications on Windows, macOS, and Linux. Flet apps also run in the browser and even as mobile apps. Flet uses Flutter under the hood, providing a modern look and feel with responsive layouts.&lt;/p&gt;
&lt;p&gt;The library's key features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modern, consistent UI&lt;/strong&gt; across desktop, web, and mobile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No HTML, CSS, or JS required&lt;/strong&gt;, only write pure Python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rich set of widgets&lt;/strong&gt; for input, layout, data display, and interactivity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live reload&lt;/strong&gt; for rapid development&lt;/li&gt;
&lt;li&gt;Built-in support for &lt;strong&gt;theming&lt;/strong&gt;, &lt;strong&gt;navigation&lt;/strong&gt;, and &lt;strong&gt;responsive design&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Easy &lt;strong&gt;event handling&lt;/strong&gt; and &lt;strong&gt;state&lt;/strong&gt; management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Flet is great for building different types of GUI apps, from utilities and dashboards to data-science tools, business apps, and even educational or hobby projects.&lt;/p&gt;
&lt;h2 id="installing-flet"&gt;Installing Flet&lt;/h2&gt;
&lt;p&gt;You can install Flet from &lt;a href="https://pypi.org/project/flet/"&gt;PyPI&lt;/a&gt; using the following &lt;code&gt;pip&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;$ pip install flet
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command downloads and installs Flet into your current Python environment. That's it! You can now write your first app.&lt;/p&gt;
&lt;h2 id="writing-your-first-flet-gui-app"&gt;Writing Your First Flet GUI App&lt;/h2&gt;
&lt;p&gt;To build a Flet app, you typically follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import &lt;code&gt;flet&lt;/code&gt; and define a function that takes a &lt;code&gt;Page&lt;/code&gt; object as an argument.&lt;/li&gt;
&lt;li&gt;Add &lt;a href="https://flet.dev/docs/controls"&gt;UI controls (widgets)&lt;/a&gt; to the page.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;flet.app()&lt;/code&gt; to start the app by passing the function as an argument.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's a quick &lt;code&gt;Hello, World!&lt;/code&gt; application in Flet:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet First App"
    page.window.width = 200
    page.window.height = 100
    page.add(ft.Text("Hello, World!"))

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the &lt;code&gt;main()&lt;/code&gt; function, we get the &lt;code&gt;page&lt;/code&gt; object as an argument. This object represents the root of our GUI. Then, we set the title and window size and add a &lt;code&gt;Text&lt;/code&gt; control that displays the &lt;code&gt;"Hello, World!"&lt;/code&gt; text.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Use &lt;code&gt;page.add()&lt;/code&gt; to add controls (UI elements or widgets) to your app. To manipulate the widgets, you can use &lt;code&gt;page.controls&lt;/code&gt;, which is a list containing the controls that have been added to the page.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! Here's what your first app looks like.&lt;/p&gt;
&lt;p&gt;&lt;img alt="First Flet GUI application showing Hello World" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/first-flet-app.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/first-flet-app.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/first-flet-app.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/first-flet-app.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/first-flet-app.png?tr=w-600 600w" loading="lazy" width="400" height="200"/&gt;
&lt;em&gt;First Flet GUI application&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  You can run a Flet app as you'd run any Python app in the terminal. Additionally, Flet allows you to use the &lt;code&gt;flet run&lt;/code&gt; command for live reload during development.&lt;/p&gt;
&lt;h2 id="exploring-flet-controls-widgets"&gt;Exploring Flet Controls (Widgets)&lt;/h2&gt;
&lt;p&gt;Flet includes a wide variety of widgets, known as &lt;strong&gt;controls&lt;/strong&gt;, in several categories. Some of these categories include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/buttons"&gt;Buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/input-and-selections"&gt;Input and Selections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/information-displays"&gt;Information Displays&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/app-structure-navigation"&gt;Navigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/charts"&gt;Charts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the following sections, you'll code simple examples showcasing a sample of each category's controls.&lt;/p&gt;
&lt;h3&gt;Flet Button Controls&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://flet.dev/docs/controls/buttons"&gt;Buttons&lt;/a&gt; are key components in any GUI application. Flet has several types of buttons that we can use in different situations, including the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/filledbutton/"&gt;&lt;code&gt;FilledButton&lt;/code&gt;&lt;/a&gt;: A filled button without a shadow. Useful for important, final actions that complete a flow, like &lt;em&gt;Save&lt;/em&gt; or &lt;em&gt;Confirm&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/elevatedbutton/"&gt;&lt;code&gt;ElevatedButton&lt;/code&gt;&lt;/a&gt;: A filled tonal button with a shadow. Useful when you need visual separation from a patterned background.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/floatingactionbutton/"&gt;&lt;code&gt;FloatingActionButton&lt;/code&gt;&lt;/a&gt;: A Material Design floating action button.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that showcases these types of buttons:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Buttons Demo"
    page.window.width = 200
    page.window.height = 200

    page.add(ft.ElevatedButton("Elevated Button"))
    page.add(ft.FilledButton("Filled Button"))
    page.add(ft.FloatingActionButton(icon=ft.Icons.ADD))

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here, we call the &lt;code&gt;add()&lt;/code&gt; method on our &lt;code&gt;page&lt;/code&gt; object to add instances of &lt;code&gt;ElevatedButton&lt;/code&gt;, &lt;code&gt;FilledButton&lt;/code&gt;, and &lt;code&gt;FloatingActionButton&lt;/code&gt;. Flet arranges these controls vertically by default.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a window that looks like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet buttons demo showing ElevatedButton, FilledButton, and FloatingActionButton" src="https://www.pythonguis.com/tutorials/getting-started-flet/flet-buttons.png"/&gt;
&lt;em&gt;Flet buttons demo&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Input and Selection Controls&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://flet.dev/docs/controls/input-and-selections"&gt;Input and selection controls&lt;/a&gt; enable users to enter data or select values in your app's GUI. Flet provides several commonly used controls in this category, including the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/textfield/"&gt;&lt;code&gt;TextField&lt;/code&gt;&lt;/a&gt;: A common single-line or multi-line text entry control.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/dropdown/"&gt;&lt;code&gt;Dropdown&lt;/code&gt;&lt;/a&gt;: A selection control that lets users pick a value from a list of options.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/checkbox/"&gt;&lt;code&gt;Checkbox&lt;/code&gt;&lt;/a&gt;: A control for boolean input, often useful for preferences and agreement toggles.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/radio/"&gt;&lt;code&gt;Radio&lt;/code&gt;&lt;/a&gt;: A selection radio button control commonly used inside a &lt;code&gt;RadioGroup&lt;/code&gt; to choose a single option from a set.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/slider/"&gt;&lt;code&gt;Slider&lt;/code&gt;&lt;/a&gt;: A control for selecting a numeric value along a track.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/switch/"&gt;&lt;code&gt;Switch&lt;/code&gt;&lt;/a&gt;: A boolean on/off toggle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that showcases some of these input and selection controls:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Input and Selections Demo"
    page.window.width = 360
    page.window.height = 320

    name = ft.TextField(label="Name")
    agree = ft.Checkbox(label="I agree to the terms")
    level = ft.Slider(
        label="Experience level",
        min=0,
        max=10,
        divisions=10,
        value=5,
    )
    color = ft.Dropdown(
        label="Favorite color",
        options=[
            ft.dropdown.Option("Red"),
            ft.dropdown.Option("Green"),
            ft.dropdown.Option("Blue"),
        ],
    )
    framework = ft.RadioGroup(
        content=ft.Column(
            [
                ft.Radio(value="Flet", label="Flet"),
                ft.Radio(value="Tkinter", label="Tkinter"),
                ft.Radio(value="PyQt6", label="PyQt6"),
                ft.Radio(value="PySide6", label="PySide6"),
            ]
        )
    )
    notifications = ft.Switch(label="Enable notifications", value=True)

    page.add(
        ft.Text("Fill in the form and adjust the options:"),
        name,
        agree,
        level,
        color,
        framework,
        notifications,
    )

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After setting the window's title and size, we create several input controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;TextField&lt;/code&gt; for the user's name&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Checkbox&lt;/code&gt; to agree to the terms&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Slider&lt;/code&gt; to select an experience level from 0 to 10&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Dropdown&lt;/code&gt; to pick a favorite color&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;RadioGroup&lt;/code&gt; with several framework choices&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Switch&lt;/code&gt; to enable or disable notifications, which defaults to &lt;em&gt;ON&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We add all these controls to the &lt;code&gt;page&lt;/code&gt; using &lt;code&gt;page.add()&lt;/code&gt;, preceded by a simple instruction text. Flet lays out the controls vertically (the default) in the order you pass them.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll see a simple form that uses text input, dropdowns, checkboxes, radio buttons, sliders, and switches.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet input and selection controls demo with TextField, Dropdown, Checkbox, Radio, Slider, and Switch" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-input-selection.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-input-selection.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-input-selection.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-input-selection.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-input-selection.png?tr=w-600 600w" loading="lazy" width="720" height="1040"/&gt;
&lt;em&gt;Flet input and selection controls demo&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Navigation Controls&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://flet.dev/docs/controls/app-structure-navigation"&gt;Navigation controls&lt;/a&gt; allow users to move between different sections or views within an app. Flet provides several navigation controls, including the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/navigationbar/"&gt;&lt;code&gt;NavigationBar&lt;/code&gt;&lt;/a&gt;: A bottom navigation bar with multiple destinations, which is useful for switching between three to five primary sections of your app.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/appbar/"&gt;&lt;code&gt;AppBar&lt;/code&gt;&lt;/a&gt;: A top app bar that can display a title, navigation icon, and action buttons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that uses &lt;code&gt;NavigationBar&lt;/code&gt; to navigate between different views:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Navigation Bar Demo"
    page.window.width = 360
    page.window.height = 260

    info = ft.Text("You are on the Home tab")

    def on_nav_change(e):
        idx = page.navigation_bar.selected_index
        if idx == 0:
            info.value = "You are on the Home tab"
        elif idx == 1:
            info.value = "You are on the Search tab"
        else:
            info.value = "You are on the Profile tab"
        page.update()

    page.navigation_bar = ft.NavigationBar(
        selected_index=0,
        destinations=[
            ft.NavigationBarDestination(icon=ft.Icons.HOME, label="Home"),
            ft.NavigationBarDestination(icon=ft.Icons.SEARCH, label="Search"),
            ft.NavigationBarDestination(icon=ft.Icons.PERSON, label="Profile"),
        ],
        on_change=on_nav_change,
    )

    page.add(
        ft.Container(content=info, alignment=ft.alignment.center, padding=20),
    )

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;NavigationBar&lt;/code&gt; has three tabs: &lt;strong&gt;Home&lt;/strong&gt;, &lt;strong&gt;Search&lt;/strong&gt;, and &lt;strong&gt;Profile&lt;/strong&gt;, each with a representative icon that you provide using &lt;code&gt;ft.Icons&lt;/code&gt;. Assigning this bar to &lt;code&gt;page.navigation_bar&lt;/code&gt; tells Flet to display it as the app's bottom navigation component.&lt;/p&gt;
&lt;p&gt;The behavior of the bar is controlled by the &lt;code&gt;on_nav_change()&lt;/code&gt; callback (more on this in the section on events and callbacks). Whenever the user clicks a tab, Flet calls &lt;code&gt;on_nav_change()&lt;/code&gt;, which updates the text with the appropriate message.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! Click the different tabs to see the text on the page update as you navigate between sections.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet navigation bar demo with Home, Search, and Profile tabs" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-navigation-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-navigation-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-navigation-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-navigation-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-navigation-bar.png?tr=w-600 600w" loading="lazy" width="720" height="520"/&gt;
&lt;em&gt;Flet navigation bar demo&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Information Display Controls&lt;/h3&gt;
&lt;p&gt;We can use &lt;a href="https://flet.dev/docs/controls/information-displays"&gt;information-display controls&lt;/a&gt; to present content to the user, such as text, images, and rich list items. These controls help communicate status, context, and details without requiring user input.&lt;/p&gt;
&lt;p&gt;Some common information-display controls include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/text/"&gt;&lt;code&gt;Text&lt;/code&gt;&lt;/a&gt;: The basic control for showing labels, paragraphs, and other readable text.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/image/"&gt;&lt;code&gt;Image&lt;/code&gt;&lt;/a&gt;: A control for displaying images from files, assets, or URLs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that combines these controls:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Information Displays Demo"
    page.window.width = 340
    page.window.height = 400

    header = ft.Text("Latest image", size=18)

    hero = ft.Image(
        src="https://picsum.photos/320/320",
        width=320,
        height=320,
        fit=ft.ImageFit.COVER,
    )

    page.add(
        header,
        hero,
    )

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;code&gt;main()&lt;/code&gt;, we create a &lt;code&gt;Text&lt;/code&gt; widget called &lt;code&gt;header&lt;/code&gt; to show &lt;code&gt;"Latest image"&lt;/code&gt; with a larger font size. The &lt;code&gt;hero&lt;/code&gt; variable is an &lt;code&gt;Image&lt;/code&gt; control that loads an image from the URL &lt;a href="https://picsum.photos/320/320"&gt;https://picsum.photos/320/320&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We use a fixed &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; together with &lt;code&gt;ImageFit.COVER&lt;/code&gt; so that the image fills its box while preserving aspect ratio and cropping if needed.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll see some text and a random image from &lt;a href="https://picsum.photos/"&gt;Picsum.photos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet information display demo showing Text and Image controls" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-info-display.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-info-display.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-info-display.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-info-display.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-info-display.png?tr=w-600 600w" loading="lazy" width="680" height="800"/&gt;
&lt;em&gt;Flet information display demo&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Dialogs, Alerts, and Panels&lt;/h3&gt;
&lt;p&gt;Dialogs, alerts, and panels enable you to draw attention to important information or reveal additional details without leaving the current screen. They are useful for confirmations, warnings, and expandable content.&lt;/p&gt;
&lt;p&gt;Some useful controls in this category are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/alertdialog/"&gt;&lt;code&gt;AlertDialog&lt;/code&gt;&lt;/a&gt;: A modal dialog that asks the user to acknowledge information or make a decision.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/banner/"&gt;&lt;code&gt;Banner&lt;/code&gt;&lt;/a&gt;: A prominent message bar displayed at the top of the page for important, non-modal information.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/datepicker/"&gt;&lt;code&gt;DatePicker&lt;/code&gt;&lt;/a&gt;: A control that lets the user pick a calendar date in a pop-up dialog.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/timepicker/"&gt;&lt;code&gt;TimePicker&lt;/code&gt;&lt;/a&gt;: A control for selecting a time of day from a dialog-style picker.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that shows an alert dialog to ask for exit confirmation:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Dialog Demo"
    page.window.width = 300
    page.window.height = 300

    def on_dlg_button_click(e):
        if e.control.text == "Yes":
            page.window.close()
        page.close(dlg_modal)

    dlg_modal = ft.AlertDialog(
        modal=True,
        title=ft.Text("Confirmation"),
        content=ft.Text("Do you want to exit?"),
        actions=[
            ft.TextButton("Yes", on_click=on_dlg_button_click),
            ft.TextButton("No", on_click=on_dlg_button_click),
        ],
        actions_alignment=ft.MainAxisAlignment.END,
    )

    page.add(
        ft.ElevatedButton(
            "Exit",
            on_click=lambda e: page.open(dlg_modal),
        ),
    )

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we first create an &lt;code&gt;AlertDialog&lt;/code&gt; with a title, some content text, and two action buttons labeled &lt;em&gt;Yes&lt;/em&gt; and &lt;em&gt;No&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;on_dlg_button_click()&lt;/code&gt; callback checks which button was clicked and closes the application window if the user selects &lt;em&gt;Yes&lt;/em&gt;. The page shows a single &lt;em&gt;Exit&lt;/em&gt; button that opens the dialog. After the user responds, the dialog is closed.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! Try clicking the button to open the dialog. You'll see a window similar to the one shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet AlertDialog demo with confirmation prompt" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-dialog.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-dialog.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-dialog.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-dialog.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-dialog.png?tr=w-600 600w" loading="lazy" width="600" height="600"/&gt;
&lt;em&gt;Flet dialog demo&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="laying-out-your-gui-with-flet-layouts"&gt;Laying Out Your GUI With Flet Layouts&lt;/h2&gt;
&lt;p&gt;Controls in this category are often described as &lt;strong&gt;container controls&lt;/strong&gt; that can hold child controls. These controls enable you to arrange widgets on an app's GUI to create a well-organized and functional interface.&lt;/p&gt;
&lt;p&gt;Flet has many container controls. Here are some of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/page/"&gt;&lt;code&gt;Page&lt;/code&gt;&lt;/a&gt;: This control is the &lt;strong&gt;root&lt;/strong&gt; of the control hierarchy or tree. It is also listed as an adaptive container control.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/column/"&gt;&lt;code&gt;Column&lt;/code&gt;&lt;/a&gt;: A container control used to arrange child controls in a column.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/row/"&gt;&lt;code&gt;Row&lt;/code&gt;&lt;/a&gt;: A container control used to arrange child controls horizontally in a row.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/container/"&gt;&lt;code&gt;Container&lt;/code&gt;&lt;/a&gt;: A container control that allows you to modify its size (e.g., &lt;code&gt;height&lt;/code&gt;) and appearance.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/stack/"&gt;&lt;code&gt;Stack&lt;/code&gt;&lt;/a&gt;: A container control where properties like &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, and &lt;code&gt;top&lt;/code&gt; allow you to place children in specific positions.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flet.dev/docs/controls/card/"&gt;&lt;code&gt;Card&lt;/code&gt;&lt;/a&gt;: A container control with slightly rounded corners and an elevation shadow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, Flet stacks widgets vertically using the &lt;code&gt;Column&lt;/code&gt; container. Here's an example that demonstrates basic layout options in Flet:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Layouts Demo"
    page.window.width = 250
    page.window.height = 300

    main_layout = ft.Column(
        [
            ft.Text("1) Vertical layout:"),
            ft.ElevatedButton("Top"),
            ft.ElevatedButton("Middle"),
            ft.ElevatedButton("Bottom"),
            ft.Container(height=12),  # Spacer

            ft.Text("2) Horizontal layout:"),
            ft.Row(
                [
                    ft.ElevatedButton("Left"),
                    ft.ElevatedButton("Center"),
                    ft.ElevatedButton("Right"),
                ]
            ),
        ],
    )

    page.add(main_layout)

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we use a &lt;code&gt;Column&lt;/code&gt; object as the app's main layout. This layout stacks text labels and buttons vertically, while the inner &lt;code&gt;Row&lt;/code&gt; object arranges three buttons horizontally. The &lt;code&gt;Container&lt;/code&gt; object with a fixed &lt;code&gt;height&lt;/code&gt; acts as a spacer between the vertical and horizontal sections.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a window like the one shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet layouts demo with Column and Row containers" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-layouts.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-layouts.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-layouts.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-layouts.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-layouts.png?tr=w-600 600w" loading="lazy" width="500" height="600"/&gt;
&lt;em&gt;Flet layouts demo&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="handling-events-and-callbacks-in-flet"&gt;Handling Events and Callbacks in Flet&lt;/h2&gt;
&lt;p&gt;Flet uses &lt;strong&gt;event handlers&lt;/strong&gt; to manage user interactions and perform actions. Most controls accept an &lt;code&gt;on_*&lt;/code&gt; argument, such as &lt;code&gt;on_click&lt;/code&gt; or &lt;code&gt;on_change&lt;/code&gt;, which you can set to a Python function or other callable that will be invoked when an event occurs on the target widget.&lt;/p&gt;
&lt;p&gt;The example below provides a text input and a button. When you click the button, it opens a dialog displaying the input text:&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 flet as ft

def main(page: ft.Page):
    page.title = "Flet Event &amp;amp; Callback Demo"
    page.window.width = 340
    page.window.height = 360

    def on_click(e):  # Event handler or callback function
        dialog_text.value = f'You typed: "{txt_input.value}"'
        page.open(dialog)
        page.update()

    txt_input = ft.TextField(label="Type something and press Click Me!")
    btn = ft.ElevatedButton("Click Me!", on_click=on_click)
    dialog_text = ft.Text("")
    dialog = ft.AlertDialog(
        modal=True,
        title=ft.Text("Dialog"),
        content=dialog_text,
        actions=[ft.TextButton("OK", on_click=lambda e: page.close(dialog))],
        open=False,
    )

    page.add(
        txt_input,
        btn,
    )

ft.app(target=main)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you click the button, the &lt;code&gt;on_click()&lt;/code&gt; handler or callback function is automatically called. It sets the dialog's text and opens the dialog. The dialog has an &lt;em&gt;OK&lt;/em&gt; button that closes it by calling &lt;code&gt;page.close(dialog)&lt;/code&gt;.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a window like the one shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Flet event handling and callback demo with TextField and AlertDialog" src="https://www.pythonguis.com/static/tutorials/flet/getting-started-flet/flet-event-callback.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-event-callback.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-event-callback.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-event-callback.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/flet/getting-started-flet/flet-event-callback.png?tr=w-600 600w" loading="lazy" width="680" height="720"/&gt;
&lt;em&gt;Flet event handling and callback demo&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To see this app in action, type some text into the input and click the &lt;em&gt;Click Me!&lt;/em&gt; button.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Flet offers a powerful and modern toolkit for developing GUI applications in Python. It allows you to create desktop and web GUIs from a single codebase. In this tutorial, you've learned the basics of using Flet for desktop apps, including controls, layouts, and event handling.&lt;/p&gt;
&lt;p&gt;Now that you understand the fundamentals of Flet, try building your first web app and experimenting with widgets, callbacks, layouts, and more advanced features like theming and navigation.&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="flet"/><category term="python"/><category term="widget"/><category term="layout"/><category term="getting-started"/><category term="application"/><category term="cross-platform"/></entry><entry><title>Getting Started With NiceGUI for Web UI Development in Python — Your First Steps With the NiceGUI Library for Web UI Development</title><link href="https://www.pythonguis.com/tutorials/getting-started-nicegui/" rel="alternate"/><published>2025-11-27T06:00:00+00:00</published><updated>2025-11-27T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2025-11-27:/tutorials/getting-started-nicegui/</id><summary type="html">NiceGUI is a Python library that allows developers to create interactive web applications with minimal effort. It's intuitive and easy to use. It provides a high-level interface for building modern web-based graphical user interfaces (GUIs) without requiring deep knowledge of web technologies like HTML, CSS, or JavaScript.</summary><content type="html">
            &lt;p&gt;NiceGUI is a Python library that allows developers to create interactive web applications with minimal effort. It's intuitive and easy to use. It provides a high-level interface for building modern web-based graphical user interfaces (GUIs) without requiring deep knowledge of web technologies like HTML, CSS, or JavaScript.&lt;/p&gt;
&lt;p&gt;In this tutorial, you'll learn how to use NiceGUI to develop web apps with Python. You'll begin with an introduction to NiceGUI and its capabilities. Then, you'll learn how to create a simple NiceGUI app in Python and explore the basics of the framework's elements. Finally, you'll use NiceGUI to handle events and customize your app's appearance.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  To get the most out of this tutorial, you should have a basic knowledge of Python. Familiarity with general GUI programming concepts, such as event handling, widgets, and layouts, will also be beneficial.&lt;/p&gt;
&lt;h2 id="installing-nicegui"&gt;Installing NiceGUI&lt;/h2&gt;
&lt;p&gt;Before using any third-party library like &lt;a href="https://nicegui.io/documentation"&gt;NiceGUI&lt;/a&gt;, you must install it in your working environment. Installing NiceGUI is as quick as running the &lt;code&gt;python -m pip install nicegui&lt;/code&gt; command in your terminal or command line. This command will install the library from the &lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's a good practice to use a Python &lt;a href="https://docs.python.org/3/tutorial/venv.html"&gt;virtual environment&lt;/a&gt; to manage dependencies for your project. To create and activate a virtual environment, open a command line or terminal window and run the following commands in your working directory:&lt;/p&gt;
&lt;p&gt;```sh:Windows
PS&amp;gt; python -m venv .\venv
PS&amp;gt; .\venv\Scripts\activate&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;
```sh:macOS
$ python -m venv venv/
$ source venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;```sh:Linux
$ python3 -m venv venv/
$ source venv/bin/activate&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;
The first command will create a folder called `venv/` containing a Python virtual environment. The Python version in this environment will match the version you have installed on your system.

Once your virtual environment is active, install NiceGUI by running:

```sh
(venv) $ python -m pip install nicegui
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With this command, you've installed NiceGUI in your active Python virtual environment and are ready to start building applications.&lt;/p&gt;
&lt;h2 id="writing-your-first-nicegui-app-in-python"&gt;Writing Your First NiceGUI App in Python&lt;/h2&gt;
&lt;p&gt;Let's create our first app with NiceGUI and Python. We'll display the traditional &lt;code&gt;"Hello, World!"&lt;/code&gt; message in a web browser. To create a minimal NiceGUI app, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import the &lt;code&gt;ui&lt;/code&gt; object from &lt;code&gt;nicegui&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a GUI element.&lt;/li&gt;
&lt;li&gt;Run the application using the &lt;code&gt;run()&lt;/code&gt; method.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a Python file named &lt;code&gt;app.py&lt;/code&gt; and add the following code:&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 nicegui import ui

ui.label('Hello, World!').classes('text-h1')

ui.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code defines a web application whose UI consists of a label showing the &lt;code&gt;Hello, World!&lt;/code&gt; message. To create the label, we use the &lt;code&gt;ui.label&lt;/code&gt; element. The call to &lt;code&gt;ui.run()&lt;/code&gt; starts the app.&lt;/p&gt;
&lt;p&gt;Run the application by executing the following command in your terminal:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;(venv) $ python app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This will open your default browser, showing a page like the one below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="First NiceGUI Application" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/first-nicegui-app.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/first-nicegui-app.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/first-nicegui-app.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/first-nicegui-app.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/first-nicegui-app.png?tr=w-600 600w" loading="lazy" width="2048" height="1152"/&gt;
&lt;em&gt;First NiceGUI Application&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Congratulations! You've just written your first NiceGUI web app using Python. The next step is to explore some features of NiceGUI that will allow you to create fully functional web applications.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  If the above command doesn't open the app in your browser, navigate to &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="exploring-nicegui-graphical-elements"&gt;Exploring NiceGUI Graphical Elements&lt;/h2&gt;
&lt;p&gt;NiceGUI &lt;strong&gt;elements&lt;/strong&gt; are the building blocks that we'll arrange to create pages. They represent UI components like buttons, labels, text inputs, and more. The elements are classified into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/section_text_elements"&gt;Text elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/section_controls"&gt;Controls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/section_data_elements"&gt;Data elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/section_audiovisual_elements"&gt;Audiovisual elements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the following sections, you'll code simple examples showcasing a sample of each category's graphical elements.&lt;/p&gt;
&lt;h3&gt;Text Elements&lt;/h3&gt;
&lt;p&gt;NiceGUI has a rich set of &lt;strong&gt;text elements&lt;/strong&gt; that allow you to display text in several ways. This set includes some of the following elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/label"&gt;Labels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/link"&gt;Links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/chat_message"&gt;Chat messages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/markdown"&gt;Markdown containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/restructured_text"&gt;reStructuredText containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/html"&gt;HTML text&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following demo app shows how to create some of these text elements:&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 nicegui import ui

# Text elements
ui.label("Label")

ui.link("PythonGUIs", "https://pythonguis.com")

ui.chat_message("Hello, World!", name="PythonGUIs Chatbot")

ui.markdown(
    """
# Markdown Heading 1
**bold text**
*italic text*
`code`
"""
)

ui.restructured_text(
    """
==========================
reStructuredText Heading 1
==========================
**bold text**
*italic text*
``code``
"""
)

ui.html("&amp;lt;strong&amp;gt;bold text using HTML tags&amp;lt;/strong&amp;gt;")

ui.run(title="NiceGUI Text Elements")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create a simple web interface showcasing various NiceGUI text elements. The page shows several text elements, including a basic label, a hyperlink, a chatbot message, and formatted text using the Markdown and reStructuredText markup languages. Finally, it shows some raw HTML.&lt;/p&gt;
&lt;p&gt;Each text element allows us to present textual content on the page in a specific way or format, which gives us a lot of flexibility for designing modern web UIs.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; Your browser will open with a page that looks like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Text Elements Demo App in NiceGUI" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/text-elements-nicegui.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/text-elements-nicegui.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/text-elements-nicegui.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/text-elements-nicegui.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/text-elements-nicegui.png?tr=w-600 600w" loading="lazy" width="2048" height="1152"/&gt;
&lt;em&gt;Text Elements Demo App in NiceGUI&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Control Elements&lt;/h3&gt;
&lt;p&gt;When it comes to &lt;strong&gt;control elements&lt;/strong&gt;, NiceGUI offers a variety of them. As their name suggests, these elements allow us to control how our web UI behaves. Here are some of the most common control elements available in NiceGUI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/button"&gt;Buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/button_dropdown"&gt;Dropdown lists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/toggle"&gt;Toggle buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/radio"&gt;Radio buttons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/checkbox"&gt;Checkboxes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/slider"&gt;Sliders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/switch"&gt;Switches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/input"&gt;Text inputs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/textarea"&gt;Text areas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/date"&gt;Date input&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The demo app below showcases some of these NiceGUI control elements:&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 nicegui import ui

# Control elements
ui.button("Button")

with ui.dropdown_button("Edit", icon="edit", auto_close=True):
    ui.item("Copy")
    ui.item("Paste")
    ui.item("Cut")

ui.toggle(["ON", "OFF"], value="ON")

ui.radio(["NiceGUI", "PyQt6", "PySide6"], value="NiceGUI").props("inline")

ui.checkbox("Enable Feature")

ui.slider(min=0, max=100, value=50, step=5)

ui.switch("Dark Mode")

ui.input("Your Name")

ui.number("Age", min=0, max=120, value=25, step=1)

ui.date(value="2025-04-11")

ui.run(title="NiceGUI Control Elements")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this app, we include several control elements: a button, a dropdown menu with editing options (&lt;em&gt;Copy&lt;/em&gt;, &lt;em&gt;Paste&lt;/em&gt;, &lt;em&gt;Cut&lt;/em&gt;), and a toggle switch between &lt;em&gt;ON&lt;/em&gt; and &lt;em&gt;OFF&lt;/em&gt; states. We also have a radio button group to choose between GUI frameworks (NiceGUI, &lt;a href="https://www.pythonguis.com/pyqt6/"&gt;PyQt6&lt;/a&gt;, &lt;a href="https://www.pythonguis.com/pyside6/"&gt;PySide6&lt;/a&gt;), a checkbox labeled &lt;em&gt;Enable Feature&lt;/em&gt;, and a slider to select a numeric value within a range.&lt;/p&gt;
&lt;p&gt;Further down, we have a switch to toggle &lt;em&gt;Dark Mode&lt;/em&gt;, a text input field for entering a name, a number input for providing age, and a date picker. Each of these controls has its own properties and methods that you can tweak to customize your web interfaces using Python and NiceGUI.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  Note that the elements in this app don't perform any actions. Later in this tutorial, you'll learn about events and actions. For now, we're just showcasing some of the available graphical elements in NiceGUI.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a page that will look something like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Control Elements Demo App in NiceGUI" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/control-elements-nicegui.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/control-elements-nicegui.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/control-elements-nicegui.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/control-elements-nicegui.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/control-elements-nicegui.png?tr=w-600 600w" loading="lazy" width="1150" height="1620"/&gt;
&lt;em&gt;Control Elements Demo App in NiceGUI&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Data Elements&lt;/h3&gt;
&lt;p&gt;If you're in the data science field, then you'll be thrilled with the variety of &lt;strong&gt;data elements&lt;/strong&gt; that NiceGUI offers. You'll find elements for some of the following tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Representing data in a &lt;a href="https://nicegui.io/documentation/table"&gt;tabular&lt;/a&gt; format&lt;/li&gt;
&lt;li&gt;Creating &lt;a href="https://nicegui.io/documentation/pyplot"&gt;plots&lt;/a&gt; and &lt;a href="https://nicegui.io/documentation/highchart"&gt;charts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Building different types of &lt;a href="https://nicegui.io/documentation/linear_progress"&gt;progress&lt;/a&gt; charts&lt;/li&gt;
&lt;li&gt;Displaying &lt;a href="https://nicegui.io/documentation/scene"&gt;3D objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Using &lt;a href="https://nicegui.io/documentation/leaflet"&gt;maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Creating &lt;a href="https://nicegui.io/documentation/tree"&gt;tree&lt;/a&gt; and &lt;a href="https://nicegui.io/documentation/log"&gt;log views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Presenting and &lt;a href="https://nicegui.io/documentation/editor"&gt;editing&lt;/a&gt; text in different formats, including plain text, &lt;a href="https://nicegui.io/documentation/code"&gt;code&lt;/a&gt;, and &lt;a href="https://nicegui.io/documentation/json_editor"&gt;JSON&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a NiceGUI app where we use a table and a Matplotlib plot to present temperature measurements against time:&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 matplotlib import pyplot as plt
from nicegui import ui

# Data elements
time = [1, 2, 3, 4, 5, 6]
temperature = [30, 32, 34, 32, 33, 31]

columns = [
    {
        "name": "time",
        "label": "Time (min)",
        "field": "time",
        "sortable": True,
        "align": "right",
    },
    {
        "name": "temperature",
        "label": "Temperature (&amp;deg;C)",
        "field": "temperature",
        "required": True,
        "align": "right",
    },
]
rows = [{"time": t, "temperature": temp} for t, temp in zip(time, temperature)]

ui.table(columns=columns, rows=rows, row_key="time")

with ui.pyplot(figsize=(5, 4)):
    plt.plot(time, temperature, "-o", color="blue", label="Temperature")
    plt.title("Temperature vs Time")
    plt.xlabel("Time (min)")
    plt.ylabel("Temperature (&amp;deg;C)")
    plt.ylim(25, 40)
    plt.legend()

ui.run(title="NiceGUI Data Elements")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create a web interface that displays a table and a line plot. The data is stored in two lists: one for time (in minutes) and one for temperature (in degrees Celsius). These values are formatted into a table with columns for time and temperature. To render the table, we use the &lt;code&gt;ui.table&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;Below the table, we create a &lt;a href="https://www.pythonguis.com/topics/matplotlib/"&gt;Matplotlib&lt;/a&gt; plot of temperature versus time and embed it in the &lt;code&gt;ui.pyplot&lt;/code&gt; element. The plot has a title, axis labels, and a legend.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a page that looks something like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Data Elements Demo App in NiceGUI" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/data-elements-nicegui.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/data-elements-nicegui.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/data-elements-nicegui.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/data-elements-nicegui.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/data-elements-nicegui.png?tr=w-600 600w" loading="lazy" width="1150" height="1612"/&gt;
&lt;em&gt;Data Elements Demo App in NiceGUI&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Audiovisual Elements&lt;/h3&gt;
&lt;p&gt;NiceGUI also has elements that allow us to display audiovisual content in our web UIs. The audiovisual content may include some of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/image"&gt;Images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/audio"&gt;Audio files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/video"&gt;Videos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/icon"&gt;Icons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/avatar"&gt;Avatars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/section_audiovisual_elements#svg"&gt;Scalable vector graphics (SVG)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below is a small demo app that shows how to add a local image to your NiceGUI-based web application:&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 nicegui import ui

with ui.image("./otje.jpg"):
    ui.label("Otje the cat!").classes("absolute-bottom text-subtitle2 text-center")

ui.run(title="NiceGUI Audiovisual Elements")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we use the &lt;code&gt;ui.image&lt;/code&gt; element to display a local image in your NiceGUI app. The image will show a subtitle at the bottom.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  NiceGUI elements provide the &lt;code&gt;classes()&lt;/code&gt; method, which allows you to apply &lt;a href="https://v3.tailwindcss.com/"&gt;Tailwind&lt;/a&gt; CSS classes to the target element. To learn more about using CSS for styling your NiceGUI apps, check the &lt;a href="https://nicegui.io/documentation/section_styling_appearance"&gt;Styling &amp;amp; Appearance&lt;/a&gt; section in the official documentation.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a page that looks something like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Audiovisual Elements Demo App in NiceGUI" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/audiovisual-elements-nicegui.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/audiovisual-elements-nicegui.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/audiovisual-elements-nicegui.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/audiovisual-elements-nicegui.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/audiovisual-elements-nicegui.png?tr=w-600 600w" loading="lazy" width="2048" height="1152"/&gt;
&lt;em&gt;Audiovisual Elements Demo App in NiceGUI&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="laying-out-pages-in-nicegui"&gt;Laying Out Pages in NiceGUI&lt;/h2&gt;
&lt;p&gt;Laying out a GUI so that every graphical component is in the right place is a fundamental step in any GUI project. NiceGUI offers several &lt;strong&gt;layout elements&lt;/strong&gt; that allow us to arrange graphical elements to build a nice-looking UI for our web apps.&lt;/p&gt;
&lt;p&gt;Here are some of the most common NiceGUI layout elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/card"&gt;Cards&lt;/a&gt; wrap another element in a frame.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/column"&gt;Column&lt;/a&gt; arranges elements vertically.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/row"&gt;Row&lt;/a&gt; arranges elements horizontally.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/grid"&gt;Grid&lt;/a&gt; organizes elements in a grid of rows and columns.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/list"&gt;List&lt;/a&gt; displays a list of elements.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nicegui.io/documentation/tabs"&gt;Tabs&lt;/a&gt; organize elements in dedicated tabs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You'll find several other elements that allow you to tweak how your app's UI looks. Below is a demo app that combines a few of these layout elements to create a minimal but well-organized user profile form:&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 nicegui import ui

with ui.card().classes("w-full max-w-3xl mx-auto shadow-lg"):
    ui.label("Profile Page").classes("text-xl font-bold")

    with ui.row().classes("w-full"):
        with ui.card():
            ui.image("./profile.png")

            with ui.card_section():
                ui.label("Profile Image").classes("text-center font-bold")
                ui.button("Change Image", icon="photo_camera")

        with ui.card().classes("flex-grow"):
            with ui.column().classes("w-full"):
                ui.input(placeholder="Your Name").classes("w-full")
                ui.select(["Male", "Female", "Other"]).classes("w-full")
                ui.input(placeholder="Eye Color").classes("w-full")
                ui.number(min=0, max=250, value=170, step=1).classes("w-full")
                ui.number(min=0, max=500, value=60, step=0.1).classes("w-full")

            with ui.row().classes("justify-end gap-2 q-mt-lg"):
                ui.button("Reset", icon="refresh").props("outline")
                ui.button("Save", icon="save").props("color=primary")

ui.run(title="NiceGUI Layout Elements")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this app, we create a clean, responsive profile information page using a layout based on the &lt;code&gt;ui.card&lt;/code&gt; element. We center the profile form and cap it at a maximum width for better readability on larger screens.&lt;/p&gt;
&lt;p&gt;We organize the elements into two main sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A profile image card on the left and a form area on the right. The left section displays a profile picture using the &lt;code&gt;ui.image&lt;/code&gt; element with a &lt;em&gt;Change Image&lt;/em&gt; button underneath.&lt;/li&gt;
&lt;li&gt;A series of input fields for personal information, including the name in a &lt;code&gt;ui.input&lt;/code&gt; element, the gender in a &lt;code&gt;ui.select&lt;/code&gt; element, the eye color in a &lt;code&gt;ui.input&lt;/code&gt; element, and the height and weight in &lt;code&gt;ui.number&lt;/code&gt; elements. At the bottom of the form, we add two buttons: &lt;em&gt;Reset&lt;/em&gt; and &lt;em&gt;Save&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We use consistent CSS styling throughout the layout to ensure proper spacing, shadows, and responsive controls. This ensures that the interface looks professional and works well across different screen sizes.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; Here's how the form looks in the browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Demo Profile Page Layout in NiceGUI" src="https://www.pythonguis.com/static/tutorials/nicegui/getting-started-nicegui/profile-page-layout-nicegui.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/profile-page-layout-nicegui.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/profile-page-layout-nicegui.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/profile-page-layout-nicegui.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/nicegui/getting-started-nicegui/profile-page-layout-nicegui.png?tr=w-600 600w" loading="lazy" width="2048" height="1152"/&gt;
&lt;em&gt;A Demo Profile Page Layout in NiceGUI&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="handling-events-and-actions-in-nicegui"&gt;Handling Events and Actions in NiceGUI&lt;/h2&gt;
&lt;p&gt;In NiceGUI, you can handle &lt;strong&gt;events&lt;/strong&gt; like mouse clicks, keystrokes, and similar interactions, just as you can in other Python GUI frameworks. Elements typically have arguments like &lt;code&gt;on_click&lt;/code&gt; and &lt;code&gt;on_change&lt;/code&gt; which are the most direct and convenient way to bind events to &lt;strong&gt;actions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here's a quick app that shows how to make a NiceGUI app perform actions in response to user events:&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 nicegui import ui

def on_button_click():
    ui.notify("Button was clicked!")

def on_checkbox_change(event):
    state = "checked" if event.value else "unchecked"
    ui.notify(f"Checkbox is {state}")

def on_slider_change(event):
    ui.notify(f"Slider value: {event.value}")

def on_input_change(event):
    ui.notify(f"Input changed to: {event.value}")

ui.label("Event Handling Demo")

ui.button("Click Me", on_click=on_button_click)
ui.checkbox("Check Me", on_change=on_checkbox_change)
ui.slider(min=0, max=10, value=5, on_change=on_slider_change)
ui.input("Type something", on_change=on_input_change)

ui.run(title="NiceGUI Events &amp;amp; Actions Demo")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this app, we first define four functions we'll use as actions. When we create the control elements, we use the appropriate argument to bind an event to a function. For example, in the &lt;code&gt;ui.button&lt;/code&gt; element, we use the &lt;code&gt;on_click&lt;/code&gt; argument, which makes the button call the associated function when we click it.&lt;/p&gt;
&lt;p&gt;We do something similar with the other elements, but use different arguments depending on the element's supported events.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  You can check the NiceGUI documentation for individual elements to learn about the specific events they can handle.&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;on_*&lt;/code&gt; type of arguments is not the only way to bind events to actions in NiceGUI. You can also use the &lt;code&gt;on()&lt;/code&gt; method, which allows you to attach event handlers manually. This approach is handy for less common events or when you want to attach multiple handlers to a single element.&lt;/p&gt;
&lt;p&gt;Here's a quick 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;from nicegui import ui

def on_click(event):
    ui.notify("Button was clicked!")

def on_hover(event):
    ui.notify("Button was hovered!")

button = ui.button("Button")
button.on("click", on_click)
button.on("mouseover", on_hover)

ui.run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create a small web app with a single button that responds to two different events. When you &lt;em&gt;click&lt;/em&gt; the button, the &lt;code&gt;on_click()&lt;/code&gt; function triggers a notification. Similarly, when you &lt;em&gt;hover&lt;/em&gt; the mouse over the button, the &lt;code&gt;on_hover()&lt;/code&gt; function displays a notification.&lt;/p&gt;
&lt;p&gt;To bind the events to the corresponding function, we use the &lt;code&gt;on()&lt;/code&gt; method. The first argument is a string representing the name of the target event. The second argument is the function that we want to run when the event occurs.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, you've learned the basics of creating web applications with NiceGUI, a powerful Python library for building browser-based user interfaces.&lt;/p&gt;
&lt;p&gt;You've explored common NiceGUI elements including text, controls, data, and audiovisual components. You've also learned how to arrange elements using layouts like cards, rows, and columns, and how to handle user events to make your apps interactive. This gives you the foundation to build modern and interactive web interfaces entirely in Python. For further exploration and advanced features, refer to the &lt;a href="https://nicegui.io/docs"&gt;official NiceGUI documentation&lt;/a&gt;.&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="nicegui"/><category term="python"/><category term="widget"/><category term="layout"/><category term="application"/><category term="web-ui"/></entry><entry><title>Getting Started With DearPyGui for GUI Development — Your First Steps With the DearPyGui Library for Desktop Python GUIs</title><link href="https://www.pythonguis.com/tutorials/getting-started-dearpygui/" rel="alternate"/><published>2025-11-19T08:00:00+00:00</published><updated>2025-11-19T08:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2025-11-19:/tutorials/getting-started-dearpygui/</id><summary type="html">Getting started with a new GUI framework can feel daunting. This guide walks you through the essentials of &lt;strong&gt;DearPyGui&lt;/strong&gt;&amp;mdash;from installation and your first app to widgets, layouts, event handling, and plotting.</summary><content type="html">
            &lt;p&gt;Getting started with a new GUI framework can feel daunting. This guide walks you through the essentials of &lt;strong&gt;DearPyGui&lt;/strong&gt;&amp;mdash;from installation and your first app to widgets, layouts, event handling, and plotting.&lt;/p&gt;
&lt;p&gt;With DearPyGui, you can quickly build modern, high‑performance desktop interfaces using Python.&lt;/p&gt;
&lt;h2 id="what-is-dearpygui"&gt;What Is DearPyGui?&lt;/h2&gt;
&lt;p&gt;DearPyGui is a GPU‑accelerated and cross‑platform GUI framework for Python, built on &lt;a href="https://github.com/ocornut/imgui"&gt;Dear ImGui&lt;/a&gt; with a retained‑mode Python API. It renders all UI using the GPU rather than native OS widgets, ensuring consistent, high‑performance UI across Windows, Linux, macOS, and even Raspberry Pi 4.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  Note that official wheels for Raspberry Pi may lag behind. Users sometimes compile from source.&lt;/p&gt;
&lt;p&gt;DearPyGui's key features include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modern, consistent UI&lt;/strong&gt; across platforms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High performance&lt;/strong&gt; via GPU rendering and C/C++ core&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable styles/themes&lt;/strong&gt; and full developer tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Over 70 widgets&lt;/strong&gt;, including plots, node editors, and tables&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in demo app&lt;/strong&gt;, theme inspector, logging, metrics, and debugger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This Python GUI framework is ideal for building interfaces ranging from simple utilities to real-time dashboards, data‑science tools, or interactive applications.&lt;/p&gt;
&lt;h2 id="installing-dearpygui"&gt;Installing DearPyGui&lt;/h2&gt;
&lt;p&gt;You can install DearPyGui from PyPI using &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;$ pip install dearpygui
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command installs DearPyGui from &lt;a href="https://pypi.org/project/dearpygui/"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="writing-your-first-dearpygui-application"&gt;Writing Your First DearPyGui Application&lt;/h2&gt;
&lt;p&gt;In general, DearPyGui apps follow the following structure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/context.html#create-context"&gt;&lt;code&gt;dpg.create_context()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Initialize DearPyGui and call it before anything else&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/viewport.html#create-viewport"&gt;&lt;code&gt;dpg.create_viewport()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Create the main application window or viewport&lt;/li&gt;
&lt;li&gt;Define &lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/widgets.html"&gt;UI widgets&lt;/a&gt; within windows or groups &amp;mdash; Add and configure widgets and containers to build your interface&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/context.html#setup-dearpygui"&gt;&lt;code&gt;dpg.setup_dearpygui()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Set up DearPyGui internals and resources before showing the viewport&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/viewport.html#show-viewport"&gt;&lt;code&gt;dpg.show_viewport()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Make the viewport window visible to the user&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/context.html#start-dearpygui"&gt;&lt;code&gt;dpg.start_dearpygui()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Start the DearPyGui main event and render loop&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dearpygui.readthedocs.io/en/latest/documentation/functions/context.html#destroy-context"&gt;&lt;code&gt;dpg.destroy_context()&lt;/code&gt;&lt;/a&gt; &amp;mdash; Clean up and release all DearPyGui resources on exit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's a quick Hello World application displaying a window with basic widgets:&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 dearpygui.dearpygui as dpg

def main():
    dpg.create_context()
    dpg.create_viewport(title="Viewport", width=300, height=100)

    with dpg.window(label="DearPyGui Demo", width=300, height=100):
        dpg.add_text("Hello, World!")

    dpg.setup_dearpygui()
    dpg.show_viewport()
    dpg.start_dearpygui()
    dpg.destroy_context()

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Inside &lt;code&gt;main()&lt;/code&gt;, we initialize the library with &lt;code&gt;dpg.create_context()&lt;/code&gt;, create a window (viewport) via &lt;code&gt;dpg.create_viewport()&lt;/code&gt;, define the GUI, set up the library with &lt;code&gt;dpg.setup_dearpygui()&lt;/code&gt;, show the viewport with &lt;code&gt;dpg.show_viewport()&lt;/code&gt;, and run the render loop using &lt;code&gt;dpg.start_dearpygui()&lt;/code&gt;. When you close the window, &lt;code&gt;dpg.destroy_context()&lt;/code&gt; cleans up resources.&lt;/p&gt;
&lt;p&gt;You define the GUI itself inside a &lt;code&gt;dpg.window()&lt;/code&gt; context block, which parents a text item with the label "Hello, World!".&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Always follow the lifecycle order: create context &amp;rarr; viewport &amp;rarr; setup &amp;rarr; show &amp;rarr; start &amp;rarr; destroy. Otherwise, the app may crash.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! Here's what your first DearPyGui app looks like.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DearPyGui first app" src="https://www.pythonguis.com/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-first-app.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-first-app.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-first-app.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-first-app.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-first-app.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;DearPyGui first app&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="exploring-dearpygui-widgets"&gt;Exploring DearPyGui Widgets&lt;/h2&gt;
&lt;p&gt;DearPyGui includes a wide variety of widgets for building Python GUIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Basic widgets&lt;/strong&gt;, including buttons, text input, sliders, and checkboxes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containers&lt;/strong&gt; like windows, groups (horizontal and vertical grouping), tabs, collapsing headers, and menus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive widgets&lt;/strong&gt;, such as color pickers, combo boxes, tables, and menus&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's an example that showcases some commonly used DearPyGui widgets:&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 dearpygui.dearpygui as dpg

def main():
    dpg.create_context()
    dpg.create_viewport(title="Widgets Demo", width=400, height=450)

    with dpg.window(
        label="Common DearPyGui Widgets",
        width=380,
        height=420,
        pos=(10, 10),
    ):
        dpg.add_text("Static label")
        dpg.add_input_text(
            label="Text Input",
            default_value="Type some text here...",
            tag="widget_input",
        )
        dpg.add_button(label="Click Me!")
        dpg.add_checkbox(label="Check Me!")
        dpg.add_radio_button(
            ("DearPyGui", "PyQt6", "PySide6"),
        )

        dpg.add_slider_int(
            label="Int Slider",
            default_value=5,
            min_value=0,
            max_value=10,
        )
        dpg.add_slider_float(
            label="Float Slider",
            default_value=0.5,
            min_value=0.0,
            max_value=1.0,
        )

        dpg.add_combo(
            ("DearPyGui", "PyQt6", "PySide6"),
            label="GUI Library",
        )
        dpg.add_color_picker(label="Pick a Color")
        dpg.add_progress_bar(
            label="Progress",
            default_value=0.5,
            width=250,
        )

    dpg.setup_dearpygui()
    dpg.show_viewport()
    dpg.start_dearpygui()
    dpg.destroy_context()

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code uses the following functions to add the widgets to the GUI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;add_text()&lt;/code&gt;: A label for static text or instructions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_input_text()&lt;/code&gt;: A single‑line text entry field&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_button()&lt;/code&gt;: A clickable button for user actions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_checkbox()&lt;/code&gt;: A toggle for boolean values&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_radio_button()&lt;/code&gt;: A group of radio buttons for selecting one from several options&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_slider_int()&lt;/code&gt;, &lt;code&gt;add_slider_float()&lt;/code&gt;: Sliders with integer and floating-point steps&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_combo()&lt;/code&gt;: A dropdown selection widget&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_color_picker()&lt;/code&gt;: A color picker widget&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_progress_bar()&lt;/code&gt;: A progress bar widget to display visual progress&lt;/li&gt;
&lt;/ul&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! Here's what the app will look like.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DearPyGui basic widgets" src="https://www.pythonguis.com/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-basic-widgets.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-basic-widgets.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-basic-widgets.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-basic-widgets.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-basic-widgets.png?tr=w-600 600w" loading="lazy" width="800" height="1336"/&gt;
&lt;em&gt;DearPyGui basic widgets&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="laying-out-your-dearpygui-interface"&gt;Laying Out Your DearPyGui Interface&lt;/h2&gt;
&lt;p&gt;By default, DearPyGui stacks widgets vertically. However, additional positioning options include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Horizontal grouping&lt;/strong&gt; using &lt;code&gt;with dpg.group(horizontal=True):&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vertical spacing&lt;/strong&gt; using &lt;code&gt;dpg.add_spacer()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indentation&lt;/strong&gt; using the per-item &lt;code&gt;indent&lt;/code&gt; keyword argument, like in &lt;code&gt;dpg.add_checkbox(label="Option A", indent=30)&lt;/code&gt; or after creation with &lt;code&gt;dpg.configure_item(tag, indent=30)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Absolute positioning&lt;/strong&gt; via &lt;code&gt;pos=(x, y)&lt;/code&gt; when creating items, or with &lt;code&gt;dpg.set_item_pos(tag, (x, y))&lt;/code&gt; after creation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Widgets go inside containers like &lt;code&gt;dpg.window()&lt;/code&gt;. You can nest containers to build complex GUI layouts:&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 dearpygui.dearpygui as dpg

def main():
    dpg.create_context()
    dpg.create_viewport(title="Layout Demo", width=520, height=420)

    with dpg.window(
        label="Layout Demo",
        width=500,
        height=380,
        pos=(10, 10),
    ):
        dpg.add_text("1) Vertical layout:")
        dpg.add_button(label="Top")
        dpg.add_button(label="Middle")
        dpg.add_button(label="Bottom")

        dpg.add_spacer(height=12)

        dpg.add_text("2) Horizontal layout:")
        with dpg.group(horizontal=True):
            dpg.add_button(label="Left")
            dpg.add_button(label="Center")
            dpg.add_button(label="Right")

        dpg.add_spacer(height=12)

        dpg.add_text("3) Indentation:")
        dpg.add_checkbox(label="Indented at creation (30px)", indent=30)
        dpg.add_checkbox(label="Indented after creation (35px)", tag="indent_b")
        dpg.configure_item("indent_b", indent=35)

        dpg.add_spacer(height=12)

        dpg.add_text("4) Absolute positioning:")
        dpg.add_text("Positioned at creation: (x=100, y=300)", pos=(100, 300))
        dpg.add_text("Positioned after creation: (x=100, y=320)", tag="move_me")
        dpg.set_item_pos("move_me", (100, 320))

    dpg.setup_dearpygui()
    dpg.show_viewport()
    dpg.start_dearpygui()
    dpg.destroy_context()

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create an app that showcases basic layout options in DearPyGui. The first section of widgets shows the default vertical stacking by adding three buttons one after another. Then, you use &lt;code&gt;dpg.add_spacer(height=12)&lt;/code&gt; to insert vertical whitespace between sections.&lt;/p&gt;
&lt;p&gt;Then, we create a horizontal row of buttons with &lt;code&gt;dpg.group(horizontal=True)&lt;/code&gt;, which groups items side-by-side. Next, we have an indentation section that demonstrates how to indent widgets at creation (&lt;code&gt;indent=30&lt;/code&gt;) and after creation using &lt;code&gt;dpg.configure_item()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, we use absolute positioning by placing one text item at a fixed coordinate using &lt;code&gt;pos=(100, 300)&lt;/code&gt; and moving another after creation with &lt;code&gt;dpg.set_item_pos()&lt;/code&gt;. These patterns are all part of DearPyGui's container and item-configuration model, which we can use to arrange the widgets in a user-friendly GUI.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a window like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DearPyGui layouts" src="https://www.pythonguis.com/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-layouts.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-layouts.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-layouts.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-layouts.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-layouts.png?tr=w-600 600w" loading="lazy" width="1040" height="896"/&gt;
&lt;em&gt;DearPyGui layouts&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="handling-events-with-dearpygui-callbacks"&gt;Handling Events with DearPyGui Callbacks&lt;/h2&gt;
&lt;p&gt;DearPyGui uses &lt;strong&gt;callbacks&lt;/strong&gt; to handle &lt;strong&gt;events&lt;/strong&gt;. Most widgets accept a &lt;code&gt;callback&lt;/code&gt; argument, which is executed when we interact with the widget itself.&lt;/p&gt;
&lt;p&gt;The example below provides a text input and a button. When we click the button, it opens a dialog showing the input text:&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 dearpygui.dearpygui as dpg

def on_click_callback(sender, app_data, user_data):
    text = dpg.get_value("input_text")
    dpg.set_value("dialog_text", f'You typed: "{text}"')
    dpg.configure_item("dialog", show=True)

def main() -&amp;gt; None:
    dpg.create_context()
    dpg.create_viewport(title="Callback Example", width=270, height=120)

    with dpg.window(label="Callback Example", width=250, height=80, pos=(10, 10)):
        dpg.add_text("Type something and press Click Me!")
        dpg.add_input_text(label="Input", tag="input_text")
        dpg.add_button(label="Click Me!", callback=on_click_callback)
        with dpg.window(
            label="Dialog",
            modal=True,
            show=False,
            width=230,
            height=80,
            tag="dialog",
            no_close=True,
            pos=(10, 10),
        ):
            dpg.add_text("", tag="dialog_text")
            dpg.add_button(
                label="OK",
                callback=lambda s, a, u: dpg.configure_item("dialog", show=False),
            )

    dpg.setup_dearpygui()
    dpg.show_viewport()
    dpg.start_dearpygui()
    dpg.destroy_context()

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The button takes the &lt;code&gt;on_click_callback()&lt;/code&gt; callback as an argument. When we click the button, DearPyGui invokes the callback with three standard arguments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sender&lt;/code&gt;, which holds the button's ID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app_data&lt;/code&gt;, which holds extra data specific to certain widgets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user_data&lt;/code&gt;, which holds custom data you could have supplied&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Inside the callback, we pull the current text from the input widget using &lt;code&gt;dpg.get_value()&lt;/code&gt;, and finally, we display the input text in a modal window.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a window like the following.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DearPyGui callbacks" src="https://www.pythonguis.com/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-callbacks.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-callbacks.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-callbacks.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-callbacks.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-callbacks.png?tr=w-600 600w" loading="lazy" width="540" height="296"/&gt;
&lt;em&gt;DearPyGui callbacks&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To see this app in action, type some text into the input and click the &lt;em&gt;Click Me!&lt;/em&gt; button.&lt;/p&gt;
&lt;h2 id="creating-plots-and-charts-with-dearpygui"&gt;Creating Plots and Charts with DearPyGui&lt;/h2&gt;
&lt;p&gt;DearPyGui comes with powerful plotting capabilities built in. It includes high-performance plots such as lines, bars, scatter plots, and histograms. These plots allow interactive zoom and pan and real-time data updates, making them excellent for scientific visualizations and data dashboards.&lt;/p&gt;
&lt;p&gt;Here's an example of how to create a line plot using DearPyGui's plotting widgets:&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 dearpygui.dearpygui as dpg
import numpy as np

def main() -&amp;gt; None:
    dpg.create_context()
    dpg.create_viewport(title="Plotting Example", width=420, height=320)

    x = np.linspace(0, 2 * np.pi, 100)
    y1 = np.sin(x)
    y2 = np.cos(x)

    with dpg.window(label="Plot Window", width=400, height=280, pos=(10, 10)):
        with dpg.plot(label="Sine and Cosine Plot", height=200, width=360):
            dpg.add_plot_legend()
            dpg.add_plot_axis(dpg.mvXAxis, label="X")
            with dpg.plot_axis(dpg.mvYAxis, label="Y"):
                dpg.add_line_series(x.tolist(), y1.tolist(), label="sin(x)")
                dpg.add_line_series(x.tolist(), y2.tolist(), label="cos(x)")

    dpg.setup_dearpygui()
    dpg.show_viewport()
    dpg.start_dearpygui()
    dpg.destroy_context()

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create two line series: sine and cosine curves. To plot them, we use NumPy‑generated data. We also add X and Y axes, plus a legend for clarity. You can update the series in a callback for live data dashboards.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt;  Run it! You'll get a plot like the one shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="DearPyGui plotting demo" src="https://www.pythonguis.com/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-plotting.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-plotting.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-plotting.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-plotting.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/dearpygui/getting-started-dearpygui/dearpygui-plotting.png?tr=w-600 600w" loading="lazy" width="840" height="696"/&gt;
&lt;em&gt;DearPyGui plotting demo&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;DearPyGui offers a powerful and highly customizable GUI toolkit for building desktop applications in Python. With a rich widget set, interactive plotting, node editors, and built-in developer tools, it's a great choice for both simple utilities and complex interfaces.&lt;/p&gt;
&lt;p&gt;In this tutorial, you learned how to install DearPyGui, create your first application, work with widgets and layouts, handle events with callbacks, and create interactive plots. Try building your own DearPyGui app and experimenting with these features to create professional Python desktop GUIs!&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="dearpygui"/><category term="widget"/><category term="layout"/><category term="application"/><category term="python"/><category term="getting-started"/></entry><entry><title>Saving and Restoring Application Settings with QSettings in PyQt6 — Learn how to use QSettings to remember user preferences, window sizes, and configuration options between sessions</title><link href="https://www.pythonguis.com/faq/pyqt6-qsettings-how-to-use-qsettings/" rel="alternate"/><published>2025-10-09T09:00:00+00:00</published><updated>2025-10-09T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-10-09:/faq/pyqt6-qsettings-how-to-use-qsettings/</id><summary type="html">Most desktop applications need to remember things between sessions. Maybe your user picked a dark theme, resized the window, or toggled a feature on or off. Without a way to save those choices, your app would forget everything the moment it closes. That's where &lt;code&gt;QSettings&lt;/code&gt; comes in.</summary><content type="html">
            &lt;p&gt;Most desktop applications need to remember things between sessions. Maybe your user picked a dark theme, resized the window, or toggled a feature on or off. Without a way to save those choices, your app would forget everything the moment it closes. That's where &lt;code&gt;QSettings&lt;/code&gt; comes in.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QSettings&lt;/code&gt; is a class provided by Qt (and available through PyQt6) that gives you a simple, cross-platform way to store and retrieve application settings. It handles all the platform-specific details for you &amp;mdash; on Windows it uses the registry, on macOS it uses property list files, and on Linux it uses configuration files. You just read and write values, and Qt figures out the rest.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through everything you need to know to start using &lt;code&gt;QSettings&lt;/code&gt; effectively in your PyQt6 applications.&lt;/p&gt;
&lt;h2 id="creating-a-qsettings-object"&gt;Creating a QSettings Object&lt;/h2&gt;
&lt;p&gt;To use &lt;code&gt;QSettings&lt;/code&gt;, you first need to create an instance. The most common way is to pass in your organization name and application name:&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.QtCore import QSettings

settings = QSettings('MyCompany', 'MyApp')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;These two strings &amp;mdash; the organization name and the application name &amp;mdash; are used by Qt to determine where your settings are stored. They act like a namespace, keeping your app's settings separate from every other application on the system.&lt;/p&gt;
&lt;p&gt;You can check exactly where your settings file lives by printing the file path:&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;print(settings.fileName())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On Linux, this might print something like:&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;/home/username/.config/MyCompany/MyApp.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On Windows, it would point to a registry path, and on macOS, a &lt;code&gt;.plist&lt;/code&gt; file. You don't need to worry about these differences &amp;mdash; &lt;code&gt;QSettings&lt;/code&gt; handles it for you.&lt;/p&gt;
&lt;h2 id="storing-values"&gt;Storing Values&lt;/h2&gt;
&lt;p&gt;Saving a setting is as simple as calling &lt;code&gt;setValue()&lt;/code&gt; with a key and a value:&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;settings.setValue('theme', 'Dark')
settings.setValue('font_size', 14)
settings.setValue('show_toolbar', True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The key is a string that you'll use later to retrieve the value. The value can be a string, integer, boolean, list, or other common Python types. &lt;code&gt;QSettings&lt;/code&gt; will serialize it appropriately.&lt;/p&gt;
&lt;p&gt;That's it &amp;mdash; the value is saved. When you call &lt;code&gt;setValue()&lt;/code&gt;, the data is written to persistent storage (the exact timing depends on the platform, but it happens automatically).&lt;/p&gt;
&lt;h2 id="reading-values-back"&gt;Reading Values Back&lt;/h2&gt;
&lt;p&gt;To read a setting, use &lt;code&gt;value()&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;theme = settings.value('theme')
print(theme)  # 'Dark'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If the key doesn't exist (for example, the very first time your app runs), &lt;code&gt;value()&lt;/code&gt; returns &lt;code&gt;None&lt;/code&gt; by default. You can provide a default value as the second argument:&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;theme = settings.value('theme', 'Light')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now if there's no &lt;code&gt;theme&lt;/code&gt; key stored yet, you'll get &lt;code&gt;'Light'&lt;/code&gt; instead of &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Handling Types&lt;/h3&gt;
&lt;p&gt;One thing that catches people off guard: &lt;code&gt;QSettings&lt;/code&gt; stores everything as strings internally (at least when using INI-style backends on Linux). This means that when you read back a number or boolean, you might get a string instead of the type you expected.&lt;/p&gt;
&lt;p&gt;To handle this, you can pass the &lt;code&gt;type&lt;/code&gt; parameter:&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;font_size = settings.value('font_size', 14, type=int)
show_toolbar = settings.value('show_toolbar', True, type=bool)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By specifying &lt;code&gt;type=int&lt;/code&gt; or &lt;code&gt;type=bool&lt;/code&gt;, you ensure that the returned value is the correct Python type, regardless of how it was stored internally. This is especially important for booleans &amp;mdash; without the &lt;code&gt;type&lt;/code&gt; parameter, you might get the string &lt;code&gt;'true'&lt;/code&gt; instead of the boolean &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="checking-if-a-setting-exists"&gt;Checking if a Setting Exists&lt;/h2&gt;
&lt;p&gt;Before reading a value, you might want to check whether it has been set at all. Use &lt;code&gt;contains()&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;if settings.contains('theme'):
    theme = settings.value('theme')
    print(f'Found saved theme: {theme}')
else:
    print('No theme saved yet, using default')
    settings.setValue('theme', 'Light')
    theme = 'Light'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This pattern is useful when you want to distinguish between "the user explicitly set this value" and "this is just the default."&lt;/p&gt;
&lt;h2 id="a-complete-example"&gt;A Complete Example&lt;/h2&gt;
&lt;p&gt;Let's put this all together in a small PyQt6 application that remembers the window size and position, as well as a user-selected theme. When you close the app, it saves these settings. When you reopen it, everything is restored.&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, QComboBox,
    QVBoxLayout, QWidget, QLabel
)
from PyQt6.QtCore import QSettings, QSize, QPoint


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.settings = QSettings('MyCompany', 'MyApp')

        self.setWindowTitle("QSettings Demo")

        # Create a simple UI with a theme selector
        layout = QVBoxLayout()

        layout.addWidget(QLabel("Choose a theme:"))

        self.theme_combo = QComboBox()
        self.theme_combo.addItems(['Light', 'Dark', 'Blue'])
        layout.addWidget(self.theme_combo)

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

        # Restore settings
        self.load_settings()

    def load_settings(self):
        # Restore window size and position
        size = self.settings.value('window_size', QSize(400, 300))
        position = self.settings.value('window_position', QPoint(100, 100))
        self.resize(size)
        self.move(position)

        # Restore theme selection
        theme = self.settings.value('theme', 'Light')
        index = self.theme_combo.findText(theme)
        if index &amp;gt;= 0:
            self.theme_combo.setCurrentIndex(index)

    def save_settings(self):
        self.settings.setValue('window_size', self.size())
        self.settings.setValue('window_position', self.pos())
        self.settings.setValue('theme', self.theme_combo.currentText())

    def closeEvent(self, event):
        self.save_settings()
        super().closeEvent(event)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this app, move or resize the window, select a different theme from the dropdown, then close the app. When you run it again, the window should appear in the same position and size, with the same theme selected.&lt;/p&gt;
&lt;p&gt;Let's walk through what's happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In &lt;code&gt;__init__&lt;/code&gt;, we create a &lt;code&gt;QSettings&lt;/code&gt; object with our organization and app names.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;load_settings()&lt;/code&gt; reads values from persistent storage and applies them to the window and widgets. Notice how we pass default values (&lt;code&gt;QSize(400, 300)&lt;/code&gt;, &lt;code&gt;QPoint(100, 100)&lt;/code&gt;, &lt;code&gt;'Light'&lt;/code&gt;) so the app has sensible starting values on the very first run.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save_settings()&lt;/code&gt; writes the current window size, position, and theme to settings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;closeEvent()&lt;/code&gt; is a built-in Qt method that gets called when the window is about to close. We override it to save our settings right before that happens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're looking for a more robust approach to saving and restoring window geometry &amp;mdash; including handling multiple monitors and window states &amp;mdash; take a look at our dedicated guide on &lt;a href="https://www.pythonguis.com/tutorials/restore-window-geometry-pyqt/"&gt;restoring window geometry with PyQt&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="removing-settings"&gt;Removing Settings&lt;/h2&gt;
&lt;p&gt;If you need to delete a stored setting, use &lt;code&gt;remove()&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;settings.remove('theme')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This removes the &lt;code&gt;theme&lt;/code&gt; key entirely. After this, &lt;code&gt;settings.contains('theme')&lt;/code&gt; would return &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="listing-all-keys"&gt;Listing All Keys&lt;/h2&gt;
&lt;p&gt;To see everything that's currently stored, use &lt;code&gt;allKeys()&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;keys = settings.allKeys()
print(keys)  # ['theme', 'font_size', 'show_toolbar', ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This can be handy for debugging, or if you want to iterate over all settings to display them in a preferences dialog.&lt;/p&gt;
&lt;h2 id="organizing-settings-with-groups"&gt;Organizing Settings with Groups&lt;/h2&gt;
&lt;p&gt;As your app grows, you might end up with a lot of settings. &lt;code&gt;QSettings&lt;/code&gt; supports groups, which let you organize keys into sections &amp;mdash; similar to folders:&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;settings.beginGroup('appearance')
settings.setValue('theme', 'Dark')
settings.setValue('font_size', 14)
settings.endGroup()

settings.beginGroup('network')
settings.setValue('timeout', 30)
settings.setValue('retry_count', 3)
settings.endGroup()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you read them back, you use the same group:&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;settings.beginGroup('appearance')
theme = settings.value('theme', 'Light')
settings.endGroup()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Alternatively, you can use a &lt;code&gt;/&lt;/code&gt; separator in the key name as a shorthand:&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;settings.setValue('appearance/theme', 'Dark')
theme = settings.value('appearance/theme', 'Light')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Both approaches produce the same result. The slash syntax is a bit more concise, while &lt;code&gt;beginGroup()&lt;/code&gt;/&lt;code&gt;endGroup()&lt;/code&gt; is cleaner when you're reading or writing several settings in the same group at once.&lt;/p&gt;
&lt;h2 id="using-qsettings-with-setorganizationname-and-setapplicationname"&gt;Using QSettings with setOrganizationName and setApplicationName&lt;/h2&gt;
&lt;p&gt;Instead of passing the organization and app names every time you create a &lt;code&gt;QSettings&lt;/code&gt; object, you can set them once on 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;app = QApplication(sys.argv)
app.setOrganizationName('MyCompany')
app.setApplicationName('MyApp')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After this, you can create &lt;code&gt;QSettings&lt;/code&gt; objects without any arguments:&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;settings = QSettings()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;It will automatically use the organization and application names you set. This is especially convenient in larger applications where you create &lt;code&gt;QSettings&lt;/code&gt; in multiple places &amp;mdash; you only need to define the names once at startup. If you're new to building PyQt6 applications and want to understand how &lt;code&gt;QApplication&lt;/code&gt; and windows work, see our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window in PyQt6&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="managing-many-settings-with-a-dictionary"&gt;Managing Many Settings with a Dictionary&lt;/h2&gt;
&lt;p&gt;If your application has a lot of settings, checking each one individually can get repetitive. A cleaner approach is to define your defaults in a dictionary and loop through them:&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;DEFAULTS = {
    'theme': 'Light',
    'font_size': 14,
    'show_toolbar': True,
    'language': 'English',
    'auto_save': True,
    'auto_save_interval': 5,
}

settings = QSettings('MyCompany', 'MyApp')

# Load settings with defaults
config = {}
for key, default in DEFAULTS.items():
    config[key] = settings.value(key, default, type=type(default))

print(config)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By using &lt;code&gt;type=type(default)&lt;/code&gt;, each value is automatically cast to the same type as its default. This keeps everything tidy and makes it easy to add new settings later &amp;mdash; just add another entry to the dictionary.&lt;/p&gt;
&lt;h2 id="where-are-settings-stored"&gt;Where Are Settings Stored?&lt;/h2&gt;
&lt;p&gt;If you're curious about where &lt;code&gt;QSettings&lt;/code&gt; puts your data, or if you need to find the settings file for debugging, here's a quick summary:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Storage Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Registry under &lt;code&gt;HKEY_CURRENT_USER\Software\MyCompany\MyApp&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Preferences/com.mycompany.MyApp.plist&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.config/MyCompany/MyApp.conf&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You can always check the exact path using:&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;print(settings.fileName())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On Linux and macOS, the settings file is a plain text file that you can open and inspect directly, which is helpful for debugging.&lt;/p&gt;
&lt;h2 id="working-with-qstandardpaths"&gt;Working with QStandardPaths&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QSettings&lt;/code&gt; tells you where &lt;em&gt;settings&lt;/em&gt; are stored, but sometimes you need to know about other standard locations &amp;mdash; where to store cached data, application data, or downloaded files. Qt provides &lt;code&gt;QStandardPaths&lt;/code&gt; for this:&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.QtCore import QStandardPaths

# Where to store app configuration
config_path = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)
print(f'Config: {config_path}')

# Where to store app data
data_path = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
print(f'Data: {data_path}')

# Where to store cached files
cache_path = QStandardPaths.writableLocation(QStandardPaths.CacheLocation)
print(f'Cache: {cache_path}')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;QStandardPaths&lt;/code&gt; is separate from &lt;code&gt;QSettings&lt;/code&gt;, but they complement each other well. Use &lt;code&gt;QSettings&lt;/code&gt; for simple key-value preferences, and &lt;code&gt;QStandardPaths&lt;/code&gt; when you need to store actual files (databases, logs, downloaded content) in the right platform-appropriate location.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QSettings&lt;/code&gt; gives you a clean, cross-platform way to persist user preferences in your PyQt6 applications. Here's a quick recap of the essentials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;QSettings&lt;/code&gt; object with your organization and app name.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;setValue(key, value)&lt;/code&gt; to save a setting.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;value(key, default, type=...)&lt;/code&gt; to read a setting, with a default fallback and explicit type.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;contains(key)&lt;/code&gt; to check if a setting exists.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;remove(key)&lt;/code&gt; to delete a setting.&lt;/li&gt;
&lt;li&gt;Override &lt;code&gt;closeEvent()&lt;/code&gt; on your main window to save settings when the app closes.&lt;/li&gt;
&lt;li&gt;Organize related settings with groups using &lt;code&gt;beginGroup()&lt;/code&gt;/&lt;code&gt;endGroup()&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt; in key names.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you're comfortable with these basics, you'll find that &lt;code&gt;QSettings&lt;/code&gt; quietly handles one of those essential-but-tedious parts of desktop app development &amp;mdash; letting you focus on the interesting stuff. When you're ready to distribute your finished application, check out our guide on &lt;a href="https://www.pythonguis.com/tutorials/packaging-pyqt6-applications-windows-pyinstaller/"&gt;packaging PyQt6 applications with PyInstaller on Windows&lt;/a&gt; to ship your app with all its settings support intact.&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.pythonguis.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="intermediate"/><category term="settings"/><category term="qsettings"/><category term="qt"/><category term="qt6"/></entry><entry><title>How can I enable editing on a QTableView in PySide6? — Modifying your model to allow editing of your data source</title><link href="https://www.pythonguis.com/faq/editing-pyside6-tableview/" rel="alternate"/><published>2025-08-19T06:00:00+00:00</published><updated>2025-08-19T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-08-19:/faq/editing-pyside6-tableview/</id><summary type="html">In the Model-Views course, we covered &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;Displaying Tabular Data in Qt ModelViews&lt;/a&gt;. This takes a data source, such as a list of lists, a NumPy array, or a Pandas &lt;code&gt;DataFrame&lt;/code&gt;, and displays it in a &lt;code&gt;QTableView&lt;/code&gt;. But often, displaying is just the first step&amp;mdash;you also want your users to be able to add and edit the table, updating the underlying data object.</summary><content type="html">
            &lt;p&gt;In the Model-Views course, we covered &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;Displaying Tabular Data in Qt ModelViews&lt;/a&gt;. This takes a data source, such as a list of lists, a NumPy array, or a Pandas &lt;code&gt;DataFrame&lt;/code&gt;, and displays it in a &lt;code&gt;QTableView&lt;/code&gt;. But often, displaying is just the first step&amp;mdash;you also want your users to be able to add and edit the table, updating the underlying data object.&lt;/p&gt;
&lt;p&gt;Reader Vic T asked:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have been trying for a few days to get edit mode to work with a &lt;code&gt;QTableView&lt;/code&gt; using Pandas for the model via &lt;code&gt;QAbstractTableModel&lt;/code&gt;. Having searched all over the internet although, I found suggestions to implement the &lt;code&gt;flags()&lt;/code&gt; method, but it doesn't seem to work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a common question when building editable tables in PySide6. The answer involves three key methods on your &lt;code&gt;QAbstractTableModel&lt;/code&gt;: &lt;code&gt;flags()&lt;/code&gt;, &lt;code&gt;setData()&lt;/code&gt;, and an update to &lt;code&gt;data()&lt;/code&gt;. Let's walk through each one.&lt;/p&gt;
&lt;h2 id="implement-flags-to-enable-editing-on-qtableview"&gt;Implement &lt;code&gt;flags()&lt;/code&gt; to Enable Editing on QTableView&lt;/h2&gt;
&lt;p&gt;You need to implement the &lt;code&gt;flags()&lt;/code&gt; method on your model to inform Qt that your model supports &lt;em&gt;editing&lt;/em&gt;. To do this, your method needs to return the &lt;code&gt;Qt.ItemFlag.ItemIsEditable&lt;/code&gt; flag, which you &lt;em&gt;or&lt;/em&gt; together (using the pipe &lt;code&gt;|&lt;/code&gt; character) with the other flags.&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;    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="implement-setdata-to-handle-user-edits"&gt;Implement &lt;code&gt;setData()&lt;/code&gt; to Handle User Edits&lt;/h2&gt;
&lt;p&gt;However, to get the editing working, you also need to implement a &lt;code&gt;setData()&lt;/code&gt; method. This method is the model's interface between Qt and your data object. It takes care of making the changes to the data.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Remember, Qt views don't know anything about your data beyond what you tell them via the model. Likewise, they also don't know how to update your list, NumPy array, or &lt;code&gt;DataFrame&lt;/code&gt; objects with the new data that has been provided. You need to handle that yourself!&lt;/p&gt;
&lt;p&gt;Below are some example &lt;code&gt;setData()&lt;/code&gt; methods for lists of lists, NumPy, and Pandas. The only difference is how we index into the data object:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="c78dd08509834929b19c96a1d6711b5d" v-on:click="switch_tab"&gt;List&lt;/li&gt;
&lt;li class="tab-link" data-tab="0eab56784e1a4fe9b1e346271be5db15" v-on:click="switch_tab"&gt;Pandas&lt;/li&gt;
&lt;li class="tab-link" data-tab="09f69c0c1e9646dbb5f12e29aaa0ff9c" v-on:click="switch_tab"&gt;NumPy&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="c78dd08509834929b19c96a1d6711b5d"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row()][index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="0eab56784e1a4fe9b1e346271be5db15"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data.iloc[index.row(),index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="09f69c0c1e9646dbb5f12e29aaa0ff9c"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row(), index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that we first need to check whether the &lt;code&gt;role&lt;/code&gt; is &lt;code&gt;Qt.ItemDataRole.EditRole&lt;/code&gt; to determine if an edit is currently being made. After making the edit, we return &lt;code&gt;True&lt;/code&gt; to confirm this.&lt;/p&gt;
&lt;h2 id="update-data-to-show-current-values-when-editing"&gt;Update &lt;code&gt;data()&lt;/code&gt; to Show Current Values When Editing&lt;/h2&gt;
&lt;p&gt;If you try the above on your model, you should be able to edit the values. However, you'll notice that when editing, it clears the current value of the cell &amp;mdash; you have to start from an empty cell. To display the current value when editing, you need to modify the &lt;code&gt;data()&lt;/code&gt; method to return the current value when the role is &lt;code&gt;Qt.ItemDataRole.EditRole&lt;/code&gt; &lt;em&gt;as well as&lt;/em&gt; when it is &lt;code&gt;Qt.ItemDataRole.DisplayRole&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="da622554448446f28dcc8bd76709a500" v-on:click="switch_tab"&gt;List&lt;/li&gt;
&lt;li class="tab-link" data-tab="2ea51e3cb5f2424a885f0b6bde7ee10e" v-on:click="switch_tab"&gt;Pandas&lt;/li&gt;
&lt;li class="tab-link" data-tab="6cb87d00da334b3abe3c8771b1ba3bb2" v-on:click="switch_tab"&gt;NumPy&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="da622554448446f28dcc8bd76709a500"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row()][index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="2ea51e3cb5f2424a885f0b6bde7ee10e"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data.iloc[index.row(), index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="6cb87d00da334b3abe3c8771b1ba3bb2"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row(), index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That's it, you should now have a properly editable &lt;code&gt;QTableView&lt;/code&gt; in PySide6.&lt;/p&gt;
&lt;h2 id="complete-editable-qtableview-examples-in-pyside6"&gt;Complete Editable QTableView Examples in PySide6&lt;/h2&gt;
&lt;p&gt;Below are complete working examples for list, NumPy, and Pandas data sources with PySide6. You can copy and run these directly to see editable table views in action.&lt;/p&gt;
&lt;h2 id="editable-qtableview-with-a-python-list-of-lists"&gt;Editable QTableView with a Python List of Lists&lt;/h2&gt;
&lt;p&gt;The following example uses a nested Python list of lists as a data source for an editable &lt;code&gt;QTableView&lt;/code&gt; with &lt;code&gt;QAbstractTableModel&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;from PySide6.QtCore import QAbstractTableModel, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView

class ListModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        # The following takes the first sub-list, and returns
        # the length (only works if all rows are an equal length)
        return len(self._data[0])

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row()][index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row()][index.column()] = value
            return True
        return False

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = [
            [1, 9, 2],
            [1, 0, -1],
            [3, 5, 2],
            [3, 3, 2],
            [5, 8, 9],
        ]

        self.model = ListModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="editable-qtableview-with-a-pandas-dataframe"&gt;Editable QTableView with a Pandas DataFrame&lt;/h2&gt;
&lt;p&gt;The following example uses a Pandas &lt;code&gt;DataFrame&lt;/code&gt; as the data source for an editable &lt;code&gt;QTableView&lt;/code&gt;, including column headings from the DataFrame column names:&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 pandas as pd
from PySide6.QtCore import QAbstractTableModel, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView

class PandasModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data.iloc[index.row(), index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data.iloc[index.row(), index.column()] = value
            return True
        return False

    def headerData(self, col, orientation, role):
        if (
            orientation == Qt.Orientation.Horizontal
            and role == Qt.ItemDataRole.DisplayRole
        ):
            return self._data.columns[col]

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = pd.DataFrame(
            [
                [1, 9, 2],
                [1, 0, -1],
                [3, 5, 2],
                [3, 3, 2],
                [5, 8, 9],
            ],
            columns=["A", "B", "C"],
        )

        self.model = PandasModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="editable-qtableview-with-a-numpy-array"&gt;Editable QTableView with a NumPy Array&lt;/h2&gt;
&lt;p&gt;The following example uses a NumPy array as the data source for an editable &lt;code&gt;QTableView&lt;/code&gt;. The array will only accept valid values (in this case, integers) when setting, so we must first coerce the value to an integer before setting it on the array. If you enter something which isn't a valid integer (e.g. &lt;em&gt;jdskfjdskjfndsf&lt;/em&gt; ), the &lt;code&gt;int()&lt;/code&gt; call will throw a &lt;code&gt;ValueError&lt;/code&gt;, which we catch. By returning &lt;code&gt;False&lt;/code&gt; when this exception is thrown, we cancel the edit:&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 numpy as np
from PySide6.QtCore import QAbstractTableModel, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView

class NumPyModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, index):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row(), index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            try:
                value = int(value)
            except ValueError:
                return False
            self._data[index.row(), index.column()] = value
            return True
        return False

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = np.array(
            [
                [1, 9, 2],
                [1, 0, -1],
                [3, 5, 2],
                [3, 3, 2],
                [5, 8, 9],
            ]
        )

        self.model = NumPyModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;To make a &lt;code&gt;QTableView&lt;/code&gt; editable in PySide6, you need to implement three things on your &lt;code&gt;QAbstractTableModel&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flags()&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Return &lt;code&gt;Qt.ItemFlag.ItemIsEditable&lt;/code&gt; to enable cell editing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setData()&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Handle writing the new value back to your data source (list, Pandas DataFrame, or NumPy array).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;data()&lt;/code&gt; with &lt;code&gt;EditRole&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Return the current cell value for both &lt;code&gt;DisplayRole&lt;/code&gt; and &lt;code&gt;EditRole&lt;/code&gt; so the editor is pre-populated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these three methods in place, your &lt;code&gt;QTableView&lt;/code&gt; will support full inline editing of table data, regardless of the underlying data structure you're using.&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.pythonguis.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="qtableview"/><category term="model-views"/><category term="editing"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>How can I enable editing on a QTableView in PyQt6? — Modifying your model to allow editing of your data source</title><link href="https://www.pythonguis.com/faq/editing-pyqt6-tableview/" rel="alternate"/><published>2025-07-19T06:00:00+00:00</published><updated>2025-07-19T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-07-19:/faq/editing-pyqt6-tableview/</id><summary type="html">In the Model-Views course, we covered &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;Displaying Tabular Data in Qt ModelViews&lt;/a&gt;. This takes a data source, such as a list of lists, a NumPy array, or a Pandas &lt;code&gt;DataFrame&lt;/code&gt;, and displays it in a &lt;code&gt;QTableView&lt;/code&gt;. But often, displaying is just the first step&amp;mdash;you also want your users to be able to add and edit the table, updating the underlying data object.</summary><content type="html">
            &lt;p&gt;In the Model-Views course, we covered &lt;a href="https://www.pythonguis.com/tutorials/qtableview-modelviews-numpy-pandas/"&gt;Displaying Tabular Data in Qt ModelViews&lt;/a&gt;. This takes a data source, such as a list of lists, a NumPy array, or a Pandas &lt;code&gt;DataFrame&lt;/code&gt;, and displays it in a &lt;code&gt;QTableView&lt;/code&gt;. But often, displaying is just the first step&amp;mdash;you also want your users to be able to add and edit the table, updating the underlying data object.&lt;/p&gt;
&lt;p&gt;Reader Vic T asked:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have been trying for a few days to get edit mode to work with a &lt;code&gt;QTableView&lt;/code&gt; using Pandas for the model via &lt;code&gt;QAbstractTableModel&lt;/code&gt;. Having searched all over the internet although, I found suggestions to implement the &lt;code&gt;flags()&lt;/code&gt; method, but it doesn't seem to work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is correct. You need to implement the &lt;code&gt;flags()&lt;/code&gt; method on your model to inform Qt that your model supports &lt;em&gt;editing&lt;/em&gt;. To do this, your method needs to return the &lt;code&gt;Qt.ItemFlag.ItemIsEditable&lt;/code&gt; flag, which you &lt;em&gt;or&lt;/em&gt; together (using the pipe &lt;code&gt;|&lt;/code&gt; character) with the other flags.&lt;/p&gt;
&lt;p&gt;In this guide, you'll learn the three steps required to make a &lt;code&gt;QTableView&lt;/code&gt; editable in PyQt6: implementing &lt;code&gt;flags()&lt;/code&gt;, &lt;code&gt;setData()&lt;/code&gt;, and updating &lt;code&gt;data()&lt;/code&gt;. We'll cover complete working examples using Python lists, Pandas DataFrames, and NumPy arrays.&lt;/p&gt;
&lt;h2 id="implement-flags-to-enable-editing-on-qtableview"&gt;Implement &lt;code&gt;flags()&lt;/code&gt; to enable editing on QTableView&lt;/h2&gt;
&lt;p&gt;The first step to making your &lt;code&gt;QTableView&lt;/code&gt; editable is to return the &lt;code&gt;ItemIsEditable&lt;/code&gt; flag from your model's &lt;code&gt;flags()&lt;/code&gt; method. Without this flag, double-clicking a cell in the table view won't open an editor widget:&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 flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;However, to get the editing working, you also need to implement a &lt;code&gt;setData()&lt;/code&gt; method. This method is the model's interface between Qt and your data object. It takes care of making the changes to the data.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Remember, Qt views don't know anything about your data beyond what you tell them via the model. Likewise, they also don't know how to update your list, NumPy array, or &lt;code&gt;DataFrame&lt;/code&gt; objects with the new data that has been provided. You need to handle that yourself!&lt;/p&gt;
&lt;h2 id="implement-setdata-to-write-changes-to-the-data-source"&gt;Implement &lt;code&gt;setData()&lt;/code&gt; to write changes to the data source&lt;/h2&gt;
&lt;p&gt;Below are some example &lt;code&gt;setData()&lt;/code&gt; methods for lists of lists, NumPy, and Pandas. The only difference between each implementation is how we index into the data object:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="641d189801d34458995ae248959a9ada" v-on:click="switch_tab"&gt;List&lt;/li&gt;
&lt;li class="tab-link" data-tab="e4bfd64a42e34cf9b155bba469c4f333" v-on:click="switch_tab"&gt;Pandas&lt;/li&gt;
&lt;li class="tab-link" data-tab="1a37bf19c5984b36ae078eb9f2fa4b82" v-on:click="switch_tab"&gt;NumPy&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="641d189801d34458995ae248959a9ada"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row()][index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="e4bfd64a42e34cf9b155bba469c4f333"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data.iloc[index.row(),index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="1a37bf19c5984b36ae078eb9f2fa4b82"&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 setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row(), index.column()] = value
            return True
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that we first need to check whether the &lt;code&gt;role&lt;/code&gt; is &lt;code&gt;Qt.ItemDataRole.EditRole&lt;/code&gt; to determine if an edit is currently being made. After making the edit, we return &lt;code&gt;True&lt;/code&gt; to confirm the data was set successfully.&lt;/p&gt;
&lt;h2 id="update-data-to-show-the-current-value-when-editing"&gt;Update &lt;code&gt;data()&lt;/code&gt; to show the current value when editing&lt;/h2&gt;
&lt;p&gt;If you try the above on your model, you should be able to edit the values. However, you'll notice that when editing, it clears the current value of the cell &amp;mdash; you have to start from an empty cell. To display the current value when editing, you need to modify the &lt;code&gt;data()&lt;/code&gt; method to return the current value when the role is &lt;code&gt;Qt.ItemDataRole.EditRole&lt;/code&gt; &lt;em&gt;as well as&lt;/em&gt; when it is &lt;code&gt;Qt.ItemDataRole.DisplayRole&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="49bbe2e858c244939fa706b81cd2ad29" v-on:click="switch_tab"&gt;List&lt;/li&gt;
&lt;li class="tab-link" data-tab="664443059220487c8311503b8525d17d" v-on:click="switch_tab"&gt;Pandas&lt;/li&gt;
&lt;li class="tab-link" data-tab="61fb53a3bd1d419a98cd6c23881eaa78" v-on:click="switch_tab"&gt;NumPy&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="49bbe2e858c244939fa706b81cd2ad29"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row()][index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="664443059220487c8311503b8525d17d"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data.iloc[index.row(), index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="61fb53a3bd1d419a98cd6c23881eaa78"&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 data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row(), index.column()]
                return str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;That's it! You should now have a properly editable &lt;code&gt;QTableView&lt;/code&gt; in PyQt6.&lt;/p&gt;
&lt;p&gt;Below are complete working examples for each data source type: Python lists, Pandas DataFrames, and NumPy arrays.&lt;/p&gt;
&lt;h2 id="editable-qtableview-with-a-python-list-of-lists"&gt;Editable QTableView with a Python list of lists&lt;/h2&gt;
&lt;p&gt;The following example uses a nested Python list of lists as a data source for an editable &lt;code&gt;QTableView&lt;/code&gt;. This is the simplest approach and doesn't require any external libraries:&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.QtCore import QAbstractTableModel, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView

class ListModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        # The following takes the first sub-list, and returns
        # the length (only works if all rows are an equal length)
        return len(self._data[0])

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row()][index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data[index.row()][index.column()] = value
            return True
        return False

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = [
            [1, 9, 2],
            [1, 0, -1],
            [3, 5, 2],
            [3, 3, 2],
            [5, 8, 9],
        ]

        self.model = ListModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="editable-qtableview-with-a-pandas-dataframe"&gt;Editable QTableView with a Pandas DataFrame&lt;/h2&gt;
&lt;p&gt;The following example uses a Pandas &lt;code&gt;DataFrame&lt;/code&gt; as the data source for an editable &lt;code&gt;QTableView&lt;/code&gt;, adding column headings from the DataFrame column names:&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 pandas as pd
from PyQt6.QtCore import QAbstractTableModel, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView

class PandasModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data.iloc[index.row(), index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            self._data.iloc[index.row(), index.column()] = value
            return True
        return False

    def headerData(self, col, orientation, role):
        if (
            orientation == Qt.Orientation.Horizontal
            and role == Qt.ItemDataRole.DisplayRole
        ):
            return self._data.columns[col]

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = pd.DataFrame(
            [
                [1, 9, 2],
                [1, 0, -1],
                [3, 5, 2],
                [3, 3, 2],
                [5, 8, 9],
            ],
            columns=["A", "B", "C"],
        )

        self.model = PandasModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="editable-qtableview-with-a-numpy-array"&gt;Editable QTableView with a NumPy array&lt;/h2&gt;
&lt;p&gt;The following example uses a NumPy array as the data source for an editable &lt;code&gt;QTableView&lt;/code&gt;. Because the array will only accept valid values (in this case, integers) when setting, we must first coerce the value to an integer before setting it on the array. If you enter something which isn't a valid integer (e.g. &lt;em&gt;jdskfjdskjfndsf&lt;/em&gt; ), the &lt;code&gt;int()&lt;/code&gt; call will throw a &lt;code&gt;ValueError&lt;/code&gt;, which we catch. By returning &lt;code&gt;False&lt;/code&gt; when this exception is thrown, we cancel the edit:&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 numpy as np
from PyQt6.QtCore import QAbstractTableModel, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView

class NumPyModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, index):
        return self._data.shape[0]

    def columnCount(self, index):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                value = self._data[index.row(), index.column()]
                return str(value)

    def setData(self, index, value, role):
        if role == Qt.ItemDataRole.EditRole:
            try:
                value = int(value)
            except ValueError:
                return False
            self._data[index.row(), index.column()] = value
            return True
        return False

    def flags(self, index):
        return (
            Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsEditable
        )

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.table = QTableView()

        data = np.array(
            [
                [1, 9, 2],
                [1, 0, -1],
                [3, 5, 2],
                [3, 3, 2],
                [5, 8, 9],
            ]
        )

        self.model = NumPyModel(data)
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)

app = QApplication([])
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;To make a &lt;code&gt;QTableView&lt;/code&gt; editable in PyQt6 using &lt;code&gt;QAbstractTableModel&lt;/code&gt;, you need to implement three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;flags()&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Return &lt;code&gt;Qt.ItemFlag.ItemIsEditable&lt;/code&gt; to enable editing on cells.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setData()&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Handle writing the new value back to your data source (list, Pandas DataFrame, or NumPy array).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;data()&lt;/code&gt; with &lt;code&gt;EditRole&lt;/code&gt;&lt;/strong&gt; &amp;mdash; Return the current cell value for &lt;code&gt;EditRole&lt;/code&gt; so the editor is pre-populated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These same principles apply whether you're using PyQt6, PyQt5, or PySide6 &amp;mdash; only the enum paths differ slightly between frameworks.&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qtableview"/><category term="model-views"/><category term="editing"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>6th Edition - Create GUI Applications with Python &amp; Qt, Released — PyQt6 &amp; PySide6 books updated for 2025 with model view controller architecture, new Python/Qt features and more examples</title><link href="https://www.pythonguis.com/blog/pyqt6-pyside6-books-updated-2025/" rel="alternate"/><published>2025-06-11T08:00:00+00:00</published><updated>2025-06-11T08:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-06-11:/blog/pyqt6-pyside6-books-updated-2025/</id><summary type="html">The 6th edition of my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt; is now
available for both &lt;strong&gt;PyQt6&lt;/strong&gt; and &lt;strong&gt;PySide6&lt;/strong&gt;.</summary><content type="html">
            &lt;p&gt;The 6th edition of my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt; is now
available for both &lt;strong&gt;PyQt6&lt;/strong&gt; and &lt;strong&gt;PySide6&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PyQt6&lt;/strong&gt; &amp;mdash; &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp;amp; Qt6 / PyQt6 Book, 6th Edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PySide6&lt;/strong&gt; &amp;mdash; &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp;amp; Qt6 / PySide6 Book, 6th Edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This update brings the book up to date with the latest changes in PyQt6 &amp;amp; PySide6, and also updates code to make use of newer features in Python. Many of the chapters have been updated and extended with more examples of form layouts, built-in dialogs and architecture, particularly using &lt;strong&gt;Model View Controller (MVC) architecture&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="whats-new-in-the-6th-edition"&gt;What's new in the 6th edition?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Updated for &lt;strong&gt;PyQt6 &amp;amp; PySide6&lt;/strong&gt; with the latest Qt6 API changes&lt;/li&gt;
&lt;li&gt;Expanded coverage of &lt;strong&gt;Model View Controller (MVC)&lt;/strong&gt; architecture for Python GUI apps&lt;/li&gt;
&lt;li&gt;More examples of &lt;strong&gt;form layouts&lt;/strong&gt;, &lt;strong&gt;built-in dialogs&lt;/strong&gt;, and &lt;strong&gt;application architecture&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Code updated to leverage modern &lt;strong&gt;Python 3&lt;/strong&gt; features and best practices&lt;/li&gt;
&lt;li&gt;Additional real-world examples to help you build professional desktop applications&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="buy-the-latest-edition"&gt;Buy the latest edition&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PyQt6&lt;/strong&gt; &amp;mdash; &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 Book, 6th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PySide6&lt;/strong&gt; &amp;mdash; &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 Book, 6th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="free-updates-for-existing-readers"&gt;Free updates for existing readers&lt;/h2&gt;
&lt;p&gt;As always, if you've previously bought a copy of the book you &lt;strong&gt;get these updates for free!&lt;/strong&gt; Just go to &lt;a href="https://www.pythonguis.com/library"&gt;your account downloads page&lt;/a&gt; and enter the email you used for the purchase.&lt;/p&gt;
&lt;p&gt;If you bought the book elsewhere (in paperback or digital) you can register to get these updates too &amp;mdash; just email your receipt to &lt;a href="mailto:register@pythonguis.com"&gt;register@pythonguis.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Enjoy!&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qt6"/><category term="python"/><category term="pyside6"/><category term="python-gui"/><category term="gui-programming"/><category term="mvc"/><category term="qt"/></entry><entry><title>Tkinter Widgets — A walkthrough of Tkinter's basic widgets</title><link href="https://www.pythonguis.com/tutorials/tkinter-basic-widgets/" rel="alternate"/><published>2025-05-19T06:00:00+00:00</published><updated>2025-05-19T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2025-05-19:/tutorials/tkinter-basic-widgets/</id><summary type="html">In Tkinter (and most GUI libraries), &lt;strong&gt;widget&lt;/strong&gt; is the name given to a component of the GUI that the user can interact with. User interfaces are made up of multiple widgets arranged within the window to make it functional and intuitive to use.</summary><content type="html">&lt;p&gt;In Tkinter (and most GUI libraries), &lt;strong&gt;widget&lt;/strong&gt; is the name given to a component of the GUI that the user can interact with. User interfaces are made up of multiple widgets arranged within the window to make it functional and intuitive to use.&lt;/p&gt;
&lt;p&gt;Tkinter comes with a decent set of widgets and even allows you to create your own custom widgets or customize existing ones.&lt;/p&gt;
&lt;h2 id="a-quick-demo"&gt;A Quick Demo&lt;/h2&gt;
&lt;p&gt;First, let's have a look at some of the most common Tkinter widgets. The following code creates a range of Tkinter widgets and adds them to a window layout so you can see them together:&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 tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Widgets Demo")

widgets = [
    tk.Label,
    tk.Checkbutton,
    ttk.Combobox,
    tk.Entry,
    tk.Button,
    tk.Radiobutton,
    tk.Scale,
    tk.Spinbox,
]

for widget in widgets:
    try:
        widget = widget(root, text=widget.__name__)
    except tk.TclError:
        widget = widget(root)
    widget.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Widgets Demo on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-widgets-demo.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-widgets-demo.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-widgets-demo.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-widgets-demo.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-widgets-demo.png?tr=w-600 600w" loading="lazy" width="430" height="776"/&gt;
&lt;em&gt;Tkinter's Widgets Demo on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  You can learn how the &lt;code&gt;pack()&lt;/code&gt; geometry manager works in our &lt;a href="https://www.pythonguis.com/tutorials/create-ui-with-tkinter-pack-layout-manager/"&gt;Using the Pack Geometry Manager in Tkinter&lt;/a&gt; tutorial.&lt;/p&gt;
&lt;p&gt;Let's have a look at all the example widgets, from top to bottom:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Widget&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Label&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Just a label, not interactive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Checkbutton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A checkbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Combobox&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A dropdown list box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Entry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enter a line of text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Button&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Radiobutton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A toggle set, with only one active item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Scale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A slider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Spinbox&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An integer spinner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are a few more widgets in Tkinter, but they don't fit so well for a quick demo example! You can see them all by checking the &lt;a href="https://tkdocs.com/tutorial/widgets.html"&gt;TkDocs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, we'll step through each of those widgets in turn, adding them to our application and seeing how they behave.&lt;/p&gt;
&lt;h2 id="label"&gt;&lt;code&gt;Label&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We'll start the tour with &lt;code&gt;Label&lt;/code&gt;, arguably one of the simplest widgets available in the Tkinter toolbox. This is a simple one-line piece of text that you can position in your application. You can set the text by passing in a &lt;code&gt;str&lt;/code&gt; as you create 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;label = tk.Label(self, text="Hello")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or, by using the &lt;code&gt;.config()&lt;/code&gt; function:&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;label = tk.Label(self, text="1")  # The label is created with the text 1.
label.config(text="2")   # The label now shows 2.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also adjust font properties, such as the family and size. Here's an app that showcases these features:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Label")
root.geometry("200x80")

label = tk.Label(root, text="Hello!", font=("Helvetica", 30))
label.pack(expand=True)

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Label Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-text.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-text.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-text.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-text.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-text.png?tr=w-600 600w" loading="lazy" width="400" height="216"/&gt;
&lt;em&gt;Tkinter's Label Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Note that if you want to change the properties of a widget font, it is usually better to set the font when creating the widget to ensure consistency.&lt;/p&gt;
&lt;p&gt;The alignment is specified by using the &lt;code&gt;anchor&lt;/code&gt; configuration option. The possible horizontal text alignments are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"w"&lt;/code&gt;, &lt;code&gt;tk.W&lt;/code&gt; (for West)&lt;/td&gt;
&lt;td&gt;Aligns with the left edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"e"&lt;/code&gt;, &lt;code&gt;tk.E&lt;/code&gt; (for East)&lt;/td&gt;
&lt;td&gt;Aligns with the right edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"center"&lt;/code&gt;, &lt;code&gt;tk.CENTER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centers horizontally in the available space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The options available for vertical alignment are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"n"&lt;/code&gt;, &lt;code&gt;tk.N&lt;/code&gt; (for North)&lt;/td&gt;
&lt;td&gt;Aligns with the top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"s"&lt;/code&gt;, &lt;code&gt;tk.S&lt;/code&gt; (for South)&lt;/td&gt;
&lt;td&gt;Aligns with the bottom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"center"&lt;/code&gt;, &lt;code&gt;tk.CENTER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centers vertically in the available space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You can combine these by setting the anchor option:&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;label = tk.Label(self, text="Hello", anchor="center")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Note that you use the &lt;code&gt;anchor&lt;/code&gt; option to combine the alignment settings.&lt;/p&gt;
&lt;p&gt;Finally, you can also use &lt;code&gt;Label&lt;/code&gt; to display an image using &lt;code&gt;PhotoImage&lt;/code&gt;. This function accepts an image file, and you can create it as follows:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Label Image")

photo = tk.PhotoImage(file="otje.png").subsample(2)
label = tk.Label(root, image=photo)
label.pack(expand=True)

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Otje, the cat, displayed in a window" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-image.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-image.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-image.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-image.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-label-widget-image.png?tr=w-600 600w" loading="lazy" width="1608" height="964"/&gt;
&lt;em&gt;Otje, the cat, displayed in a window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What a lovely face!&lt;/p&gt;
&lt;h2 id="button"&gt;&lt;code&gt;Button&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Button&lt;/code&gt; widget is one of the most commonly used widgets in Tkinter. It represents a clickable button that can trigger an action when pressed. We typically use buttons for submitting forms, opening dialogs, or starting processes.&lt;/p&gt;
&lt;p&gt;Here's a simple 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;import tkinter as tk

root = tk.Tk()
root.title("Tkinter Button")
root.geometry("200x100")

def on_click():
    label.config(text="Button clicked!")

button = tk.Button(
    root,
    text="Click Me",
    command=on_click,
)
button.pack(padx=5, pady=5)

# A helper label to show the result of the click
label = tk.Label(root, text="Waiting for click...")
label.pack(padx=5, pady=5)

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Button Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-button-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-button-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-button-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-button-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-button-widget.png?tr=w-600 600w" loading="lazy" width="400" height="256"/&gt;
&lt;em&gt;Tkinter's Button Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, you create a button with the label &lt;code&gt;"Click Me"&lt;/code&gt;. The &lt;code&gt;command&lt;/code&gt; argument connects the button to the &lt;code&gt;on_click()&lt;/code&gt; function, which updates the label's text when we click the button.&lt;/p&gt;
&lt;p&gt;You can customize the button by adjusting its &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;font&lt;/code&gt;, &lt;code&gt;bg&lt;/code&gt; (background color), and &lt;code&gt;fg&lt;/code&gt; (foreground/text color). 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;button = tk.Button(
    root,
    text="Styled Button",
    bg="blue",
    fg="white",
    font=("Arial", 14)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Buttons are versatile and form the backbone of interactive Tkinter applications.&lt;/p&gt;
&lt;h2 id="checkbutton"&gt;&lt;code&gt;Checkbutton&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The next widget to explore is &lt;code&gt;Checkbutton&lt;/code&gt;. As its name suggests, it presents a checkable box to the user. As with all Tkinter widgets, it has a number of configuration options to change the widget behaviors:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Checkbutton")
root.geometry("210x80")

def show_state():
    checked = "Checked" if var.get() else "Unchecked"
    checkbox.config(text=f"Check me! ({checked})")

var = tk.IntVar()
checkbox = tk.Checkbutton(root, text="Check me! (Checked)", variable=var)
checkbox.select()
checkbox.config(command=show_state)
checkbox.pack(padx=5, pady=10)

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Checkbutton Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-checkbutton-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-checkbutton-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-checkbutton-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-checkbutton-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-checkbutton-widget.png?tr=w-600 600w" loading="lazy" width="420" height="216"/&gt;
&lt;em&gt;Tkinter's Checkbutton Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can programmatically set a checkbox state using &lt;code&gt;select()&lt;/code&gt; or &lt;code&gt;deselect()&lt;/code&gt;. You can access the state using a &lt;code&gt;tk.IntVar()&lt;/code&gt; variable, which holds the checkbox's state: &lt;code&gt;1&lt;/code&gt; for checked and &lt;code&gt;0&lt;/code&gt; for unchecked.&lt;/p&gt;
&lt;h2 id="combobox"&gt;&lt;code&gt;Combobox&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Combobox&lt;/code&gt; widget is a drop-down list, closed by default with an arrow to open it. You can select a single item from the list, and the currently selected item is shown as a label on the widget. The combo box is suited to selecting a choice from a long list of options.&lt;/p&gt;
&lt;p&gt;You can add items to a &lt;code&gt;Combobox&lt;/code&gt; by passing a list of strings to its &lt;code&gt;values&lt;/code&gt; argument. The items will be added in the order we provide them:&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 tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Tkinter Combobox")
root.geometry("200x80")

def selection_changed(event):
    label.config(text=f"{event.widget.get()} selected!")

combobox = ttk.Combobox(root, values=["One", "Two", "Three"])
combobox.set("One")
combobox.bind("&amp;lt;&amp;lt;ComboboxSelected&amp;gt;&amp;gt;", selection_changed)
combobox.pack(padx=5, pady=5, fill="x")

# A helper label to show the selected value
label = tk.Label(root, text="One selected!")
label.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Combobox Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-combobox-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-combobox-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-combobox-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-combobox-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-combobox-widget.png?tr=w-600 600w" loading="lazy" width="400" height="216"/&gt;
&lt;em&gt;Tkinter's Combobox Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, we create a combo box whose values come from a Python list. Then, we set the current value to &lt;code&gt;"One"&lt;/code&gt; with the &lt;code&gt;set()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;bind()&lt;/code&gt; function connects the &lt;code&gt;&amp;lt;&amp;lt;ComboboxSelected&amp;gt;&amp;gt;&lt;/code&gt; event to the &lt;code&gt;selection_changed()&lt;/code&gt; function. This event is triggered when the currently selected item changes. The function updates the text of the helper label to reflect the selected item.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Combobox&lt;/code&gt; widgets can also be editable, allowing users to enter values not currently in the list. To achieve this, you need to set the &lt;code&gt;state&lt;/code&gt; argument to &lt;code&gt;"normal"&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;widget.config(state="normal")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also set a limit to the number of items allowed by configuring the list or using custom validation.&lt;/p&gt;
&lt;h2 id="listbox"&gt;&lt;code&gt;Listbox&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Listbox&lt;/code&gt; widget is similar to &lt;code&gt;Combobox&lt;/code&gt;, except that its options are presented as a scrollable list of items. It also supports the selection of multiple items at once. The &lt;code&gt;Listbox&lt;/code&gt; class offers a &lt;code&gt;&amp;lt;&amp;lt;ListboxSelect&amp;gt;&amp;gt;&lt;/code&gt; event that sends the selected item's index:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Listbox")

def selection_changed(event):
    selection = event.widget.curselection()
    if selection:
        index = selection[0]
        label.config(text=f"{event.widget.get(index)} selected!")
        event.widget.get(index)

listbox = tk.Listbox(root)
for item in ["One", "Two", "Three"]:
    listbox.insert(tk.END, item)
listbox.bind("&amp;lt;&amp;lt;ListboxSelect&amp;gt;&amp;gt;", selection_changed)
listbox.pack(padx=5, pady=5, fill="both", expand=True)

# A helper label to show the selected value
label = tk.Label(root, text="One selected!")
label.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Listbox Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-listbox-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-listbox-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-listbox-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-listbox-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-listbox-widget.png?tr=w-600 600w" loading="lazy" width="384" height="484"/&gt;
&lt;em&gt;Tkinter's Listbox Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, you create a list box and populate it through a &lt;code&gt;for&lt;/code&gt; loop and the &lt;code&gt;insert()&lt;/code&gt; function. Then, you connect the &lt;code&gt;"&amp;lt;&amp;lt;ListboxSelect&amp;gt;&amp;gt;"&lt;/code&gt; event with the &lt;code&gt;selection_changed()&lt;/code&gt; function. The helper label at the bottom of the window shows the selected item.&lt;/p&gt;
&lt;h2 id="entry"&gt;&lt;code&gt;Entry&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Entry&lt;/code&gt; widget is a simple single-line text editing box, into which users can type input. These are used for form fields, or settings where there is no restricted list of valid inputs. For example, when entering an email address or computer name:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Entry")

def return_pressed(event):
    label.config(text=event.widget.get())

entry = tk.Entry(root)
entry.insert(0, "Enter your text")
entry.bind("&amp;lt;Return&amp;gt;", return_pressed)
entry.pack(padx=5, pady=5, fill="x")

# A helper label to show the selected value
label = tk.Label(root, text="Entry demo!")
label.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Entry Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-entry-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-entry-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-entry-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-entry-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-entry-widget.png?tr=w-600 600w" loading="lazy" width="404" height="196"/&gt;
&lt;em&gt;Tkinter's Entry Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, you create a text entry using the &lt;code&gt;Entry&lt;/code&gt; widget. Then, you insert a placeholder text using the &lt;code&gt;insert()&lt;/code&gt; function. The &lt;code&gt;Entry&lt;/code&gt; widget allows you to handle various text events, including when the &lt;em&gt;Return&lt;/em&gt; key is pressed. Binding this even to &lt;code&gt;return_pressed()&lt;/code&gt; ensures that when you press &lt;em&gt;Enter&lt;/em&gt;, the helper label displays the text that you type into the entry.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Entry&lt;/code&gt; class has several useful features. They even allow you to perform &lt;a href="https://www.pythonguis.com/tutorials/input-validation-tkinter/"&gt;input validation&lt;/a&gt; using custom validation functions and regular expressions.&lt;/p&gt;
&lt;h2 id="spinbox"&gt;&lt;code&gt;Spinbox&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Spinbox&lt;/code&gt; widget provides an input box for numerical values. It has arrows to increase and decrease the value. It supports integers natively:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Spinbox")
root.geometry("200x80")

spinbox_var = tk.StringVar(value="0")
spinbox = tk.Spinbox(
    root,
    from_=-10,
    to=10,
    textvariable=spinbox_var,
)
spinbox.pack(padx=5, pady=5, fill="x")

# A helper label to show the selected value
label = tk.Label(root, textvariable=spinbox_var)
label.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Tkinter's Spinbox Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-spinbox-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-spinbox-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-spinbox-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-spinbox-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-spinbox-widget.png?tr=w-600 600w" loading="lazy" width="400" height="216"/&gt;
&lt;em&gt;Tkinter's Spinbox Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The code above shows the various features available for the &lt;code&gt;Spinbox&lt;/code&gt; widget. You can set the interval extremes with the &lt;code&gt;from_&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; arguments. In practice, you'll often use the &lt;code&gt;textvariable&lt;/code&gt; option to control the spin box value.&lt;/p&gt;
&lt;h2 id="scale"&gt;&lt;code&gt;Scale&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Scale&lt;/code&gt; widget provides a slide-bar widget that works much like a &lt;code&gt;Spinbox&lt;/code&gt;. Rather than displaying the current value numerically, it displays the position of the slider handle along the length of the widget.&lt;/p&gt;
&lt;p&gt;This widget is often useful when we need to adjust between two extremes but where absolute accuracy is not required. The most common use of this type of widget is for volume controls in multimedia apps:&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 tkinter as tk

root = tk.Tk()
root.title("Tkinter Scale")
root.geometry("200x80")

def value_changed(event):
    label.config(text=event.widget.get())

scale = tk.Scale(root, from_=0, to=10, orient="horizontal")
scale.bind("&amp;lt;Motion&amp;gt;", value_changed)
scale.pack(padx=5, pady=5, fill="x")

# A helper label to show the selected value
label = tk.Label(root, text="0")
label.pack(padx=5, pady=5, fill="x")

root.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a slider widget. Drag the slider to change the value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tkinter's Scale Widget on macOS" src="https://www.pythonguis.com/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-scale-widget.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-scale-widget.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-scale-widget.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-scale-widget.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/tkinter/tkinter-basic-widgets/tkinter-scale-widget.png?tr=w-600 600w" loading="lazy" width="400" height="216"/&gt;
&lt;em&gt;Tkinter's Scale Widget on macOS&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can also construct a slider with a vertical or horizontal orientation by setting the &lt;code&gt;orient&lt;/code&gt; option.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This concludes our brief tour of the common widgets used in Tkinter applications. To see the full list of available widgets, including all their options and attributes, take a look at the &lt;a href="https://docs.python.org/3/library/tkinter.html"&gt;Tkinter Documentation&lt;/a&gt; or &lt;a href="https://tkdocs.com/tutorial/widgets.html"&gt;TkDocs&lt;/a&gt; site.&lt;/p&gt;</content><category term="python"/><category term="tkinter"/><category term="widgets"/><category term="foundation"/><category term="tk"/><category term="tkinter-foundation"/></entry><entry><title>What does @Slot() do? — Is the Slot decorator even necessary?</title><link href="https://www.pythonguis.com/faq/what-does-slot-do/" rel="alternate"/><published>2025-05-12T06:00:00+00:00</published><updated>2025-05-12T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-05-12:/faq/what-does-slot-do/</id><summary type="html">When working with Qt slots and signals in PySide6 you will discover the &lt;code&gt;@Slot&lt;/code&gt; decorator. This decorator is used to &lt;em&gt;mark&lt;/em&gt; a Python function or method as a &lt;em&gt;slot&lt;/em&gt; to which a Qt signal can be connected. However, as you can see in our &lt;a href="/tutorials/pyside6-signals-slots-events/"&gt;PySide6 signals and slots tutorial&lt;/a&gt; you don't &lt;em&gt;have&lt;/em&gt; to use this. Any Python function or method can be used, normally, as a slot for Qt signals. But elsewhere, in our &lt;a href="/tutorials/multithreading-pyside6-applications-qthreadpool/"&gt;PySide6 multithreading tutorial&lt;/a&gt; we &lt;em&gt;do&lt;/em&gt; use it.</summary><content type="html">
            &lt;p&gt;When working with Qt slots and signals in PySide6 you will discover the &lt;code&gt;@Slot&lt;/code&gt; decorator. This decorator is used to &lt;em&gt;mark&lt;/em&gt; a Python function or method as a &lt;em&gt;slot&lt;/em&gt; to which a Qt signal can be connected. However, as you can see in our &lt;a href="/tutorials/pyside6-signals-slots-events/"&gt;PySide6 signals and slots tutorial&lt;/a&gt; you don't &lt;em&gt;have&lt;/em&gt; to use this. Any Python function or method can be used, normally, as a slot for Qt signals. But elsewhere, in our &lt;a href="/tutorials/multithreading-pyside6-applications-qthreadpool/"&gt;PySide6 multithreading tutorial&lt;/a&gt; we &lt;em&gt;do&lt;/em&gt; use it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What's going on here?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why do you sometimes use &lt;code&gt;@Slot&lt;/code&gt; but usually not?&lt;/li&gt;
&lt;li&gt;What happens when you omit the &lt;code&gt;@Slot&lt;/code&gt; decorator?&lt;/li&gt;
&lt;li&gt;Are there times when &lt;code&gt;@Slot&lt;/code&gt; is &lt;em&gt;required&lt;/em&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're looking for the PyQt6 equivalent of this article, see &lt;a href="/faq/what-does-pyqtslot-do/"&gt;What does @pyqtSlot() do?&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="what-does-the-pyside6-documentation-say-about-slot"&gt;What Does the PySide6 Documentation Say About @Slot?&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html#the-pyqtslot-decorator"&gt;PyQt6 documentation&lt;/a&gt; has a good explanation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Although PyQt6 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt6 provides the &lt;code&gt;pyqtSlot()&lt;/code&gt; function decorator to do this.&lt;/p&gt;
&lt;p&gt;Connecting a signal to a decorated Python method has the advantage of reducing the amount of memory used and is slightly faster.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In PySide6 the decorator is named simply &lt;code&gt;Slot()&lt;/code&gt; but is &lt;a href="https://doc.qt.io/qtforpython-6.5/PySide6/QtCore/Slot.html"&gt;otherwise functionally compatible&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PySide6 adopts PyQt's new signal and slot syntax as-is. The PySide6 implementation is functionally compatible with the PyQt one [...].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From the above we see that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any Python callable can be used as a slot when connecting signals.&lt;/li&gt;
&lt;li&gt;It is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it.&lt;/li&gt;
&lt;li&gt;There is a side-benefit in that marking a function or method with &lt;code&gt;Slot()&lt;/code&gt; reduces the amount of memory used, and makes the slot faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-is-the-slot-decorator-required-in-pyside6"&gt;When Is the @Slot Decorator Required in PySide6?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Sometimes necessary&lt;/em&gt; is a bit vague. In practice the only situation where you &lt;em&gt;need&lt;/em&gt; to use &lt;code&gt;@Slot&lt;/code&gt; decorators is when working with threads. This is because of a difference in how signal connections are handled in decorated vs. undecorated slots.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you decorate a method with &lt;code&gt;@Slot&lt;/code&gt; then that slot is created as a native Qt slot, and behaves identically to a C++ slot.&lt;/li&gt;
&lt;li&gt;If you don't decorate the method then PySide6 will create a "proxy" object wrapper which provides a native slot to Qt.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In normal use this is fine, aside from the performance impact (see below). But when working with threads, there is a complication: is the proxy object created on the GUI thread or on the runner thread? If it ends up on the wrong thread, this can lead to segmentation faults. Using the &lt;code&gt;@Slot&lt;/code&gt; decorator side-steps this issue, because no proxy is created.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  When updating my &lt;a href="/pyside6-book/"&gt;PySide6 book&lt;/a&gt; I wondered -- &lt;em&gt;is this still necessary?!&lt;/em&gt; -- and tested removing it from the examples. Many examples continue to work, but some failed. To be safe, always use &lt;code&gt;@Slot&lt;/code&gt; decorators on your &lt;code&gt;QRunnable.run&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;For more on working with threads safely in PySide6, see our complete guide to &lt;a href="/tutorials/multithreading-pyside6-applications-qthreadpool/"&gt;multithreading PySide6 applications with QThreadPool&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="slot-performance-benchmark-does-it-make-a-difference"&gt;@Slot Performance Benchmark: Does It Make a Difference?&lt;/h2&gt;
&lt;p&gt;The PyQt6 documentation notes that using native slots "has the advantage of reducing the amount of memory used and is slightly faster". But how much faster is it really, and does decorating slots actually save much memory?&lt;/p&gt;
&lt;p&gt;We can test this directly by using &lt;a href="https://github.com/schollii/sandals/blob/master/pyqt5_connections_mem_speed.py"&gt;this script from Oliver L Schoenborn&lt;/a&gt;. Updating for PySide6 (replace &lt;code&gt;PyQt5&lt;/code&gt; with &lt;code&gt;PySide6&lt;/code&gt; and &lt;code&gt;pyqtSlot&lt;/code&gt; with &lt;code&gt;Slot&lt;/code&gt; and it will work as-is) and running this we get the following results:&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  See the &lt;a href="https://www.codeproject.com/articles/1123088/pyqt-signal-slot-connection-performance"&gt;original results for PyQt5&lt;/a&gt; for comparison.&lt;/p&gt;
&lt;p&gt;First the results for the speed of emitting signals when connected to a decorated slot, vs non-decorated.&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;Raw slot mean, stddev:  1.608 0.066
Pyqt slot mean, stddev: 1.587 0.045
Percent gain with Slot: 1 %
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The result shows &lt;code&gt;@Slot&lt;/code&gt; as 1% faster, but this is negligible (the original data on PyQt5 also showed no difference). So, using &lt;code&gt;@Slot&lt;/code&gt; will have no noticeable impact on the speed of signal handling in your applications.&lt;/p&gt;
&lt;p&gt;Next are the results for establishing connections. This shows the speed and memory usage of connecting to decorated vs. non-decorated slots.&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;Comparing mem and time required to create 10000000 connections, 1000 times

Measuring for 100000 connections
              # connects   mem (bytes)       time (sec)
Raw         :   100000     38670336 (36MB)    0.381
PySide Slot :   100000     17858560 (17MB)    0.426
Ratios      :                     2               1

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The results show that decorated slots are marginally faster to connect to, but the difference is negligible. Based on these numbers, when connecting 100 signals the total execution time difference would be 0.03 ms vs 0.04 ms. This is negligible, not to mention imperceptible.&lt;/p&gt;
&lt;p&gt;Perhaps more significant is that using raw connections uses 2x the memory of decorated connections. Again though, bear in mind that for a more realistic upper limit of connections (100) the actual difference here is 3.6KB vs 1.7KB.&lt;/p&gt;
&lt;p&gt;The bottom line: don't expect any dramatic improvements in performance or memory usage from using &lt;code&gt;@Slot&lt;/code&gt; decorators, unless you're working with insanely large numbers of signals or making regular connections you won't see any difference at all. That said, decorating your slots is an easy win if you need it.&lt;/p&gt;
&lt;h2 id="using-slot-to-overload-signal-types-in-pyside6"&gt;Using @Slot to Overload Signal Types in PySide6&lt;/h2&gt;
&lt;p&gt;In Qt, signals can be used to transmit more than one type of data by &lt;a href="https://doc.qt.io/qtforpython-6/tutorials/basictutorial/signals_and_slots.html#overloading-signals-and-slots"&gt;overloading signals and slots with different types&lt;/a&gt;. For more on passing data through signals, see our tutorial on &lt;a href="/tutorials/pyside6-transmitting-extra-data-qt-signals/"&gt;transmitting extra data with Qt signals in PySide6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, with the following code the &lt;code&gt;my_slot_fn&lt;/code&gt; will &lt;em&gt;only&lt;/em&gt; receive signals which match the signature of two &lt;code&gt;int&lt;/code&gt; values.&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;@Slot(int, int)
def my_slot_fn(a, b):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is a legacy of Qt5 and not recommended in new code. In Qt6 all of these signals have been replaced with separate signals with distinct names for different types. I recommend you follow the same approach in your own code for the sake of simplicity.&lt;/p&gt;
&lt;h2 id="conclusion-when-should-you-use-slot-in-pyside6"&gt;Conclusion: When Should You Use @Slot in PySide6?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;@Slot&lt;/code&gt; decorator can be used to mark Python functions or methods as Qt slots in PySide6. This decorator is only required on slots which may be connected to across threads, for example the &lt;code&gt;run&lt;/code&gt; method of &lt;code&gt;QRunnable&lt;/code&gt; objects. For all other slots it can be omitted. There is a very small performance benefit to using it, which you may want to consider when your application makes a large number of signal-slot connections.&lt;/p&gt;
&lt;p&gt;To summarize when you need the &lt;code&gt;@Slot&lt;/code&gt; decorator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Threading with QRunnable or QThread&lt;/strong&gt;: Always use &lt;code&gt;@Slot&lt;/code&gt; on methods that will be called across threads to avoid segmentation faults. See our &lt;a href="/tutorials/multithreading-pyside6-applications-qthreadpool/"&gt;PySide6 QThreadPool tutorial&lt;/a&gt; for practical examples.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Normal signal-slot connections&lt;/strong&gt;: The decorator is optional. Any Python callable works as a slot without it. Learn more in our &lt;a href="/tutorials/pyside6-signals-slots-events/"&gt;PySide6 signals, slots &amp;amp; events guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance-critical applications&lt;/strong&gt;: Use &lt;code&gt;@Slot&lt;/code&gt; if you have thousands of connections and need to reduce memory overhead, though the gains are minimal for typical applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not sure whether to use PySide6 or PyQt6 for your project? See our comparison of &lt;a href="/faq/pyqt6-vs-pyside6/"&gt;PyQt6 vs PySide6&lt;/a&gt; to help you decide.&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.pythonguis.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="pyside2"/><category term="pyside"/><category term="threading"/><category term="threads"/><category term="signals"/><category term="slots"/><category term="python"/><category term="qt"/><category term="qt6"/><category term="qt5"/></entry><entry><title>Build an Image Noise Reduction Tool with Streamlit and OpenCV — Clean up noisy images using OpenCV denoising algorithms in Python</title><link href="https://www.pythonguis.com/examples/streamlit-denoiser-application/" rel="alternate"/><published>2025-05-05T06:00:00+00:00</published><updated>2025-05-05T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-05-05:/examples/streamlit-denoiser-application/</id><summary type="html">Image noise is a random variation of brightness or color in images, which can make it harder to discern finer details in a photo. Noise is an artefact of how the image is captured. In digital photography, sensor electronic noise causes random fuzziness over the true image. It is more noticeable in low light, where the lower signal from the sensor is amplified, amplifying the noise with it. Similar noisy artifacts are also present in analog photos and film, but there it is caused by the film grain. Finally, you can also see noise-like artifacts introduced by lossy compression algorithms such as JPEG.</summary><content type="html">&lt;p&gt;Image noise is a random variation of brightness or color in images, which can make it harder to discern finer details in a photo. Noise is an artefact of how the image is captured. In digital photography, sensor electronic noise causes random fuzziness over the true image. It is more noticeable in low light, where the lower signal from the sensor is amplified, amplifying the noise with it. Similar noisy artifacts are also present in analog photos and film, but there it is caused by the film grain. Finally, you can also see noise-like artifacts introduced by lossy compression algorithms such as JPEG.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Noise reduction&lt;/strong&gt; or &lt;strong&gt;denoising&lt;/strong&gt; improves the visual appearance of a photo and can be an important step in a larger image analysis pipeline. Eliminating noise can make it easier to identify features algorithmically. However, we need to ensure that the denoised image is still an accurate representation of the original capture.&lt;/p&gt;
&lt;p&gt;Denoising is a complex topic. Fortunately, several different algorithms are available in Python. In this tutorial, we'll use algorithms from OpenCV and build them into a Streamlit web app. The app will allow a user to upload images, choose from common noise reduction algorithms &amp;mdash; such as Gaussian Blur, Median Blur, Minimum Blur, Maximum Blur, and Non-local Means &amp;mdash; and adjust the strength of the noise reduction using a slider. The user can then download the resulting noise-reduced image.&lt;/p&gt;
&lt;p&gt;By the end of this tutorial, you will --&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learn how to build interactive Python web applications with Streamlit.&lt;/li&gt;
&lt;li&gt;Understand how to work with images using the Python libraries OpenCV and Pillow.&lt;/li&gt;
&lt;li&gt;Be able to apply noise reduction algorithms to images and allow users to download the processed images in different formats.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's quite a lot to this example, so we'll break it down into small steps to make sure we understand how everything works.&lt;/p&gt;
&lt;h2 id="setting-up-the-python-working-environment"&gt;Setting Up the Python Working Environment&lt;/h2&gt;
&lt;p&gt;In this tutorial, we'll use the Streamlit library to build the noise reduction app's GUI.&lt;/p&gt;
&lt;!--
INFO: To learn the basics of Streamlit, check out the complete [Streamlit Tutorial](https://www.pythonguis.com/streamlit-tutorial/)
--&gt;
&lt;p&gt;To perform the denoising, we'll be using &lt;a href="https://opencv.org/"&gt;OpenCV&lt;/a&gt;. Don't worry if you're not familiar with this library, we'll be including working examples you can copy for everything we do.&lt;/p&gt;
&lt;p&gt;With that in mind, let's create a &lt;a href="https://www.pythonguis.com/tutorials/python-virtual-environments/"&gt;virtual environment&lt;/a&gt; and install our requirements into it. To do this, you can run the following commands:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="e81a4dce09ff4d78b39166c091be088a" v-on:click="switch_tab"&gt;macOS&lt;/li&gt;
&lt;li class="tab-link" data-tab="0dcf536891f9497c9681198b535e0e6f" v-on:click="switch_tab"&gt;Windows&lt;/li&gt;
&lt;li class="tab-link" data-tab="6bf0093fd0f24c119f69d11148e86397" v-on:click="switch_tab"&gt;Linux&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="e81a4dce09ff4d78b39166c091be088a"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;$ mkdir denoise/
$ cd denoise
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install streamlit opencv-python pillow numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="0dcf536891f9497c9681198b535e0e6f"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-cmd"&gt;cmd&lt;/span&gt;
&lt;pre&gt;&lt;code class="cmd"&gt;&amp;gt; mkdir denoise/
&amp;gt; cd denoise
&amp;gt; python -m venv venv
&amp;gt; venv\Scripts\activate.bat
(venv)&amp;gt; pip install streamlit opencv-python pillow numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="6bf0093fd0f24c119f69d11148e86397"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-sh"&gt;sh&lt;/span&gt;
&lt;pre&gt;&lt;code class="sh"&gt;$ mkdir denoise/
$ cd denoise
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install streamlit opencv-python pillow numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With these commands, you create a &lt;code&gt;denoise/&lt;/code&gt;  folder for storing your project. Inside that folder, you create a new virtual environment, activate it, and install Streamlit, OpenCV, Pillow &amp;amp; numpy.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  For platform-specific troublshooting, check the &lt;a href="https://www.pythonguis.com/tutorials/python-virtual-environments/"&gt;Working With Python Virtual Environments&lt;/a&gt; tutorial.&lt;/p&gt;
&lt;h2 id="building-the-streamlit-application-outline"&gt;Building the Streamlit Application Outline&lt;/h2&gt;
&lt;p&gt;We'll start by constructing a simple Streamlit application and then expand it from there.&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 streamlit as st

# Set the title of our app.
st.title("Noise Reduction App")

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save this file as &lt;code&gt;app.py&lt;/code&gt; and use the following command to run 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;streamlit run app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Streamlit will start up and will launch the application in your default web browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Streamlit application title displayed in the browser" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-app-title-only.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-app-title-only.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-app-title-only.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-app-title-only.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-app-title-only.png?tr=w-600 600w" loading="lazy" width="907" height="228"/&gt;
&lt;em&gt;The Streamlit application title displayed in the browser.&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If it doesn't launch by itself, you can see the web address to open in the console.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The Streamlit application launch message showing the local server address where the app can be viewed" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-launch-message.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-launch-message.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-launch-message.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-launch-message.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-launch-message.png?tr=w-600 600w" loading="lazy" width="747" height="122"/&gt;
&lt;em&gt;The Streamlit application launch message showing the local server address where the app can be viewed.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that we have the app working, we can step through and build up our app.&lt;/p&gt;
&lt;h2 id="uploading-an-image-with-streamlit"&gt;Uploading an Image with Streamlit&lt;/h2&gt;
&lt;p&gt;First we need a way to upload an image to denoise. Streamlit provides a simple &lt;code&gt;.file_uploader&lt;/code&gt; method which can be used to upload an image from your computer.
This is a generic file upload handler, but you can provide both a message to display (to specify what to upload) and constrain the file types that are supported.&lt;/p&gt;
&lt;p&gt;Below we define a &lt;code&gt;file_uploader&lt;/code&gt; which shows a message "Choose an image..." and accepts JPEG and PNG images.&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 streamlit as st

# Set the title of our app.
st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])

print(uploaded_file)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  For historic reasons, JPEG images can have both &lt;code&gt;.jpg&lt;/code&gt; or &lt;code&gt;.jpeg&lt;/code&gt; extensions, so we include both in the list.&lt;/p&gt;
&lt;p&gt;Run the code and you'll see the file upload box in the app. Try uploading a file.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Streamlit application with a file-upload widget" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-file-upload.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-file-upload.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-file-upload.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-file-upload.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-file-upload.png?tr=w-600 600w" loading="lazy" width="947" height="442"/&gt;
&lt;em&gt;Streamlit application with a file-upload widget.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The uploaded image is stored in the variable &lt;code&gt;uploaded_file&lt;/code&gt;. Before a file is uploaded, the value of &lt;code&gt;uploaded_file&lt;/code&gt; will be &lt;code&gt;None&lt;/code&gt;. Once the user uploads an image, this variable will contain an &lt;code&gt;UploadedFile&lt;/code&gt; object.&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;None
UploadedFile(file_id='73fd9a97-9939-4c02-b9e8-80bd2749ff76', name='headcake.jpg', type='image/jpeg', size=652805, _file_urls=file_id: "73fd9a97-9939-4c02-b9e8-80bd2749ff76"
upload_url: "/_stcore/upload_file/7c881339-82e4-4d64-ba20-a073a11f7b60/73fd9a97-9939-4c02-b9e8-80bd2749ff76"
delete_url: "/_stcore/upload_file/7c881339-82e4-4d64-ba20-a073a11f7b60/73fd9a97-9939-4c02-b9e8-80bd2749ff76"
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We can use this &lt;code&gt;UploadedFile&lt;/code&gt; object to load and display the image in the browser.&lt;/p&gt;
&lt;h2 id="how-streamlit-re-evaluation-works"&gt;How Streamlit Re-evaluation Works&lt;/h2&gt;
&lt;p&gt;If you're used to writing Python scripts the behavior of the script and file upload box might be a confusing. Normally a script would execute from top to bottom, but here the value of &lt;code&gt;uploaded_file&lt;/code&gt; is &lt;em&gt;changing&lt;/em&gt; and the &lt;code&gt;print&lt;/code&gt; statement is being re-run as the state changes.&lt;/p&gt;
&lt;p&gt;There's a lot of clever stuff going on under the hood here, but in simple terms the Streamlit script is being &lt;em&gt;re-evaluated&lt;/em&gt; in response to changes. On each change the script runs again, from top to bottom. But importantly, the state of widgets is not reset on each run.&lt;/p&gt;
&lt;p&gt;When we upload a file, that file gets stored in the state of the file upload widget and this triggers the script to re-start. When it gets to the &lt;code&gt;st.file_uploader&lt;/code&gt; call, that &lt;code&gt;UploadedFile&lt;/code&gt; object will be returned immediately from the stored state. It can then affect the flow of the code after it.&lt;/p&gt;
&lt;p&gt;The following code allows you to see these re-runs more clearly, by displaying the current timestamp in the header. Every time the code is re-executed this number will update.&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 time import time

import streamlit as st

# Set the title of our app.
st.title(f"Noise Reduction App {int(time())}")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Try uploading an image and then removing it. You'll see the timestamp in the title change each time. This is the script being re-evaluated in response to changes in the widget state.&lt;/p&gt;
&lt;h3&gt;Loading and Displaying the Uploaded Image&lt;/h3&gt;
&lt;p&gt;While we can upload an image, we can't see it yet. Let's implement that now.&lt;/p&gt;
&lt;p&gt;As mentioned, the uploaded file is available as an &lt;code&gt;UploadedFile&lt;/code&gt; object in the &lt;code&gt;uploaded_file&lt;/code&gt; variable. This object can be passed directly to &lt;code&gt;st.image&lt;/code&gt; to display the image back in the browser. You can also add a caption and auto resize the image to the width of the application.&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 numpy as np
import streamlit as st
from PIL import Image

st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])


if uploaded_file is not None:
    # Convert the uploaded file to a PIL image.
    image = Image.open(uploaded_file)

    st.image(image, caption="Uploaded Image", use_container_width=True)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and upload an image. You'll see the image appear under the file upload widget.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Streamlit application showing an uploaded image" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-uploaded-image.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-uploaded-image.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-uploaded-image.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-uploaded-image.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-uploaded-image.png?tr=w-600 600w" loading="lazy" width="633" height="1068"/&gt;
&lt;em&gt;Streamlit application showing an uploaded image.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Converting the Image to a NumPy Array for OpenCV Processing&lt;/h3&gt;
&lt;p&gt;While the above works fine for displaying the image in the browser, we want to process the image through the OpenCV noise reduction algorithms. For that we need to get the image into a format which OpenCV recognizes. We can do that using Pillow &amp;amp; NumPy.&lt;/p&gt;
&lt;p&gt;The updated code to handle this conversion is shown below.&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 numpy as np
import streamlit as st
from PIL import Image

st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])


if uploaded_file is not None:
    # Convert the uploaded file to a PIL image.
    image = Image.open(uploaded_file)

    # Convert the image to an RGB NumPy array for processing.
    image = image.convert("RGB")
    image = np.array(image)

    # Displaying the RGB image.
    st.image(image, caption="Uploaded Image", use_container_width=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code, the uploaded file is opened using Pillow's &lt;code&gt;Image.open()&lt;/code&gt; method, which reads the image into a PIL image format. The image is then converted into Pillow's RGB format, for consistency (discarding transparency for example). This regular format is then converted into a NumPy array which OpenCV requires for processing.&lt;/p&gt;
&lt;p&gt;Helpfully, Streamlit's &lt;code&gt;st.image&lt;/code&gt; method &lt;em&gt;also&lt;/em&gt; understands the NumPy RGB image format, so we can pass the image array directly to it. This will be useful when we want to display the processed image, since we won't need to convert it before doing that.&lt;/p&gt;
&lt;p&gt;If you run the above it will work exactly as before. But now we have our uploaded image available as an RGB array in the &lt;code&gt;image&lt;/code&gt; variable. We'll use that to do our processing next.&lt;/p&gt;
&lt;h2 id="configuring-the-noise-reduction-algorithm"&gt;Configuring the Noise Reduction Algorithm&lt;/h2&gt;
&lt;p&gt;The correct noise reduction strategy depends on the image and type of noise present. For a given image you may want to try different algorithms and adjust the extent of the noise reduction. To accommodate that, we're going to add two new controls to our application -- an &lt;em&gt;algorithm&lt;/em&gt; drop down, and a &lt;em&gt;kernel size&lt;/em&gt; slider.&lt;/p&gt;
&lt;p&gt;The first presents a select box from which the user can choose which algorithm to use. The second allows the user to configure the behavior of the given algorithm -- specifically the size of the area being considered by each algorithm when performing noise reduction.&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 numpy as np
import streamlit as st
from PIL import Image

st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])

algorithm = st.selectbox(
    "Select noise reduction algorithm",
    (
        "Gaussian Blur Filter",
        "Median Blur Filter",
        "Minimum Blur Filter",
        "Maximum Blur Filter",
        "Non-local Means Filter",
    ),
)

kernel_size = st.slider("Select kernel size", 1, 10, step=2)


if uploaded_file is not None:
    # Convert the uploaded file to a PIL image.
    image = Image.open(uploaded_file)

    # Convert the image to an RGB NumPy array for processing.
    image = image.convert("RGB")
    image = np.array(image)

    # Displaying the RGB image.
    st.image(image, caption="Uploaded Image", use_container_width=True)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this you'll see the new widgets in the UI. The uploaded image is displayed last since it is the last thing to be added.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The algorithm selection and configuration widgets shown in the app" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-algorithm-widgets.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-algorithm-widgets.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-algorithm-widgets.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-algorithm-widgets.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-algorithm-widgets.png?tr=w-600 600w" loading="lazy" width="1002" height="1110"/&gt;
&lt;em&gt;The algorithm selection and configuration widgets shown in the app.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The slider for the kernel size allows the user to adjust the kernel size, which determines the strength of the noise reduction effect. The &lt;em&gt;kernel&lt;/em&gt; is a small matrix used in convolution to blur or process the image for noise removal. The larger the kernel size, the stronger the effect will be but also the more blurring or distortion you will see in the image.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  The removal of noise is always a balancing act between noise and accuracy of the image.&lt;/p&gt;
&lt;p&gt;The slider ranges from 1 to 10, with a step of 2 (i.e., possible kernel sizes are 1, 3, 5, 7, and 9).&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  The kernel size must be an odd number to maintain symmetry in the image processing algorithms.&lt;/p&gt;
&lt;h2 id="applying-noise-reduction-with-opencv-in-python"&gt;Applying Noise Reduction with OpenCV in Python&lt;/h2&gt;
&lt;p&gt;Now we have all the parts in place to actually perform noise reduction on the image. The final step is to add the calls to OpenCV's noise reduction algorithms and show the resulting, noise-reduced image back in the UI.&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 cv2
import numpy as np
import streamlit as st
from PIL import Image

st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])

algorithm = st.selectbox(
    "Select noise reduction algorithm",
    (
        "Gaussian Blur Filter",
        "Median Blur Filter",
        "Minimum Blur Filter",
        "Maximum Blur Filter",
        "Non-local Means Filter",
    ),
)

kernel_size = st.slider("Select kernel size", 1, 10, step=2)


if uploaded_file is not None:
    # Convert the uploaded file to a PIL image.
    image = Image.open(uploaded_file)

    # Convert the image to an RGB NumPy array for processing.
    image = image.convert("RGB")
    image = np.array(image)

    # Displaying the RGB image.
    st.image(image, caption="Uploaded Image", use_container_width=True)

    # Applying the selected noise reduction algorithm based on user selection
    if algorithm == "Gaussian Blur Filter":
        denoised_image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
    elif algorithm == "Median Blur Filter":
        denoised_image = cv2.medianBlur(image, kernel_size)
    elif algorithm == "Minimum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.erode(image, kernel, iterations=1)
    elif algorithm == "Maximum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.dilate(image, kernel, iterations=1)
    elif algorithm == "Non-local Means Filter":
        denoised_image = cv2.fastNlMeansDenoisingColored(
            image, None, kernel_size, kernel_size, 7, 15
        )

    # Displaying the denoised image in RGB format
    st.image(denoised_image, caption="Denoised Image", use_container_width=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this you can now upload your images and apply denoising to them. Try changing the algorithm and adjusting the kernel size parameter to see the effect it has on the noise reduction. The denoised image is displayed at the bottom with the caption "Denoised Image".&lt;/p&gt;
&lt;p&gt;Each of the noise reduction strategies is described below. The &lt;code&gt;median blur&lt;/code&gt; and &lt;code&gt;non-local means&lt;/code&gt; methods are the most effective for normal images.&lt;/p&gt;
&lt;h3&gt;Gaussian Blur Filter&lt;/h3&gt;
&lt;p&gt;Gaussian blur smooths the image by applying a Gaussian function to a pixel's neighbors. The kernel size determines the area over which the blur is applied, with larger kernels leading to stronger blurs. This method preserves edges fairly well and is often used in preprocessing for tasks like object detection.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gaussian blur filter applied to an image using a 3x3 kernel" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/gaussian-blur-algorithm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/gaussian-blur-algorithm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/gaussian-blur-algorithm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/gaussian-blur-algorithm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/gaussian-blur-algorithm.png?tr=w-600 600w" loading="lazy" width="512" height="288"/&gt;
&lt;em&gt;Gaussian blur filter applied to an image using a 3x3 kernel.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is effective at removing light noise, at the expense of sharpness.&lt;/p&gt;
&lt;h3&gt;Median Blur Filter&lt;/h3&gt;
&lt;p&gt;Median blur reduces noise by replacing each pixel's value with the median value from the surrounding pixels, making it effective against salt-and-pepper noise. It preserves edges better than Gaussian blur but can still affect the sharpness of fine details.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Median blur filter applied to an image using a 3x3 kernel window" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/median-blur-algorithm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-blur-algorithm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-blur-algorithm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-blur-algorithm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-blur-algorithm.png?tr=w-600 600w" loading="lazy" width="512" height="288"/&gt;
&lt;em&gt;Median blur filter applied to an image using a 3x3 kernel window.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Median blur noise reduction (kernel size = 7)" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/median-noise-reduction.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction.png?tr=w-600 600w" loading="lazy" width="1293" height="481"/&gt;
&lt;em&gt;Median blur noise reduction (kernel size = 7).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Median blur noise reduction (kernel size = 5)" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/median-noise-reduction2.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction2.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction2.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction2.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/median-noise-reduction2.png?tr=w-600 600w" loading="lazy" width="951" height="315"/&gt;
&lt;em&gt;Median blur noise reduction (kernel size = 5).&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Minimum Blur Filter (Erosion)&lt;/h3&gt;
&lt;p&gt;This filter uses the concept of morphological erosion. It shrinks bright areas in the image by sliding a small kernel over it. This filter is effective for removing noise in bright areas but may distort the overall structure if applied too strongly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Erosion algorithm applied to an image using 3x3 kernel window" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/erosion-algorithm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-algorithm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-algorithm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-algorithm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-algorithm.png?tr=w-600 600w" loading="lazy" width="512" height="288"/&gt;
&lt;em&gt;Erosion algorithm applied to an image using 3x3 kernel window.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This works well to remove light noise from dark regions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Erosion noise reduction (kernel size = 5)" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/erosion-noise-reduction.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-noise-reduction.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-noise-reduction.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-noise-reduction.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/erosion-noise-reduction.png?tr=w-600 600w" loading="lazy" width="952" height="324"/&gt;
&lt;em&gt;Erosion noise reduction (kernel size = 5).&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Maximum Blur Filter (Dilation)&lt;/h3&gt;
&lt;p&gt;In contrast to erosion, dilation expands bright areas and is effective in eliminating dark noise spots. However, it can result in the expansion of bright regions, altering the shape of objects in the image.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dilation algorithm applied to an image using 3x3 kernel window" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/dilation-algorithm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/dilation-algorithm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/dilation-algorithm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/dilation-algorithm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/dilation-algorithm.png?tr=w-600 600w" loading="lazy" width="512" height="288"/&gt;
&lt;em&gt;Dilation algorithm applied to an image using 3x3 kernel window.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This works well to remove dark noise from light regions.&lt;/p&gt;
&lt;h3&gt;Non-Local Means Denoising Filter&lt;/h3&gt;
&lt;p&gt;This method identifies similar regions from across the image, then combines these together to average out the noise. This works particularly well in images with repeating regions, or flat areas of color, but less well when the image has too much noise to be able to identify the similar regions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Non-local means noise reduction on smoke from birthday candles (kernel size = 5)." src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/non-local-means-noise-reduction.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/non-local-means-noise-reduction.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/non-local-means-noise-reduction.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/non-local-means-noise-reduction.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/non-local-means-noise-reduction.png?tr=w-600 600w" loading="lazy" width="1211" height="462"/&gt;
&lt;em&gt;Non-local means noise reduction example.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="improving-the-streamlit-layout-with-columns"&gt;Improving the Streamlit Layout with Columns&lt;/h2&gt;
&lt;p&gt;It's not very user friendly having the input and output images one above the other, as you need to scroll up and down to see the effect of the algorithm. Streamlit has support for arranging widgets in columns. We'll use that to put the two images next to one another.&lt;/p&gt;
&lt;p&gt;To create columns in Streamlit you use &lt;code&gt;st.columns()&lt;/code&gt; passing in the number of columns to create. This returns column objects (as many as you request) which can be used as &lt;em&gt;context managers&lt;/em&gt; to wrap your widget calls. In code, this looks like the following:&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;    # Displaying the denoised image in RGB format
    col1, col2 = st.columns(2)

    with col1:
        st.image(image, caption="Uploaded Image", use_container_width=True)

    with col2:
        st.image(denoised_image, caption="Denoised Image", use_container_width=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here we call &lt;code&gt;st.columns(2)&lt;/code&gt; creating two columns, returning into &lt;code&gt;col1&lt;/code&gt; and &lt;code&gt;col2&lt;/code&gt;. We then use these with &lt;code&gt;with&lt;/code&gt; to wrap the two &lt;code&gt;st.image&lt;/code&gt; calls. This puts them into two adjacent columns.&lt;/p&gt;
&lt;p&gt;Run this and you'll see the two images next to one another. This makes it much easier to see the impact of changes in the algorithm or parameters.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The source and processed image arranged next to one another using columns" src="https://www.pythonguis.com/static/examples/streamlit/noise-reduction/streamlit-columns.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-columns.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-columns.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-columns.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/streamlit/noise-reduction/streamlit-columns.png?tr=w-600 600w" loading="lazy" width="957" height="915"/&gt;
&lt;em&gt;The source and processed image arranged next to one another using columns.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="downloading-the-denoised-image"&gt;Downloading the Denoised Image&lt;/h2&gt;
&lt;p&gt;Our application now allows users to upload images and process them to remove noise, with a configurable noise removal algorithm and kernel size. The final step is to allow users to download and save the processed image somewhere.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You can actually just right-click and use your browser's option to Save the image if you like. But adding this to the UI makes it more explicit and allows us to offer different image output formats.&lt;/p&gt;
&lt;p&gt;First, we need to import the &lt;code&gt;io&lt;/code&gt; module. In a normal image processing script, you could simply save the generated image to disk. Our Streamlit app could be running on a server somewhere, and saving the result to the server isn't useful: we want to be able to send it to the user. For that, we need to send it to the web browser. Browsers don't understand Python objects, so we need to save our image data to a simple &lt;code&gt;bytes&lt;/code&gt; object. The &lt;code&gt;io&lt;/code&gt; module allows us to do that.&lt;/p&gt;
&lt;p&gt;Add an import for Python's &lt;code&gt;io&lt;/code&gt; module to the imports at the top of the code.&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 io

import cv2
import numpy as np
import streamlit as st
from PIL import Image
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now under the rest of the code we can add the widgets and logic for saving and presenting the image as a download. First add a select box to choose the image format.&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;    # ..snipped the rest of the code.

    # Dropdown to select the file format for downloading
    file_format = st.selectbox("Select output format", ("PNG", "JPEG"))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next we need to take our &lt;code&gt;denoised_image&lt;/code&gt; and convert this from a NumPy array back to a PIL image. Then we can use Pillow's native methods for saving the image to a simple bytestream, which can be sent to the web browser.&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;    # Converting NumPy array to PIL image in RGB mode
    denoised_image_pil = Image.fromarray(denoised_image)

    # Creating a buffer to store the image data in the selected format
    buf = io.BytesIO()
    denoised_image_pil.save(buf, format=file_format)
    byte_data = buf.getvalue()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Since OpenCV operations return a NumPy array (the same format we provide it with) it must be converted back to a PIL image before saving. The &lt;code&gt;io.BytesIO()&lt;/code&gt; creates an in-memory &lt;em&gt;file buffer&lt;/em&gt; to write to. That way we don't need to actually save the image. We write the image using the Image &lt;code&gt;.save()&lt;/code&gt; method in the requested file format.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Note that this saved image is in an actual PNG/JPEG image format at this point, not just pure image data.&lt;/p&gt;
&lt;p&gt;We can retrieve the bytes data from the buffer using &lt;code&gt;.getvalue()&lt;/code&gt;. The resulting &lt;code&gt;byte_data&lt;/code&gt; is a raw bytes object that can be passed to the web browser. This is handled by a Streamlit download button.&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;    # Button to download the processed image
    st.download_button(
        label="Download Image",
        data=byte_data,
        file_name=f"denoised_image.{file_format.lower()}",
        mime=f"image/{file_format.lower()}"
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Notice we've also set the filename and mimetype, using the selected &lt;code&gt;file_format&lt;/code&gt; variable.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  If you're adding additional file formats, be aware that the mimetypes are not always 1:1 with the file extensions. In this case we've used &lt;code&gt;.jpeg&lt;/code&gt; since the mimetype is &lt;code&gt;image/jpeg&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="refactoring-the-code-for-better-structure"&gt;Refactoring the Code for Better Structure&lt;/h2&gt;
&lt;p&gt;The complete code so far is shown below.&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 io

import cv2
import numpy as np
import streamlit as st
from PIL import Image

st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])

algorithm = st.selectbox(
    "Select noise reduction algorithm",
    (
        "Gaussian Blur Filter",
        "Median Blur Filter",
        "Minimum Blur Filter",
        "Maximum Blur Filter",
        "Non-local Means Filter",
    ),
)

kernel_size = st.slider("Select kernel size", 1, 10, step=2)


if uploaded_file is not None:
    # Convert the uploaded file to a PIL image.
    image = Image.open(uploaded_file)

    # Convert the image to an RGB NumPy array for processing.
    image = image.convert("RGB")
    image = np.array(image)

    # Applying the selected noise reduction algorithm based on user selection
    if algorithm == "Gaussian Blur Filter":
        denoised_image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
    elif algorithm == "Median Blur Filter":
        denoised_image = cv2.medianBlur(image, kernel_size)
    elif algorithm == "Minimum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.erode(image, kernel, iterations=1)
    elif algorithm == "Maximum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.dilate(image, kernel, iterations=1)
    elif algorithm == "Non-local Means Filter":
        denoised_image = cv2.fastNlMeansDenoisingColored(
            image, None, kernel_size, kernel_size, 7, 15
        )

    # Displaying the denoised image in RGB format
    col1, col2 = st.columns(2)

    with col1:
        st.image(image, caption="Uploaded Image", use_container_width=True)

    with col2:
        st.image(denoised_image, caption="Denoised Image", use_container_width=True)

    # Dropdown to select the file format for downloading
    file_format = st.selectbox("Select output format", ("PNG", "JPEG", "JPG"))

    # Converting NumPy array to PIL image in RGB mode
    denoised_image_pil = Image.fromarray(denoised_image)

    # Creating a buffer to store the image data in the selected format
    buf = io.BytesIO()
    denoised_image_pil.save(buf, format=file_format)
    byte_data = buf.getvalue()

    # Button to download the processed image
    st.download_button(
        label="Download Image",
        data=byte_data,
        file_name=f"denoised_image.{file_format.lower()}",
        mime=f"image/{file_format.lower()}",
    )

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the completed app you can now upload images, denoise them using the different algorithms and kernel parameters and then save them as JPEG or PNG format images.&lt;/p&gt;
&lt;p&gt;However, we can still improve this. There is a lot of code nested under the &lt;code&gt;if uploaded_file is not None:&lt;/code&gt; branch, and the logic and processing steps aren't well organized -- everything runs together, mixed in with the UI. When developing UI applications it's a good habit to separate UI and non-UI code where possible (logic vs. presentation). That keeps related code together in the same context, aiding readability and maintainability.&lt;/p&gt;
&lt;p&gt;Below is the same code refactored to move the file opening, denoising and file exporting logic out into separate handler functions.&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 io

import cv2
import numpy as np
import streamlit as st
from PIL import Image


def image_to_array(file_to_open):
    """Load a Streamlit image into an array."""
    # Convert the uploaded file to a PIL image.
    image = Image.open(file_to_open)

    # Convert the image to an RGB NumPy array for processing.
    image = image.convert("RGB")
    image = np.array(image)
    return image


def denoise_image(image, algorithm, kernel_size):
    """Apply a denoising algorithm to the provided image, with the given kernel size."""
    # Applying the selected noise reduction algorithm based on user selection
    if algorithm == "Gaussian Blur Filter":
        denoised_image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
    elif algorithm == "Median Blur Filter":
        denoised_image = cv2.medianBlur(image, kernel_size)
    elif algorithm == "Minimum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.erode(image, kernel, iterations=1)
    elif algorithm == "Maximum Blur Filter":
        kernel = np.ones((kernel_size, kernel_size), np.uint8)
        denoised_image = cv2.dilate(image, kernel, iterations=1)
    elif algorithm == "Non-local Means Filter":
        denoised_image = cv2.fastNlMeansDenoisingColored(
            image, None, kernel_size, kernel_size, 7, 15
        )
    return denoised_image


def image_array_to_bytes(image_to_convert):
    """Given an image array, convert it to a bytes object."""

    # Converting NumPy array to PIL image in RGB mode
    image_pil = Image.fromarray(image_to_convert)

    # Creating a buffer to store the image data in the selected format
    buf = io.BytesIO()
    image_pil.save(buf, format=file_format)
    byte_data = buf.getvalue()
    return byte_data


st.title("Noise Reduction App")

uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])

algorithm = st.selectbox(
    "Select noise reduction algorithm",
    (
        "Gaussian Blur Filter",
        "Median Blur Filter",
        "Minimum Blur Filter",
        "Maximum Blur Filter",
        "Non-local Means Filter",
    ),
)

kernel_size = st.slider("Select kernel size", 1, 10, step=2)


if uploaded_file is not None:
    image = image_to_array(uploaded_file)
    denoised_image = denoise_image(image, algorithm, kernel_size)

    # Displaying the denoised image in RGB format
    col1, col2 = st.columns(2)

    with col1:
        st.image(image, caption="Uploaded Image", use_container_width=True)

    with col2:
        st.image(denoised_image, caption="Denoised Image", use_container_width=True)

    # Dropdown to select the file format for downloading
    file_format = st.selectbox("Select output format", ("PNG", "JPEG", "JPG"))

    byte_data = image_array_to_bytes(denoised_image)

    # Button to download the processed image
    st.download_button(
        label="Download Image",
        data=byte_data,
        file_name=f"denoised_image.{file_format.lower()}",
        mime=f"image/{file_format.lower()}",
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, the main flow of the code now consists entirely of Streamlit UI setup code and calls to the processing functions we have defined. Both the UI and processing code is now easier to read and maintain.&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  In larger projects you may choose to move the functions out into separate files of related functions and import them into your Streamlit app.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, you built an image noise reduction application using Python, Streamlit, and OpenCV. The app allows users to upload images, apply different noise reduction algorithms &amp;mdash; including Gaussian blur, median blur, erosion, dilation, and non-local means denoising &amp;mdash; and download the processed image.&lt;/p&gt;
&lt;p&gt;It also allows the user to customize the kernel size, which controls the strength of the denoising effect. This makes the app useful for a variety of noise types and image processing tasks.&lt;/p&gt;
&lt;p&gt;Streamlit makes it simple to build powerful Python web applications, taking the power of Python's rich ecosystem and making it available through the browser. You can extend this project further by adding more OpenCV filters, supporting additional image formats, or deploying the app to the cloud for others to use.&lt;/p&gt;</content><category term="streamlit"/><category term="application"/><category term="image-processing"/><category term="opencv"/><category term="python"/></entry><entry><title>Kivy's Complex Widgets — Learn How to Use Kivy's Complex UX Widgets in Your Apps</title><link href="https://www.pythonguis.com/tutorials/kivy-complex-ui-widgets/" rel="alternate"/><published>2025-04-28T06:00:00+00:00</published><updated>2025-04-28T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2025-04-28:/tutorials/kivy-complex-ui-widgets/</id><summary type="html">Kivy is a powerful framework for developing multi-touch GUI applications using Python. It provides a set of rich built-in widgets which you can use to build complex GUI applications.</summary><content type="html">
            &lt;p&gt;Kivy is a powerful framework for developing multi-touch GUI applications using Python. It provides a set of rich built-in widgets which you can use to build complex GUI applications.&lt;/p&gt;
&lt;p&gt;In a previous tutorial we covered the &lt;a href="/tutorials/kivy-ux-widgets/"&gt;basic Kivy widgets&lt;/a&gt; such as text inputs, buttons and checkboxes. In this tutorial, we will take things further, exploring some of the more complex widgets that Kivy provides. These include: &lt;code&gt;Bubble&lt;/code&gt;, &lt;code&gt;DropDown&lt;/code&gt;, &lt;code&gt;FileChooser&lt;/code&gt;, &lt;code&gt;Popup&lt;/code&gt;, &lt;code&gt;Spinner&lt;/code&gt;, &lt;code&gt;RecycleView&lt;/code&gt;, &lt;code&gt;TabbedPanel&lt;/code&gt;, &lt;code&gt;VideoPlayer&lt;/code&gt;, and &lt;code&gt;VKeyboard&lt;/code&gt;. With these complex Kivy widgets, you can add advanced features to your Python GUI apps.&lt;/p&gt;
&lt;h2 id="writing-an-outline-kivy-app"&gt;Writing an Outline Kivy App&lt;/h2&gt;
&lt;p&gt;We'll start this tutorial with a simple application skeleton, which we will then modify below. Save the following code in a file named &lt;code&gt;app.py&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;from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout

class WidgetNameApp(App):
    title = "WidgetName Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout()

        return root

WidgetNameApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here, we've created a Kivy application with an empty window. The &lt;code&gt;BoxLayout&lt;/code&gt; acts as the root widget, this will act as the container to add our complex widgets to. The &lt;code&gt;build()&lt;/code&gt; method sets the window's background color to a dark teal shade and adjusts the window size to 360x640 pixels, which is a mobile-friendly size.&lt;/p&gt;
&lt;p class="admonition admonition-info"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-info"&gt;&lt;/i&gt;&lt;/span&gt;  To learn more about creating your first Kivy app, check out the &lt;a href="https://www.pythonguis.com/tutorials/getting-started-kivy/"&gt;Getting Started With Kivy for GUI Development&lt;/a&gt; tutorial.&lt;/p&gt;
&lt;h2 id="providing-option-selections-with-the-kivy-spinner-widget"&gt;Providing Option Selections With the Kivy &lt;code&gt;Spinner&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;Spinner&lt;/code&gt; widget is a dropdown selector that allows users to choose one option from multiple choices. This is ideal when working with a list of simple text choices. Below is an example that builds a &lt;code&gt;Spinner&lt;/code&gt; that lets you select from different parts of this website.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner

class SpinnerApp(App):
    title = "Spinner Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (300, 300)

        root = FloatLayout()

        # Create the Spinner
        spinner = Spinner(
            text="Home",
            values=("Home", "Latest", "FAQ", "Forum", "Contact", "About"),
            size_hint=(None, None),
            size=(200, 70),
            pos_hint={"center_x": 0.2, "center_y": 0.9},
            sync_height=True,
        )

        root.add_widget(spinner)

        return root

SpinnerApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;Spinner&lt;/code&gt; widget works as a simple dropdown list, allowing users to select one option from multiple text choices. We've set the &lt;code&gt;Spinner&lt;/code&gt; to start with &lt;code&gt;"Home"&lt;/code&gt; as the default &lt;code&gt;text&lt;/code&gt; and provided other options (&lt;code&gt;"Latest"&lt;/code&gt;, &lt;code&gt;"FAQ"&lt;/code&gt;, &lt;code&gt;"Forum"&lt;/code&gt;, &lt;code&gt;"Contact"&lt;/code&gt;, and &lt;code&gt;"About"&lt;/code&gt;) as a list of values.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You need to repeat the &lt;code&gt;"Home"&lt;/code&gt; option in &lt;code&gt;values&lt;/code&gt; so that you don't lose it when you select another option.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get an app that looks as shown below.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a Spinner Widget for dropdown selection" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/spinner-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/spinner-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/spinner-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/spinner-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/spinner-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="600" height="656"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;Spinner&lt;/code&gt; Widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The dropdown spinner allows users to select from predefined choices. You can use this widget to create elements that work like a dropdown list, optimizing space and providing a clean UI.&lt;/p&gt;
&lt;h2 id="providing-options-with-the-kivy-dropdown-list-widget"&gt;Providing Options With the Kivy &lt;code&gt;DropDown&lt;/code&gt; List Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;DropDown&lt;/code&gt; widget provides a more complex menu component that allows users to choose from multiple options. Like the Spinner, this provides an intuitive way for users to select from a set of choices, but here you can display more than just text. This makes it more complex to use, but allows for more flexibility.&lt;/p&gt;
&lt;p&gt;Below is an example of using the &lt;code&gt;DropDown&lt;/code&gt; widget to create a dropdown list that lets you select your favorite Python GUI library, displayed on a series of &lt;code&gt;Button&lt;/code&gt; objects.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown

class DropDownApp(App):
    title = "DropDown Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (200, 200)

        root = BoxLayout(orientation="vertical", padding=10, spacing=10)

        # Create a dropdown with 4 buttons
        dropdown = DropDown()
        for item in ["Kivy", "PyQt6", "PySide6", "Tkinter"]:
            option_btn = Button(text=item, size_hint_y=None, height=50, width=150)
            option_btn.bind(on_release=lambda btn: dropdown.select(btn.text))
            dropdown.add_widget(option_btn)

        # Create a main button to show the dropdown
        button = Button(
            text="Library",
            size_hint=(None, None),
            size=(150, 50),
        )
        button.bind(on_release=dropdown.open)
        dropdown.bind(
            on_select=lambda instance, text: setattr(button, "text", text),
        )
        root.add_widget(button)
        return root

DropDownApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we have a &lt;code&gt;DropDown&lt;/code&gt; widget that lets the user select a library from a list of options. You populate the dropdown with four options &lt;code&gt;"Kivy"&lt;/code&gt;, &lt;a href="https://www.pythonguis.com/pyqt6/"&gt;&lt;code&gt;"PyQt6"&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.pythonguis.com/pyside6/"&gt;&lt;code&gt;"PySide6"&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://www.pythonguis.com/tkinter/"&gt;&lt;code&gt;"Tkinter"&lt;/code&gt;&lt;/a&gt;, which are displayed in &lt;code&gt;Button&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;We set each button to trigger the &lt;code&gt;dropdown.select()&lt;/code&gt; method when clicked, passing the button's text as the selected value.&lt;/p&gt;
&lt;p&gt;Then, we anchor the dropdown to a &lt;em&gt;Library&lt;/em&gt; button. When we press the &lt;em&gt;Library&lt;/em&gt; button, the dropdown menu opens, displaying the options. Once we select an option, the &lt;code&gt;on_select&lt;/code&gt; event updates the main button's text to reflect the chosen library.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a window with a dropdown list in the lower left corner. Click in the dropdown widget to change the current selection.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a DropDown widget for selecting options" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/dropdown-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/dropdown-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/dropdown-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/dropdown-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/dropdown-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="400" height="456"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;DropDown&lt;/code&gt; widget&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="accessing-files-with-the-kivy-filechooser-widget"&gt;Accessing Files With the Kivy &lt;code&gt;FileChooser&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;filechooser&lt;/code&gt; module provides classes for describing, displaying and browsing file systems. In this module there are two ready-made widget views which present the file system as either a list, or as icons. The example below demonstrates both &lt;code&gt;FileChooserIconView&lt;/code&gt; and &lt;code&gt;FileChooserListView&lt;/code&gt; in action.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.filechooser import FileChooserIconView, FileChooserListView

class FileChooserApp(App):
    title = "FileChooser Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        # Create icon-view and list-view file choosers
        filechooser_icons = FileChooserIconView()
        filechooser_list = FileChooserListView()

        root.add_widget(filechooser_icons)
        root.add_widget(filechooser_list)

        return root

FileChooserApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create file chooser widgets to browse and select files using two different views:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Icon view&lt;/strong&gt; (&lt;code&gt;FileChooserIconView&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List view&lt;/strong&gt; (&lt;code&gt;FileChooserListView&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The icon view displays files as wrapped rows of icons, clicking on a folder icon will navigate down into that folder. The list view presents them in a list-tree like format, where clicking on a folder will show files and folders nested under it.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; On macOS, you'll see a window that looks something like the following. Try clicking on the folder icons and entries in the list view to see how navigation works in the two examples.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing FileChooser widgets with icon and list views" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/filechooser-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/filechooser-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/filechooser-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/filechooser-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/filechooser-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="720" height="1336"/&gt;
&lt;em&gt;A Kivy app showing file chooser widgets&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="building-quick-dialogs-with-the-kivy-popup-widget"&gt;Building Quick Dialogs With the Kivy &lt;code&gt;Popup&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;Popup&lt;/code&gt; widget allows us to display modal dialogs with custom content, layouts and widgets. Popups can be used to show messages or ask for input. The following example displays a simple popup message with a title, message and OK button.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup

class PopupApp(App):
    title = "Popup Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (400, 400)

        root = FloatLayout()

        button = Button(
            text="Open Popup",
            on_press=lambda x: self.show_popup(),
            size_hint=(None, None),
            size=(200, 50),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
        )

        root.add_widget(button)

        return root

    def show_popup(self):
        # Create and show the Popup
        popup = Popup(
            title="Info",
            size_hint=(0.6, 0.6),
            size=(300, 300),
            auto_dismiss=False,
        )

        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)

        message = Label(text="Hello, World!")

        ok_button = Button(text="OK", size_hint=(None, None), size=(80, 40))
        ok_button.bind(on_release=popup.dismiss)

        layout.add_widget(message)
        layout.add_widget(ok_button)

        popup.content = layout
        popup.open()

PopupApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, you create a &lt;code&gt;Popup&lt;/code&gt; widget that displays information as a modal dialog. When the user clicks the &lt;em&gt;Open Popup&lt;/em&gt; button, the &lt;code&gt;show_popup()&lt;/code&gt; method is triggered, creating a &lt;code&gt;Popup&lt;/code&gt; that occupies 60% of the screen in both directions.&lt;/p&gt;
&lt;p&gt;We set &lt;code&gt;auto_dismiss&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;, which means the popup won't close if we click outside of it. The popup contains the &lt;code&gt;Hello, World!&lt;/code&gt; message and an &lt;em&gt;OK&lt;/em&gt; button. When we click the button, we dismiss (close) the popup. Popups are effective for displaying alerts, confirmations, or other information in a Kivy app.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a window with a button labeled "Open Popup". Click on the &lt;em&gt;Open Popup&lt;/em&gt; button to display the popup window.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a Popup modal dialog widget" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/popup-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/popup-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/popup-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/popup-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/popup-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="800" height="856"/&gt;
&lt;em&gt;A Kivy app showing a popup dialog&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="creating-contextual-popups-with-the-kivy-bubble-widget"&gt;Creating Contextual Popups With the Kivy &lt;code&gt;Bubble&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;Bubble&lt;/code&gt; widget is a UI element commonly used for contextual popups, tooltips, or chat applications. Below is a quick Kivy application that shows some text and lets you click on it to change its format. We'll start by importing the necessary objects and subclassing the &lt;code&gt;Bubble&lt;/code&gt; class:&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 kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.bubble import Bubble, BubbleButton, BubbleContent
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

class FormattingBubble(Bubble):
    def __init__(self, target_text, **kwargs):
        super().__init__(**kwargs)

        # Customizing the bubble
        self.size_hint = (None, None)
        self.size = (dp(120), dp(50))
        self.arrow_pos = "top_mid"
        self.orientation = "horizontal"
        self.target_label = target_text

        # Add formatting buttons
        bold_btn = BubbleButton(text="Bold")
        italic_btn = BubbleButton(text="Italic")
        bold_btn.bind(on_release=lambda x: self.on_format("bold"))
        italic_btn.bind(on_release=lambda x: self.on_format("italic"))

        # Add the buttons to the bubble
        bubble_content = BubbleContent()
        bubble_content.add_widget(bold_btn)
        bubble_content.add_widget(italic_btn)

        self.add_widget(bubble_content)

    def on_format(self, format_type):
        if format_type == "bold":
            self.target_label.text = f"[b]{self.target_label.text}[/b]"
        elif format_type == "italic":
            self.target_label.text = f"[i]{self.target_label.text}[/i]"
        self.parent.remove_widget(self)

class BubbleApp(App):
    title = "Bubble Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = FloatLayout()

        self.text = Label(
            text="Click this text to apply formatting",
            size_hint=(0.8, 0.2),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
            markup=True,
        )
        self.text.bind(on_touch_down=self.show_bubble)

        root.add_widget(self.text)
        root.bind(on_touch_down=self.dismiss_bubbles)

        return root

    def show_bubble(self, instance, touch):
        if instance.collide_point(*touch.pos):
            self.remove_all_bubbles()
            bubble = FormattingBubble(target_text=self.text)
            bubble.pos = (
                touch.x - bubble.width / 2, touch.y - bubble.height - dp(10)
            )
            self.root.add_widget(bubble)

    def dismiss_bubbles(self, instance, touch):
        if instance == self.root and not self.text.collide_point(*touch.pos):
            self.remove_all_bubbles()

    def remove_all_bubbles(self):
        for widget in self.root.children[:]:
            if isinstance(widget, FormattingBubble):
                self.root.remove_widget(widget)
                return

BubbleApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;FormattingBubble&lt;/code&gt; class inherits from &lt;code&gt;Bubble&lt;/code&gt; and provides text formatting options for a label. It initializes with a specific size, arrow position, and horizontal layout. The bubble will contain two buttons: &lt;em&gt;Bold&lt;/em&gt; and &lt;em&gt;Italic&lt;/em&gt;. When pressed, these buttons apply the respective formatting to the target text by triggering the &lt;code&gt;on_format()&lt;/code&gt; method. This method wraps the text in Kivy's markup tags &lt;code&gt;[b]...[/b]&lt;/code&gt; for bold and &lt;code&gt;[i]...[/i]&lt;/code&gt; for italic.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;BubbleApp&lt;/code&gt; class represents the Kivy application. It sets up a &lt;code&gt;FloatLayout&lt;/code&gt; with a centered &lt;code&gt;Label&lt;/code&gt; displaying a message. When the user taps the label, the &lt;code&gt;show_bubble()&lt;/code&gt; method creates and positions a &lt;code&gt;FormattingBubble&lt;/code&gt; above the tapped location.&lt;/p&gt;
&lt;p&gt;The app also ensures that only one bubble is visible at a time by removing existing ones before showing a new one. Additionally, tapping outside the label dismisses any active bubbles using the &lt;code&gt;dismiss_bubbles()&lt;/code&gt; method.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; The app features a dark teal background and a mobile-friendly window size. The &lt;code&gt;Bubble&lt;/code&gt; widget appears when we click the text.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a Bubble widget for contextual popups" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/bubble-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/bubble-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/bubble-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/bubble-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/bubble-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="720" height="1336"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;Bubble&lt;/code&gt; widget&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="displaying-large-data-sets-with-the-kivy-recycleview-widget"&gt;Displaying Large Data Sets With the Kivy &lt;code&gt;RecycleView&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;RecycleView&lt;/code&gt; widget efficiently displays large data sets by recycling views or graphical elements. Instead of creating a widget for every item in the dataset, &lt;code&gt;RecycleView&lt;/code&gt; reuses a small number of widgets to display visible items only, significantly improving performance.&lt;/p&gt;
&lt;p&gt;To illustrate, let's create a view that lets you inspect a database of employee profiles. The data is stored in a CSV file that looks like the following:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-csv"&gt;csv&lt;/span&gt;
&lt;pre&gt;&lt;code class="csv"&gt;name,job,department
John Smith,Developer,IT
Jane Doe,Designer,Graphics
Anne Frank,Artist,Painting
David Lee,Engineer,Civil
Ella Brown,Doctor,Medical
Frank Thomas,Chef,Culinary
Henry Ford,Driver,Transport
Nathan Young,Consultant,Business
Olivia King,Manager,Administration
Peter Wright,Director,Management
Queen Bell,President,Executive
Walter Thompson,Assistant,Support
Xena Garcia,Associate,Associate
Zack Harris,Consultant,Consulting
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can read and load this data with the &lt;code&gt;csv&lt;/code&gt; module. To visualize the data, you can create a view with the &lt;code&gt;RecycleView&lt;/code&gt; widget. For the individual views, you can use the &lt;code&gt;Button&lt;/code&gt; widget, which will let you display the employee's profile:&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 csv

from kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView

class EmployeesView(RecycleView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.employees_data = self._read_from_csv()

        # Load the employees data into the data attribute
        self.data = [
            {
                "text": f"{employee['name']}",
                "on_release": self._create_callback(employee["name"]),
            }
            for employee in self.employees_data
        ]

        layout_manager = RecycleBoxLayout(
            default_size=(None, dp(56)),
            default_size_hint=(1, None),
            size_hint_y=None,
            orientation="vertical",
        )
        layout_manager.bind(minimum_height=layout_manager.setter("height"))

        self.add_widget(layout_manager)
        self.viewclass = "Button"

    def _create_callback(self, name):
        return lambda: self.on_button_click(name)

    def _read_from_csv(self):
        with open("employees.csv", mode="r") as file:
            return [row for row in csv.DictReader(file)]

    def on_button_click(self, name):
        popup = Popup(
            title=f"{name}'s Profile",
            size_hint=(0.8, 0.5),
            size=(300, 300),
            auto_dismiss=False,
        )
        employees_data = [
            employee for employee in self.employees_data if employee["name"] == name
        ]
        profile = "\n".join(
            [f"{key.capitalize()}: {value}" for key, value in employees_data[0].items()]
        )
        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
        message = Label(text=profile)
        ok_button = Button(text="OK", size_hint=(None, None))
        ok_button.bind(on_release=popup.dismiss)
        layout.add_widget(message)
        layout.add_widget(ok_button)
        popup.content = layout
        popup.open()

class RecycleViewApp(App):
    title = "RecycleView Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        return EmployeesView()

RecycleViewApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we subclass &lt;code&gt;RecycleView&lt;/code&gt; to display the list of employees loaded from a CSV file. The &lt;code&gt;_read_from_csv()&lt;/code&gt; method opens the file and reads the data using the &lt;code&gt;csv.DictReader()&lt;/code&gt; class, which converts each CSV line into a dictionary whose keys come from the file header line.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; attribute is key for the app to work because it'll hold the data that we want to display. To arrange widgets in a &lt;code&gt;RecycleView&lt;/code&gt;, we use a &lt;code&gt;RecycleBoxLayout&lt;/code&gt;. The &lt;code&gt;viewclass&lt;/code&gt; attribute lets us set the widget that we'll use to display each data item.&lt;/p&gt;
&lt;p class="admonition admonition-warning"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-exclamation-circle"&gt;&lt;/i&gt;&lt;/span&gt;  It's important to note that for the &lt;code&gt;RecycleView&lt;/code&gt; to work properly, we should set &lt;code&gt;viewclass&lt;/code&gt; at the end when the data is already loaded and the layout is set up.&lt;/p&gt;
&lt;p&gt;Then, we populate the &lt;code&gt;RecycleView&lt;/code&gt; view with buttons, each displaying an employee's name. Clicking a button triggers &lt;code&gt;_create_callback()&lt;/code&gt;, which generates a callback that opens a popup displaying the selected employee's profile details.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; You'll get a nice-looking window listing the employees. Click a button to view the associated employee's profile. Scroll down to load more profiles.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a RecycleView Widget for displaying large data sets" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/recycleview-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/recycleview-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/recycleview-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/recycleview-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/recycleview-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="720" height="1336"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;RecycleView&lt;/code&gt; Widget&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="building-tabbed-uis-with-the-kivy-tabbedpanel-widget"&gt;Building Tabbed UIs With the Kivy &lt;code&gt;TabbedPanel&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;TabbedPanel&lt;/code&gt; widget lets us organize content into tabs, to improve navigation and optimize the use of space. This is commonly used in settings dialogs where there are lots of options available.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader

class TabbedPanelApp(App):
    title = "TabbedPanel Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        # Create the TabbedPanel
        root = TabbedPanel(do_default_tab=False)

        # Create the tabs
        general_tab = TabbedPanelHeader(text="General")
        general_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        general_content.add_widget(Label(text="General Settings", font_size=40))
        general_tab.content = general_content
        root.add_widget(general_tab)

        editor_tab = TabbedPanelHeader(text="Editor")
        editor_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        editor_content.add_widget(Label(text="Editor Settings", font_size=40))
        editor_tab.content = editor_content
        root.add_widget(editor_tab)

        profile_tab = TabbedPanelHeader(text="Profile")
        profile_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        profile_content.add_widget(Label(text="User Profile", font_size=40))
        profile_tab.content = profile_content
        root.add_widget(profile_tab)

        return root

TabbedPanelApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create a Kivy app that shows a tabbed interface using the &lt;code&gt;TabbedPanel&lt;/code&gt; widget. It disables the default tab and manually adds three tabs: &lt;em&gt;General&lt;/em&gt;, &lt;em&gt;Editor&lt;/em&gt;, and &lt;em&gt;Profile&lt;/em&gt;, each represented by a &lt;code&gt;TabbedPanelHeader&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;Inside the tabs, we place a &lt;code&gt;BoxLayout&lt;/code&gt; to hold a label that displays a description as a placeholder tab content. Tabs allow us to organize content into visually distinct sections within an application's UI.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; Your app will display three tabs. When you click the tab header, the app shows the tab's content. The active tab shows a light blue line at the bottom.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a TabbedPanel Widget with multiple tabs" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/tabbedpanel-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/tabbedpanel-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/tabbedpanel-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/tabbedpanel-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/tabbedpanel-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="720" height="1336"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;TabbedPanel&lt;/code&gt; Widget&lt;/em&gt;&lt;/p&gt;
&lt;p class="admonition admonition-tip"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-lightbulb"&gt;&lt;/i&gt;&lt;/span&gt;  Try and add some more widgets to each tab panel.&lt;/p&gt;
&lt;h2 id="allowing-user-input-with-the-kivy-vkeyboard-widget"&gt;Allowing User Input With the Kivy &lt;code&gt;VKeyboard&lt;/code&gt; Widget&lt;/h2&gt;
&lt;p&gt;The Kivy &lt;code&gt;VKeyboard&lt;/code&gt; widget allows you to create a virtual keyboard that is useful for touchscreen applications that require the user to type in text. Below is a short app that demonstrates a virtual keyboard in action. When you type text using the keyboard, it is displayed on the label.&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 kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.vkeyboard import VKeyboard

class VKeyboardApp(App):
    title = "VKeyboard Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        self.display_label = Label(text="Type in!", font_size=40)
        root.add_widget(self.display_label)

        # Create the virtual keyboard
        keyboard = VKeyboard(size_hint=(1, 0.4))
        keyboard.bind(on_key_up=self.keyboard_on_key_up)
        root.add_widget(keyboard)

        return root

    def keyboard_on_key_up(self, *args):
        keycode = args[1]
        text = args[2]
        if keycode == "backspace":
            if (
                len(self.display_label.text) &amp;gt; 0
                and self.display_label.text != "Type in!"
            ):
                self.display_label.text = self.display_label.text[:-1]
                if self.display_label.text == "":
                    self.display_label.text = "Type in!"
        elif keycode == "spacebar":
            if self.display_label.text == "Type in!":
                self.display_label.text = " "
            else:
                self.display_label.text += " "
        elif keycode in {"enter", "shift", "alt", "ctrl", "escape", "tab", "capslock"}:
            pass
        else:
            if self.display_label.text == "Type in!":
                self.display_label.text = text
            else:
                self.display_label.text += text

VKeyboardApp().run()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we manually add a virtual keyboard to our app's interface using the &lt;code&gt;VKeyboard&lt;/code&gt; widget and display typed text using a label.&lt;/p&gt;
&lt;p&gt;When a key is released, the &lt;code&gt;keyboard_on_key_up()&lt;/code&gt; method processes the input. Printable characters are appended to the label text. Backspace removes the last character, and the spacebar inserts a space.&lt;/p&gt;
&lt;p class="admonition admonition-note"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-sticky-note"&gt;&lt;/i&gt;&lt;/span&gt;  You typically wouldn't use the &lt;code&gt;VKeyboard&lt;/code&gt; widget as in the example above. Input widgets, like &lt;code&gt;TextInput&lt;/code&gt;, will automatically bring up the virtual keyboard when focused on mobile devices.&lt;/p&gt;
&lt;p&gt;We ignore special keys like &lt;em&gt;Enter&lt;/em&gt;, &lt;em&gt;Shift&lt;/em&gt;, &lt;em&gt;Alt&lt;/em&gt;, &lt;em&gt;Ctrl&lt;/em&gt;, and &lt;em&gt;Escape&lt;/em&gt;. This allows us to interact with a virtual keyboard and see the input displayed dynamically in the label.&lt;/p&gt;
&lt;p class="admonition admonition-run"&gt;&lt;span class="admonition-kind"&gt;&lt;i class="fas fa-rocket"&gt;&lt;/i&gt;&lt;/span&gt; &lt;strong&gt;Run it!&lt;/strong&gt; A virtual keyboard appears at the button of the app's window, allowing you to enter text in touch-based devices. When you type on the virtual keyboard at the bottom of the app's window, the label reflects what you've typed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Kivy app showing a VKeyboard Widget for virtual keyboard input" src="https://www.pythonguis.com/static/tutorials/kivy/kivy-complex-ui-widgets/vkeyboard-widget-app-kivy.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/vkeyboard-widget-app-kivy.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/vkeyboard-widget-app-kivy.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/vkeyboard-widget-app-kivy.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/kivy/kivy-complex-ui-widgets/vkeyboard-widget-app-kivy.png?tr=w-600 600w" loading="lazy" width="720" height="1336"/&gt;
&lt;em&gt;A Kivy app showing a &lt;code&gt;VKeyboard&lt;/code&gt; Widget&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Kivy provides a rich set of complex UX widgets that you can use to create cross-platform Python GUI applications. In this tutorial, you learned how to use the &lt;code&gt;Spinner&lt;/code&gt; for dropdown selections, &lt;code&gt;DropDown&lt;/code&gt; for custom menus, &lt;code&gt;FileChooser&lt;/code&gt; for file browsing, &lt;code&gt;Popup&lt;/code&gt; for modal dialogs, &lt;code&gt;Bubble&lt;/code&gt; for contextual popups, &lt;code&gt;RecycleView&lt;/code&gt; for efficiently displaying large data sets, &lt;code&gt;TabbedPanel&lt;/code&gt; for tabbed interfaces, and &lt;code&gt;VKeyboard&lt;/code&gt; for virtual keyboard input. Using the examples above as inspiration, you should now be able to integrate these complex Kivy widgets into your own Python apps. See if you can extend these examples further, adding more widgets or functionality to them.&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.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="kivy"/><category term="widgets"/><category term="ux"/><category term="advanced"/><category term="foundation"/><category term="kivy-foundation"/></entry></feed>