<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - qt</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/qt.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2025-10-09T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><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.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="python"/><category term="intermediate"/><category term="settings"/><category term="qsettings"/><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;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.mfitzp.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>How to Show Only Icons in QTableView Cells (Hide Text) — Use custom model roles to display icons without text in your PyQt6 tables</title><link href="https://www.pythonguis.com/faq/show-only-icon-in-qtableview-cell-pyqt6/" rel="alternate"/><published>2025-04-13T06:52:05+00:00</published><updated>2025-04-13T06:52:05+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-04-13:/faq/show-only-icon-in-qtableview-cell-pyqt6/</id><summary type="html">When you're building a table-based interface with &lt;code&gt;QTableView&lt;/code&gt; in PyQt6, you'll often want to show icons in certain cells &amp;mdash; for example, a checkmark for &lt;code&gt;True&lt;/code&gt; and a cross for &lt;code&gt;False&lt;/code&gt;. But by default, Qt will also display the underlying data value as text alongside the icon. That means you end up with something like a green tick next to the word "True", which looks cluttered and unprofessional.</summary><content type="html">
            &lt;p&gt;When you're building a table-based interface with &lt;code&gt;QTableView&lt;/code&gt; in PyQt6, you'll often want to show icons in certain cells &amp;mdash; for example, a checkmark for &lt;code&gt;True&lt;/code&gt; and a cross for &lt;code&gt;False&lt;/code&gt;. But by default, Qt will also display the underlying data value as text alongside the icon. That means you end up with something like a green tick next to the word "True", which looks cluttered and unprofessional.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to display &lt;em&gt;only&lt;/em&gt; icons in your &lt;code&gt;QTableView&lt;/code&gt; cells, suppressing the text entirely. We'll build a complete working example so you can see exactly how it all fits together.&lt;/p&gt;
&lt;h2 id="how-qt-models-serve-data"&gt;How Qt Models Serve Data&lt;/h2&gt;
&lt;p&gt;Before we jump into the solution, it helps to understand a little about how &lt;code&gt;QTableView&lt;/code&gt; gets its data.&lt;/p&gt;
&lt;p&gt;When a &lt;code&gt;QTableView&lt;/code&gt; renders a cell, it asks the underlying model for data by calling the model's &lt;code&gt;data()&lt;/code&gt; method. But it doesn't just ask once &amp;mdash; it asks multiple times, each time with a different &lt;strong&gt;role&lt;/strong&gt;. Roles tell the model &lt;em&gt;what kind&lt;/em&gt; of data the view is looking for. The most common roles are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Qt.DisplayRole&lt;/code&gt; &amp;mdash; the text to show in the cell.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt.DecorationRole&lt;/code&gt; &amp;mdash; an icon or color swatch to show in the cell.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you return a value for &lt;code&gt;Qt.DisplayRole&lt;/code&gt;, that value gets converted to a string and displayed as text. When you return a &lt;code&gt;QIcon&lt;/code&gt; or &lt;code&gt;QColor&lt;/code&gt; for &lt;code&gt;Qt.DecorationRole&lt;/code&gt;, it appears as a small icon to the left of the text.&lt;/p&gt;
&lt;p&gt;So if you're returning &lt;em&gt;both&lt;/em&gt; a display value and a decoration for the same cell, you'll see both. The trick to showing only the icon is to return &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; on the cells where you want to suppress the text.&lt;/p&gt;
&lt;h2 id="a-basic-table-model-with-icons"&gt;A Basic Table Model with Icons&lt;/h2&gt;
&lt;p&gt;Let's start with a simple custom model that displays a numpy array of mixed data &amp;mdash; some strings, some numbers, and some boolean values. We'll add icons for the boolean columns.&lt;/p&gt;
&lt;p&gt;First, make sure you have PyQt6 and numpy installed:&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 numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We'll start from an application outline, that shows both icons and text for boolean 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;import sys
import numpy as np
from PyQt6.QtCore import Qt, QAbstractTableModel
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView


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

    def rowCount(self, parent=None):
        return self._data.shape[0]

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

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        value = self._data[index.row(), index.column()]

        if role == Qt.DisplayRole:
            return str(value)

        if role == Qt.DecorationRole:
            if isinstance(value, bool):
                if value:
                    return QIcon("tick.png")
                return QIcon("cross.png")

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTableView Icons Example")

        data = np.array([
            [True, "Alice", 32, False],
            [False, "Bob", 28, True],
            [True, "Charlie", 45, True],
            [False, "Diana", 36, False],
        ], dtype=object)

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)
        self.resize(500, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this, you'll see that the boolean cells show an icon &lt;em&gt;and&lt;/em&gt; the text "True" or "False". That's because &lt;code&gt;Qt.DisplayRole&lt;/code&gt; is returning &lt;code&gt;str(value)&lt;/code&gt; for every cell, including the boolean ones.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QTableView showing icons alongside boolean text values" src="https://www.pythonguis.com/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-600 600w" loading="lazy" width="561" height="500"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You'll need &lt;code&gt;tick.png&lt;/code&gt; and &lt;code&gt;cross.png&lt;/code&gt; icon files in your project folder for the icons to appear. You can use any small icon images you like, or grab free ones from a site like &lt;a href="https://icons8.com"&gt;icons8.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="suppressing-the-text-for-boolean-cells"&gt;Suppressing the Text for Boolean Cells&lt;/h2&gt;
&lt;p&gt;The fix is straightforward. In the &lt;code&gt;data()&lt;/code&gt; method, when the role is &lt;code&gt;Qt.DisplayRole&lt;/code&gt;, we check whether the cell's value is a boolean. If it is, we return &lt;code&gt;None&lt;/code&gt; instead of the string representation. Returning &lt;code&gt;None&lt;/code&gt; tells Qt "there's nothing to display as text for this cell."&lt;/p&gt;
&lt;p&gt;Update the &lt;code&gt;Qt.DisplayRole&lt;/code&gt; section of your &lt;code&gt;data()&lt;/code&gt; method 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;def data(self, index, role=Qt.DisplayRole):
    if not index.isValid():
        return None

    value = self._data[index.row(), index.column()]

    if role == Qt.DisplayRole:
        if isinstance(value, bool):
            return None  # Don't show text for boolean cells
        return str(value)

    if role == Qt.DecorationRole:
        if isinstance(value, bool):
            if value:
                return QIcon("tick.png")
            return QIcon("cross.png")

    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That's it! The two lines that make the difference are:&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 isinstance(value, bool):
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By returning &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; when the value is a boolean, we tell the view there's no text to render. The &lt;code&gt;Qt.DecorationRole&lt;/code&gt; still returns the icon as before, so the icon appears on its own &amp;mdash; clean and uncluttered.&lt;/p&gt;
&lt;h2 id="the-complete-working-example"&gt;The Complete Working Example&lt;/h2&gt;
&lt;p&gt;Here's the full code with the fix applied:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import numpy as np
from PyQt6.QtCore import Qt, QAbstractTableModel
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView


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

    def rowCount(self, parent=None):
        return self._data.shape[0]

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

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        value = self._data[index.row(), index.column()]

        if role == Qt.DisplayRole:
            if isinstance(value, bool):
                return None  # Suppress text for boolean values
            return str(value)

        if role == Qt.DecorationRole:
            if isinstance(value, bool):
                if value:
                    return QIcon("tick.png")
                return QIcon("cross.png")

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTableView Icons Example")

        data = np.array([
            [True, "Alice", 32, False],
            [False, "Bob", 28, True],
            [True, "Charlie", 45, True],
            [False, "Diana", 36, False],
        ], dtype=object)

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)
        self.resize(500, 300)


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, the boolean columns will show only the tick or cross icons, with no text beside them. The other columns (names and ages) continue to display their text values as normal.&lt;/p&gt;
&lt;h2 id="centering-the-icons"&gt;Centering the Icons&lt;/h2&gt;
&lt;p&gt;You might notice that the icons sit to the left of the cell by default. If you'd like them centered, you can handle &lt;code&gt;Qt.TextAlignmentRole&lt;/code&gt; in your &lt;code&gt;data()&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;if role == Qt.TextAlignmentRole:
    if isinstance(value, bool):
        return Qt.AlignCenter
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add this block alongside the other role checks inside your &lt;code&gt;data()&lt;/code&gt; method. This tells the view to center the content of boolean cells, which looks much tidier when the icon is the only thing in the cell.&lt;/p&gt;
&lt;h2 id="applying-this-pattern-to-other-data-types"&gt;Applying This Pattern to Other Data Types&lt;/h2&gt;
&lt;p&gt;The same approach works for any situation where you want to show an icon without text. The pattern is always the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Return your icon from &lt;code&gt;Qt.DecorationRole&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Return &lt;code&gt;None&lt;/code&gt; from &lt;code&gt;Qt.DisplayRole&lt;/code&gt; for that cell.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, if you had a column showing status values like &lt;code&gt;"active"&lt;/code&gt;, &lt;code&gt;"inactive"&lt;/code&gt;, or &lt;code&gt;"pending"&lt;/code&gt;, you could map each to a colored icon and suppress the 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;if role == Qt.DisplayRole:
    if index.column() == 2:  # Status column
        return None
    return str(value)

if role == Qt.DecorationRole:
    if index.column() == 2:  # Status column
        status_icons = {
            "active": QIcon("green.png"),
            "inactive": QIcon("red.png"),
            "pending": QIcon("yellow.png"),
        }
        return status_icons.get(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can use column index checks, value type checks, or any other logic to decide which cells should have their text suppressed.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Displaying only icons in &lt;code&gt;QTableView&lt;/code&gt; cells comes down to understanding how Qt's model/view role system works. The view asks for display text and decoration separately, so you have full control over what appears. By returning &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; on cells where you only want an icon, you get a clean, icon-only display while keeping the rest of your table's text intact.&lt;/p&gt;
&lt;p&gt;This is a small technique, but it makes a real difference in how polished your table interfaces look &amp;mdash; and it's a great example of why understanding the role system in Qt's model/view architecture is so valuable.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qtableview"/><category term="python"/><category term="icons"/><category term="model-view"/><category term="qt"/><category term="qt6"/></entry><entry><title>How to Show Only Icons in QTableView Cells (Hide Text) — Use custom model roles to display icons without text in your PySide6 tables</title><link href="https://www.pythonguis.com/faq/show-only-icon-in-qtableview-cell-pyside6/" rel="alternate"/><published>2025-04-13T06:52:05+00:00</published><updated>2025-04-13T06:52:05+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-04-13:/faq/show-only-icon-in-qtableview-cell-pyside6/</id><summary type="html">When you're building a table-based interface with &lt;code&gt;QTableView&lt;/code&gt; in PySide6 or PyQt6, you'll often want to show icons in certain cells &amp;mdash; for example, a checkmark for &lt;code&gt;True&lt;/code&gt; and a cross for &lt;code&gt;False&lt;/code&gt;. But by default, Qt will also display the underlying data value as text alongside the icon. That means you end up with something like a green tick next to the word "True", which looks cluttered and unprofessional.</summary><content type="html">
            &lt;p&gt;When you're building a table-based interface with &lt;code&gt;QTableView&lt;/code&gt; in PySide6 or PyQt6, you'll often want to show icons in certain cells &amp;mdash; for example, a checkmark for &lt;code&gt;True&lt;/code&gt; and a cross for &lt;code&gt;False&lt;/code&gt;. But by default, Qt will also display the underlying data value as text alongside the icon. That means you end up with something like a green tick next to the word "True", which looks cluttered and unprofessional.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to display &lt;em&gt;only&lt;/em&gt; icons in your &lt;code&gt;QTableView&lt;/code&gt; cells, suppressing the text entirely. We'll build a complete working example using &lt;code&gt;QAbstractTableModel&lt;/code&gt; so you can see exactly how it all fits together.&lt;/p&gt;
&lt;h2 id="how-qt-models-serve-data-to-qtableview"&gt;How Qt Models Serve Data to QTableView&lt;/h2&gt;
&lt;p&gt;Before we jump into the solution, it helps to understand a little about how &lt;code&gt;QTableView&lt;/code&gt; gets its data from the model.&lt;/p&gt;
&lt;p&gt;When a &lt;code&gt;QTableView&lt;/code&gt; renders a cell, it asks the underlying model for data by calling the model's &lt;code&gt;data()&lt;/code&gt; method. But it doesn't just ask once &amp;mdash; it asks multiple times, each time with a different &lt;strong&gt;role&lt;/strong&gt;. Roles tell the model &lt;em&gt;what kind&lt;/em&gt; of data the view is looking for. The most common roles are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Qt.DisplayRole&lt;/code&gt; &amp;mdash; the text to show in the cell.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Qt.DecorationRole&lt;/code&gt; &amp;mdash; an icon or color swatch to show in the cell.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you return a value for &lt;code&gt;Qt.DisplayRole&lt;/code&gt;, that value gets converted to a string and displayed as text. When you return a &lt;code&gt;QIcon&lt;/code&gt; or &lt;code&gt;QColor&lt;/code&gt; for &lt;code&gt;Qt.DecorationRole&lt;/code&gt;, it appears as a small icon to the left of the text.&lt;/p&gt;
&lt;p&gt;So if you're returning &lt;em&gt;both&lt;/em&gt; a display value and a decoration for the same cell, you'll see both. The trick to showing only the icon is to return &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; on the cells where you want to suppress the text.&lt;/p&gt;
&lt;h2 id="a-basic-qtableview-model-with-icons"&gt;A Basic QTableView Model with Icons&lt;/h2&gt;
&lt;p&gt;Let's start with a simple custom table model that displays a numpy array of mixed data &amp;mdash; some strings, some numbers, and some boolean values. We'll add icons for the boolean columns using &lt;code&gt;Qt.DecorationRole&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, make sure you have PySide6 and numpy installed:&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 pyside6 numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We'll start from an application outline, that shows both icons and text for boolean 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;import sys
import numpy as np
from PySide6.QtCore import Qt, QAbstractTableModel
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView


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

    def rowCount(self, parent=None):
        return self._data.shape[0]

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

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        value = self._data[index.row(), index.column()]

        if role == Qt.DisplayRole:
            return str(value)

        if role == Qt.DecorationRole:
            if isinstance(value, bool):
                if value:
                    return QIcon("tick.png")
                return QIcon("cross.png")

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTableView Icons Example")

        data = np.array([
            [True, "Alice", 32, False],
            [False, "Bob", 28, True],
            [True, "Charlie", 45, True],
            [False, "Diana", 36, False],
        ], dtype=object)

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)
        self.resize(500, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this, you'll see that the boolean cells show an icon &lt;em&gt;and&lt;/em&gt; the text "True" or "False". That's because &lt;code&gt;Qt.DisplayRole&lt;/code&gt; is returning &lt;code&gt;str(value)&lt;/code&gt; for every cell, including the boolean ones.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QTableView showing icons alongside boolean text values" src="https://www.pythonguis.com/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/show-only-icon-in-qtableview-cell/9E2jaHpjo60dsjrItXbzTO11Xct.png?tr=w-600 600w" loading="lazy" width="561" height="500"/&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You'll need &lt;code&gt;tick.png&lt;/code&gt; and &lt;code&gt;cross.png&lt;/code&gt; icon files in your project folder for the icons to appear. You can use any small icon images you like, or grab free ones from a site like &lt;a href="https://icons8.com"&gt;icons8.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="suppressing-text-for-icon-only-qtableview-cells"&gt;Suppressing Text for Icon-Only QTableView Cells&lt;/h2&gt;
&lt;p&gt;The fix is straightforward. In the &lt;code&gt;data()&lt;/code&gt; method, when the role is &lt;code&gt;Qt.DisplayRole&lt;/code&gt;, we check whether the cell's value is a boolean. If it is, we return &lt;code&gt;None&lt;/code&gt; instead of the string representation. Returning &lt;code&gt;None&lt;/code&gt; tells Qt "there's nothing to display as text for this cell."&lt;/p&gt;
&lt;p&gt;Update the &lt;code&gt;Qt.DisplayRole&lt;/code&gt; section of your &lt;code&gt;data()&lt;/code&gt; method 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;def data(self, index, role=Qt.DisplayRole):
    if not index.isValid():
        return None

    value = self._data[index.row(), index.column()]

    if role == Qt.DisplayRole:
        if isinstance(value, bool):
            return None  # Don't show text for boolean cells
        return str(value)

    if role == Qt.DecorationRole:
        if isinstance(value, bool):
            if value:
                return QIcon("tick.png")
            return QIcon("cross.png")

    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That's it! The two lines that make the difference are:&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 isinstance(value, bool):
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By returning &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; when the value is a boolean, we tell the view there's no text to render. The &lt;code&gt;Qt.DecorationRole&lt;/code&gt; still returns the icon as before, so the icon appears on its own &amp;mdash; clean and uncluttered.&lt;/p&gt;
&lt;h2 id="complete-working-example-icon-only-qtableview-cells"&gt;Complete Working Example: Icon-Only QTableView Cells&lt;/h2&gt;
&lt;p&gt;Here's the full code with the fix applied:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import numpy as np
from PySide6.QtCore import Qt, QAbstractTableModel
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QMainWindow, QTableView


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

    def rowCount(self, parent=None):
        return self._data.shape[0]

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

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        value = self._data[index.row(), index.column()]

        if role == Qt.DisplayRole:
            if isinstance(value, bool):
                return None  # Suppress text for boolean values
            return str(value)

        if role == Qt.DecorationRole:
            if isinstance(value, bool):
                if value:
                    return QIcon("tick.png")
                return QIcon("cross.png")

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTableView Icons Example")

        data = np.array([
            [True, "Alice", 32, False],
            [False, "Bob", 28, True],
            [True, "Charlie", 45, True],
            [False, "Diana", 36, False],
        ], dtype=object)

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)
        self.resize(500, 300)


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, the boolean columns will show only the tick or cross icons, with no text beside them. The other columns (names and ages) continue to display their text values as normal.&lt;/p&gt;
&lt;h2 id="centering-icons-in-qtableview-cells"&gt;Centering Icons in QTableView Cells&lt;/h2&gt;
&lt;p&gt;You might notice that the icons sit to the left of the cell by default. If you'd like them centered, you can handle &lt;code&gt;Qt.TextAlignmentRole&lt;/code&gt; in your &lt;code&gt;data()&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;if role == Qt.TextAlignmentRole:
    if isinstance(value, bool):
        return Qt.AlignCenter
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add this block alongside the other role checks inside your &lt;code&gt;data()&lt;/code&gt; method. This tells the view to center the content of boolean cells, which looks much tidier when the icon is the only thing in the cell.&lt;/p&gt;
&lt;h2 id="applying-this-pattern-to-other-data-types"&gt;Applying This Pattern to Other Data Types&lt;/h2&gt;
&lt;p&gt;The same approach works for any situation where you want to show an icon without text in a &lt;code&gt;QTableView&lt;/code&gt;. The pattern is always the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Return your icon from &lt;code&gt;Qt.DecorationRole&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Return &lt;code&gt;None&lt;/code&gt; from &lt;code&gt;Qt.DisplayRole&lt;/code&gt; for that cell.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, if you had a column showing status values like &lt;code&gt;"active"&lt;/code&gt;, &lt;code&gt;"inactive"&lt;/code&gt;, or &lt;code&gt;"pending"&lt;/code&gt;, you could map each to a colored icon and suppress the 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;if role == Qt.DisplayRole:
    if index.column() == 2:  # Status column
        return None
    return str(value)

if role == Qt.DecorationRole:
    if index.column() == 2:  # Status column
        status_icons = {
            "active": QIcon("green.png"),
            "inactive": QIcon("red.png"),
            "pending": QIcon("yellow.png"),
        }
        return status_icons.get(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can use column index checks, value type checks, or any other logic to decide which cells should have their text suppressed.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Displaying only icons in &lt;code&gt;QTableView&lt;/code&gt; cells comes down to understanding how Qt's model roles work. The view asks for display text and decoration separately through &lt;code&gt;Qt.DisplayRole&lt;/code&gt; and &lt;code&gt;Qt.DecorationRole&lt;/code&gt;, so you have full control over what appears. By returning &lt;code&gt;None&lt;/code&gt; for &lt;code&gt;Qt.DisplayRole&lt;/code&gt; on cells where you only want an icon, you get a clean, icon-only display while keeping the rest of your table's text intact.&lt;/p&gt;
&lt;p&gt;This is a small technique, but it makes a real difference in how polished your PySide6 and PyQt6 table interfaces look &amp;mdash; and it's a great example of why understanding the role system in Qt's model/view architecture is so valuable.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="pyqt6"/><category term="qtableview"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Build a Desktop Sticky Notes Application with PySide6 &amp; SQLAlchemy — Create moveable desktop reminders with Python</title><link href="https://www.pythonguis.com/examples/pyside6-desktop-sticky-notes/" rel="alternate"/><published>2025-04-03T12:01:00+00:00</published><updated>2025-04-03T12:01:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2025-04-03:/examples/pyside6-desktop-sticky-notes/</id><summary type="html">Do you ever find yourself needing to take a quick note of some information but have nowhere to put it? Then this app is for you! This virtual sticky notes (or Post-it notes) app allows you to keep short text notes quickly from anywhere via the system tray. Create a new note, paste what you need in. It'll stay there until you delete it.</summary><content type="html">
            &lt;p&gt;Do you ever find yourself needing to take a quick note of some information but have nowhere to put it? Then this app is for you! This virtual sticky notes (or Post-it notes) app allows you to keep short text notes quickly from anywhere via the system tray. Create a new note, paste what you need in. It'll stay there until you delete it.&lt;/p&gt;
&lt;p&gt;The application is written in PySide6 and the notes are implemented as frameless windows &amp;mdash; that is, windows without any title bar or controls. Notes can be dragged around the desktop and edited at will. Text in the notes and note positions are stored in a SQLite database, via SQLAlchemy, with note details and positions being restored on each session.&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 is quite a complicated example, but we'll be walking through it slowly step by step. The &lt;a href="https://www.pythonguis.com/d/sticky-notes-pyside6.zip"&gt;full source code&lt;/a&gt; is available, with working examples at each stage of the development if you get stuck.&lt;/p&gt;
&lt;h2 id="setting-up-the-working-environment"&gt;Setting Up the Working Environment&lt;/h2&gt;
&lt;p&gt;In this tutorial, we'll use the PySide6 library to build the sticky notes app's GUI. We'll assume that you have a basic understanding of PySide6 apps.&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 the basics of PySide6, check out the complete &lt;a href="https://www.pythonguis.com/pyside6-tutorial/"&gt;PySide6 Tutorials&lt;/a&gt; or my book &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;Create GUI Applications with Python &amp;amp; PySide6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To store the notes between sessions, we will use &lt;a href="https://www.sqlalchemy.org/"&gt;&lt;code&gt;SQLAlchemy&lt;/code&gt;&lt;/a&gt; with a SQLite database (a file). Don't worry if you're not familiar with SQLAlchemy, we won't be going deep into that topic &amp;amp; have working examples you can copy.&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="647fc52c5c644033a6dc55ae38b31263" v-on:click="switch_tab"&gt;macOS&lt;/li&gt;
&lt;li class="tab-link" data-tab="2edc17630fe64605afb29388036fd21c" v-on:click="switch_tab"&gt;Windows&lt;/li&gt;
&lt;li class="tab-link" data-tab="c1b1f7a986e040b5b4a09d8e4ff00ccf" v-on:click="switch_tab"&gt;Linux&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="647fc52c5c644033a6dc55ae38b31263"&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 notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="2edc17630fe64605afb29388036fd21c"&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 notes/
&amp;gt; cd notes
&amp;gt; python -m venv venv
&amp;gt; venv\Scripts\activate.bat
(venv)&amp;gt; pip install pyside6 sqlalchemy
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="c1b1f7a986e040b5b4a09d8e4ff00ccf"&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 notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
&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;notes/&lt;/code&gt;  folder for storing your project. Inside that folder, you create a new virtual environment, activate it, and install &lt;a href="https://pypi.org/project/PySide6/"&gt;PySide6&lt;/a&gt; and &lt;a href="https://pypi.org/project/SQLAlchemy/"&gt;SQLAlchemy&lt;/a&gt; from PyPi.&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-sticky-notes-gui-with-pyside6"&gt;Building the Sticky Notes GUI with PySide6&lt;/h2&gt;
&lt;p&gt;Let's start by building a simple notes UI where we can create, move and close notes on the desktop. We'll deal with persistance later.&lt;/p&gt;
&lt;p&gt;The UI for our desktop sticky notes will be &lt;em&gt;a bit strange&lt;/em&gt; since there is no central window, all the windows are independent yet look identical (aside from the contents). We also need the app to remain open in the background, using the system tray or toolbar, so we can show/hide the notes again without closing and re-opening the application each time.&lt;/p&gt;
&lt;p&gt;We'll start by defining a single note, and then deal with these other issues later. Create a new file named &lt;code&gt;notes.py&lt;/code&gt; and add the following outline application to it.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication, QTextEdit, QVBoxLayout, QWidget

app = QApplication(sys.argv)


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout()
        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)


note = NoteWindow()
note.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code we first create a Qt &lt;code&gt;QApplication&lt;/code&gt; instance. This needs to be done before creating our widgets. Next we define a simple custom window class &lt;code&gt;NoteWindow&lt;/code&gt; by subclassing &lt;code&gt;QWidget&lt;/code&gt;. We add a vertical layout to the window, and enter a single &lt;code&gt;QTextEdit&lt;/code&gt; widget. We then create an instance of this window object as &lt;code&gt;note&lt;/code&gt; and show it by calling &lt;code&gt;.show()&lt;/code&gt;. This puts the window on the desktop. Finally, we start up our application by calling &lt;code&gt;app.exec()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can run this file like any other Python script.&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 notes.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When the application launches you'll see the following on your desktop.&lt;/p&gt;
&lt;p&gt;&lt;img alt='Our editable "note" on the desktop' src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-window.png?tr=w-600 600w" loading="lazy" width="730" height="533"/&gt;
&lt;em&gt;Simple "notes" window on the desktop&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you click in the text editor in the middle, you can enter some text.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Technically&lt;/em&gt; this is a note, but we can do better.&lt;/p&gt;
&lt;h2 id="styling-the-sticky-notes-with-qss-qt-style-sheets"&gt;Styling the Sticky Notes with QSS (Qt Style Sheets)&lt;/h2&gt;
&lt;p&gt;Our note doesn't look anything like a sticky note yet. Let's change that by applying some simple styles to it.&lt;/p&gt;
&lt;p&gt;Firstly we can change the colors of the window, textarea and text. In Qt there are multiple ways to do this -- for example, we could override the system palette definition for the window. However, the simplest approach is to use QSS, which is Qt's version of CSS.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication, QTextEdit, QVBoxLayout, QWidget

app = QApplication(sys.argv)


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()
        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)


note = NoteWindow()
note.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the code above we have set a background color of hex &lt;code&gt;#ffff99&lt;/code&gt; for our note window, and set the text color to hex &lt;code&gt;#62622f&lt;/code&gt; a sort of muddy brown. The &lt;code&gt;border:0&lt;/code&gt; removes the frame from the text edit, which otherwise would appear as a line on the bottom of the window. Finally, we set the font size to 16 points, to make the notes easier to read.&lt;/p&gt;
&lt;p&gt;If you run the code now you'll see this, much more notely note.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The note with our QSS styles applied" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-styled.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-styled.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-styled.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-styled.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-styled.png?tr=w-600 600w" loading="lazy" width="791" height="541"/&gt;
&lt;em&gt;The note with the QSS styling applied&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="remove-window-decorations-with-frameless-window-flags"&gt;Remove Window Decorations with Frameless Window Flags&lt;/h2&gt;
&lt;p&gt;The last thing breaking the illusion of a sticky note on the desktop is the window decorations -- the titlebar and window controls. We can remove these using Qt &lt;em&gt;window flags&lt;/em&gt;. We can also use a window flag to make the notes appear on top of other windows. Later we'll handle hiding and showing the notes via a tray 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 PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)


note = NoteWindow()
note.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To set window flags, we need to import the Qt flags from the &lt;code&gt;QtCore&lt;/code&gt; namespace. Then you can set flags on the window using &lt;code&gt;.setWindowFlags()&lt;/code&gt;. Note that since windows have flags already set, and we don't want to replace them all, we get the current flags with &lt;code&gt;.windowFlags()&lt;/code&gt; and then add the additional flags to it using boolean OR &lt;code&gt;|&lt;/code&gt;. We've added two flags here -- &lt;code&gt;Qt.WindowType.FramelessWindowHint&lt;/code&gt; which removes the window decorations, and &lt;code&gt;Qt.WindowType.WindowStaysOnTopHint&lt;/code&gt; which keeps the windows on top.&lt;/p&gt;
&lt;p&gt;Run this and you'll see a window with the decorations removed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Note with the window decorations removed" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-nodecoration.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-nodecoration.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-nodecoration.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-nodecoration.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-nodecoration.png?tr=w-600 600w" loading="lazy" width="781" height="462"/&gt;
&lt;em&gt;Note with the window decorations removed&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;  With the window decorations removed you no longer have access to the close button. But you can still close the window using Alt-F4 (Windows) or the application menu (macOS).&lt;/p&gt;
&lt;p&gt;While you can close the window, it'd be nicer if there was a button to do it. We can add a custom close button using &lt;code&gt;QPushButton&lt;/code&gt; and hook this up to the window's &lt;code&gt;.close()&lt;/code&gt; method to re-implement 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;import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QPushButton,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()
        # layout.setSpacing(0)

        buttons = QHBoxLayout()
        self.close_btn = QPushButton("&amp;times;")
        self.close_btn.setStyleSheet(
            "font-weight: bold; font-size: 25px; width: 25px; height: 25px;"
        )
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        self.close_btn.clicked.connect(self.close)
        buttons.addStretch()  # Add stretch on left to push button right.
        buttons.addWidget(self.close_btn)
        layout.addLayout(buttons)

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)


note = NoteWindow()
note.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Our close button is created using &lt;code&gt;QPushButton&lt;/code&gt; with a unicode multiplication symbol (an x) as the label. We set a stylesheet on this button to size the label and button. Then we set a custom cursor on the button to make it clearer that this is a clickable &lt;em&gt;thing&lt;/em&gt; that performs an action. Finally, we connect the &lt;code&gt;.clicked&lt;/code&gt; signal of the button to the window's close method &lt;code&gt;self.close&lt;/code&gt;. The button will close the window.&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;  Later we'll use this button to delete notes.&lt;/p&gt;
&lt;p&gt;To add the close button to the top right of the window, we create a horizontal layout with &lt;code&gt;QHBoxLayout&lt;/code&gt;. We first add a stretch, then the push button. This has the effect of pushing the button to the right. Finally, we add our buttons layout to the main layout of the note, before the text edit. This puts it at the top of the window.&lt;/p&gt;
&lt;p&gt;Run the code now and our note is complete!&lt;/p&gt;
&lt;p&gt;&lt;img alt="The complete note UI with close button" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-complete.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-complete.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-complete.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-complete.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-complete.png?tr=w-600 600w" loading="lazy" width="888" height="503"/&gt;
&lt;em&gt;The complete note UI with close button&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="making-notes-draggable-with-mouse-events"&gt;Making Notes Draggable with Mouse Events&lt;/h2&gt;
&lt;p&gt;The note &lt;em&gt;looks&lt;/em&gt; like a sticky note now, but we can't move it around and there is only one (unless we run the application multiple times concurrently). We'll fix both of those next, starting with the moveability of the notes.&lt;/p&gt;
&lt;p&gt;This is fairly straightforward to achieve in PySide6 because Qt makes the raw mouse events available on all widgets. To implement dragging, we can intercept these events and update the position of the window based on the distance the mouse has moved.&lt;/p&gt;
&lt;p&gt;To implement this, add the following two methods to the bottom of the &lt;code&gt;NoteWindow&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;class NoteWindow(QWidget):
    # ... existing code skipped

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Clicking and dragging a window involves three actions: the mouse press, the mouse move and the mouse release. We have defined two methods here &lt;code&gt;mousePressEvent&lt;/code&gt; and &lt;code&gt;mouseMoveEvent&lt;/code&gt;. In &lt;code&gt;mousePressEvent&lt;/code&gt; we receive the initial press of the mouse and store the position where the click occurred. This method is only called on the initial press of the mouse when starting to drag the window.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;mouseMoveEvent&lt;/code&gt; is called on every &lt;em&gt;subsequent&lt;/em&gt; move while the mouse button &lt;em&gt;remains&lt;/em&gt; pressed. On each move we take the new mouse position and subtract the previous position to get the &lt;em&gt;delta&lt;/em&gt; -- that is, the change in mouse position from the initial press to the current event. Then we &lt;em&gt;move&lt;/em&gt; the window by that amount, storing the new previous position after the move.&lt;/p&gt;
&lt;p&gt;The effect of this is that every time the &lt;code&gt;mouseMoveEvent&lt;/code&gt; method is called, the window moves by the amount that the mouse has moved since the last call. The window moves -- or is dragged -- by the mouse.&lt;/p&gt;
&lt;h2 id="creating-multiple-notes"&gt;Creating Multiple Notes&lt;/h2&gt;
&lt;p&gt;The note looks like a note, it is now moveable, but there is still only a single note -- not hugely useful! Let's fix that now.&lt;/p&gt;
&lt;p&gt;Currently we're creating the &lt;code&gt;NoteWindow&lt;/code&gt; when the application starts up, just before we call &lt;code&gt;app.exec()&lt;/code&gt;. If we create new notes while the application is running it will need to happen in a function or method, which is triggered somehow. This introduces a new problem, since we need to have some way to store the &lt;code&gt;NoteWindow&lt;/code&gt; objects so they aren't automatically deleted (and the window closed) when the function or method exits.&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;  Python automatically deletes objects when they fall out of scope if there aren't any remaining references to them.&lt;/p&gt;
&lt;p&gt;We can solve this by storing the &lt;code&gt;NoteWindow&lt;/code&gt; objects somewhere. Usually we'd do this on our main window, but in this app there is no main window. There are a few options here, but in this case we're going to use a simple dictionary.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QPushButton,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)

# Store references to the NoteWindow objects in this, keyed by id.
active_notewindows = {}


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()

        buttons = QHBoxLayout()
        self.close_btn = QPushButton("&amp;times;")
        self.close_btn.setStyleSheet(
            "font-weight: bold; font-size: 25px; width: 25px; height: 25px;"
        )
        self.close_btn.clicked.connect(self.close)
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        buttons.addStretch()  # Add stretch on left to push button right.
        buttons.addWidget(self.close_btn)
        layout.addLayout(buttons)

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)

        # Store a reference to this note in the
        active_notewindows[id(self)] = self

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()


def create_notewindow():
    note = NoteWindow()
    note.show()


create_notewindow()
create_notewindow()
create_notewindow()
create_notewindow()
app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code we've added our &lt;code&gt;active_notewindows&lt;/code&gt; dictionary. This holds references to our &lt;code&gt;NoteWindow&lt;/code&gt; objects, keyed by &lt;code&gt;id()&lt;/code&gt;. Note that this is Python's internal id for this object, so it is consistent and unique. We can use this same id to remove the note. We add each note to this dictionary at the bottom of it's &lt;code&gt;__init__&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Next we've implemented a &lt;code&gt;create_notewindow()&lt;/code&gt; function which creates an instance of &lt;code&gt;NoteWindow&lt;/code&gt; and shows it, just as before. Nothing else is needed, since the note itself handles storing it's references on creation.&lt;/p&gt;
&lt;p&gt;Finally, we've added multiple calls to &lt;code&gt;create_notewindow()&lt;/code&gt; to create multiple notes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Multiple notes on the desktop" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-multiple.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-multiple.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-multiple.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-multiple.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-multiple.png?tr=w-600 600w" loading="lazy" width="1282" height="881"/&gt;
&lt;em&gt;Multiple notes on the desktop&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="adding-a-system-tray-icon-with-qsystemtrayicon"&gt;Adding a System Tray Icon with QSystemTrayIcon&lt;/h2&gt;
&lt;p&gt;We can now create multiple notes programmatically, but we want to be able to do this from the UI. We could implement this behavior on the notes themselves, but then it wouldn't work if all the notes had been closed or hidden. Instead, we'll create a system tray application -- this will show in the system tray on Windows, or on the macOS toolbar. Users can use this to create new notes, and quit the application.&lt;/p&gt;
&lt;p&gt;There's quite a lot to this, so we'll step through it in stages.&lt;/p&gt;
&lt;p&gt;Update the code, adding the imports shown at the top, and the rest following the definition of &lt;code&gt;create_notewindow&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 PySide6.QtCore import Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

# ... code hidden up to create_notewindow() definition

create_notewindow()

# Create system tray icon
icon = QIcon("sticky-note.png")

# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)


def handle_tray_click(reason):
    # If the tray is left-clicked, create a new note.
    if (
        QSystemTrayIcon.ActivationReason(reason)
        == QSystemTrayIcon.ActivationReason.Trigger
    ):
        create_notewindow()


tray.activated.connect(handle_tray_click)

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code we've first created a &lt;code&gt;QIcon&lt;/code&gt; object passing in the filename of the icon to use. I'm using a sticky note icon from the &lt;a href="http://p.yusukekamiyamane.com/"&gt;Fugue icon set&lt;/a&gt; by designer Yusuke Kamiyamane. Feel free to use any icon you prefer.&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;  We're using a relative path here. If you don't see the icon, make sure you're running the script from the same folder &lt;em&gt;or&lt;/em&gt; provide the path.&lt;/p&gt;
&lt;p&gt;The system tray icon is managed through a &lt;code&gt;QSystemTrayIcon&lt;/code&gt; object. We set our icon on this, and set the tray icon to visible (so it is not automatically hidden by Windows).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QSystemTrayIcon&lt;/code&gt; has a signal &lt;code&gt;activated&lt;/code&gt; which fires whenever the icon is activated in some way -- for example, being clicked with the left or right mouse button. We're only interested in a single left click for now -- we'll use the right click for our menu shortly. To handle the left click, we create a handler function which accepts &lt;code&gt;reason&lt;/code&gt; (the reason for the activation) and then checks this against &lt;code&gt;QSystemTrayIcon.ActivationReason.Trigger&lt;/code&gt;. This is the reason reported when a left click is used.&lt;/p&gt;
&lt;p&gt;If the left mouse button has been clicked, we call &lt;code&gt;create_notewindow()&lt;/code&gt; to create a new instance of a note.&lt;/p&gt;
&lt;p&gt;If you run this example now, you'll see the sticky note in your tray and clicking on it will create a new note on the current desktop! You can create as many notes as you like, and once you close them all the application will close.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The sticky note icon in the tray" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-tray-windows.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-windows.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-windows.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-windows.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-windows.png?tr=w-600 600w" loading="lazy" width="322" height="146"/&gt;
&lt;em&gt;The sticky note icon in the system tray on Windows&lt;/em&gt;&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 is happening because by default Qt will close an application once all its windows have closed. This can be disabled, but we need to add another way to quit before we do it, otherwise our app will be &lt;em&gt;unstoppable&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="adding-a-context-menu-to-the-system-tray"&gt;Adding a Context Menu to the System Tray&lt;/h2&gt;
&lt;p&gt;To allow the notes application to be closed from the tray, we need a menu. System tray menus are normally accessible through right-clicking on the icon. To implement that we can set a &lt;code&gt;QMenu&lt;/code&gt; as a &lt;em&gt;context&lt;/em&gt; menu on the &lt;code&gt;QSystemTrayIcon&lt;/code&gt;. The actions in menus in Qt are defined using &lt;code&gt;QAction&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 PySide6.QtCore import Qt
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMenu,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

# ... code hidden up to handle_tray_click

tray.activated.connect(handle_tray_click)


# Don't automatically close app when the last window is closed.
app.setQuitOnLastWindowClosed(False)

# Create the menu
menu = QMenu()
add_note_action = QAction("Add note")
add_note_action.triggered.connect(create_notewindow)
menu.addAction(add_note_action)

# Add a Quit option to the menu.
quit_action = QAction("Quit")
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)

# Add the menu to the tray
tray.setContextMenu(menu)


app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We create the menu using &lt;code&gt;QMenu&lt;/code&gt;. Actions are created using &lt;code&gt;QAction&lt;/code&gt; passing in the label as a string. This is the text that will be shown for the menu item.
The &lt;code&gt;.triggered&lt;/code&gt; signal fires when the action is clicked (in a menu, or toolbar) or activated through a keyboard shortcut. Here we've connected the add note action to our &lt;code&gt;create_notewindow&lt;/code&gt; function. We've also added an action to quit the application. This is connected to the built-in &lt;code&gt;.quit&lt;/code&gt; slot on our &lt;code&gt;QApplication&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;The menu is set on the tray using &lt;code&gt;.setContextMenu()&lt;/code&gt;. In Qt context menus are automatically shown when the user right clicks on the tray.&lt;/p&gt;
&lt;p&gt;Finally, we have also disabled the behavior of closing the application when the last window is closed using &lt;code&gt;app.setQuitOnLastWindowClosed(False)&lt;/code&gt;. Now, once you close all the windows, the application will remain running in the background. You can close it by going to the tray, right-clicking and selecting "Quit".&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 find this annoying while developing, just comment this line out again.&lt;/p&gt;
&lt;p&gt;We've had a lot of changes so far, so here is the current complete 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 sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)

# Store references to the NoteWindow objects in this, keyed by id.
active_notewindows = {}


class NoteWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()

        buttons = QHBoxLayout()
        self.close_btn = QPushButton("&amp;times;")
        self.close_btn.setStyleSheet(
            "font-weight: bold; font-size: 25px; width: 25px; height: 25px;"
        )
        self.close_btn.clicked.connect(self.close)
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        buttons.addStretch()  # Add stretch on left to push button right.
        buttons.addWidget(self.close_btn)
        layout.addLayout(buttons)

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)

        # Store a reference to this note in the active_notewindows
        active_notewindows[id(self)] = self

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()


def create_notewindow():
    note = NoteWindow()
    note.show()


create_notewindow()

# Create the icon
icon = QIcon("sticky-note.png")

# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)


def handle_tray_click(reason):
    # If the tray is left-clicked, create a new note.
    if (
        QSystemTrayIcon.ActivationReason(reason)
        == QSystemTrayIcon.ActivationReason.Trigger
    ):
        create_notewindow()


tray.activated.connect(handle_tray_click)

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this now you will be able to right click the note in the tray to show the menu.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The sticky note icon in the tray showing its context menu" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-tray-active-windows.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-active-windows.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-active-windows.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-active-windows.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-tray-active-windows.png?tr=w-600 600w" loading="lazy" width="333" height="143"/&gt;
&lt;em&gt;The sticky note icon in the tray showing its context menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Test the &lt;em&gt;Add note&lt;/em&gt; and &lt;em&gt;Quit&lt;/em&gt; functionality to make sure they're working.&lt;/p&gt;
&lt;p&gt;So, now we have our note UI implemented, the ability to create and remove notes and a persistent tray icon where we can also create notes &amp;amp; close the application. The last piece of the puzzle is persisting the notes between runs of the application -- if we leave a note on the desktop, we want it to still be there if we come back tomorrow. We'll implement that next.&lt;/p&gt;
&lt;h2 id="setting-up-the-sqlite-database-with-sqlalchemy"&gt;Setting Up the SQLite Database with SQLAlchemy&lt;/h2&gt;
&lt;p&gt;To be able to store and load notes, we need an underlying data model. For this demo we're using SQLAlchemy as an interface to an SQLite database. This provides an Object-Relational Mapping (ORM) interface, which is a fancy way of saying we can interact with the database through Python objects.&lt;/p&gt;
&lt;p&gt;We'll define our database in a separate file, to keep the UI file manageable. So start by creating a new file named &lt;code&gt;database.py&lt;/code&gt; in your project folder.&lt;/p&gt;
&lt;p&gt;In that file add the imports for SQLAlchemy, and instantiate the &lt;code&gt;Base&lt;/code&gt; class for our models.&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 sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next in the same &lt;code&gt;database.py&lt;/code&gt; file, define our note database model. This inherits from the &lt;code&gt;Base&lt;/code&gt; class we've just created, by calling &lt;code&gt;declarative_base()&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;class Note(Base):
    __tablename__ = "note"
    id = Column(Integer, primary_key=True)
    text = Column(String(1000), nullable=False)
    x = Column(Integer, nullable=False, default=0)
    y = Column(Integer, nullable=False, default=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Each note object has 4 properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;id&lt;/strong&gt; the unique ID for the given note, used to delete from the database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;text&lt;/strong&gt; the text content of the note&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;x&lt;/strong&gt; the &lt;em&gt;x&lt;/em&gt; position on the screen&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;y&lt;/strong&gt; the &lt;em&gt;y&lt;/em&gt; position on the screen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next we need to create the &lt;em&gt;engine&lt;/em&gt; -- in this case, this is our SQLite file, which we're calling &lt;code&gt;notes.db&lt;/code&gt;. We can then create the tables (if they don't already exist). Since our &lt;code&gt;Note&lt;/code&gt; class registers itself with the &lt;code&gt;Base&lt;/code&gt; we can do that by calling &lt;code&gt;create_all&lt;/code&gt; on the &lt;code&gt;Base&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;engine = create_engine("sqlite:///notes.db")

Base.metadata.create_all(engine)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save the &lt;code&gt;database.py&lt;/code&gt; file and run it&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 database.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After it is complete, if you look in the folder you should see the &lt;code&gt;notes.db&lt;/code&gt;. This file contains the table structure for the &lt;code&gt;Note&lt;/code&gt; model we defined above.&lt;/p&gt;
&lt;p&gt;Finally, we need a &lt;em&gt;session&lt;/em&gt; to interact with the database from the UI. Since we only need a single session when the app is running, we can go ahead and create it in this file and then import it into the UI code.&lt;/p&gt;
&lt;p&gt;Add the following to &lt;code&gt;database.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;# Create a session to handle updates.
Session = sessionmaker(bind=engine)
session = Session()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The final complete code for our database interface 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;from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

Base = declarative_base()


class Note(Base):
    __tablename__ = "note"
    id = Column(Integer, primary_key=True)
    text = Column(String(1000), nullable=False)
    x = Column(Integer, nullable=False, default=0)
    y = Column(Integer, nullable=False, default=0)


engine = create_engine("sqlite:///notes.db")

Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now that our data model is defined, and our database created, we can go ahead and interface our &lt;code&gt;Notes&lt;/code&gt; model into the UI. This will allow us to load notes at startup (to show existing notes), save notes when they are updated and delete notes when they are removed.&lt;/p&gt;
&lt;h2 id="integrating-sqlalchemy-with-the-pyside6-ui"&gt;Integrating SQLAlchemy with the PySide6 UI&lt;/h2&gt;
&lt;p&gt;Our data model holds the text content and x &amp;amp; y positions of the notes. To keep the active notes and model in sync we need a few things.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each &lt;code&gt;NoteWindow&lt;/code&gt; must have its own associated instance of the &lt;code&gt;Note&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;Note&lt;/code&gt; objects should be created when creating a new &lt;code&gt;NoteWindow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;NoteWindow&lt;/code&gt; should sync its initial state to a &lt;code&gt;Note&lt;/code&gt; if provided.&lt;/li&gt;
&lt;li&gt;Moving &amp;amp; editing a &lt;code&gt;NoteWindow&lt;/code&gt; should update the data in the &lt;code&gt;Note&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Changes to &lt;code&gt;Note&lt;/code&gt; should be synced to the database.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can tackle these one by one.&lt;/p&gt;
&lt;p&gt;First let's setup our &lt;code&gt;NoteWindow&lt;/code&gt; to accept, and store a reference to &lt;code&gt;Note&lt;/code&gt; objects if provided, or create a new one if not.&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 database import Note
from PySide6.QtCore import Qt
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMenu,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)

# Store references to the NoteWindow objects in this, keyed by id.
active_notewindows = {}


class NoteWindow(QWidget):
    def __init__(self, note=None):
        super().__init__()

        # ... add to the bottom of the __init__ method

        if note is None:
            self.note = Note()
        else:
            self.note = note


&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code we've imported the &lt;code&gt;Note&lt;/code&gt; object from our &lt;code&gt;database.py&lt;/code&gt; file. In the &lt;code&gt;__init__&lt;/code&gt; of our &lt;code&gt;NoteWindow&lt;/code&gt; we've added an optional parameter to receive a &lt;code&gt;Note&lt;/code&gt; object. If this is &lt;code&gt;None&lt;/code&gt; (or nothing provided) a new &lt;code&gt;Note&lt;/code&gt; will be created instead. The passed, or created note, is then stored on the &lt;code&gt;NoteWindow&lt;/code&gt; so we can use it later.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;Note&lt;/code&gt; object is still not being loaded, updated, or persisted to the database. So let's implement that next. We add two methods, &lt;code&gt;load()&lt;/code&gt; and &lt;code&gt;save()&lt;/code&gt; to our &lt;code&gt;NoteWindow&lt;/code&gt; to handle the loading and saving 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;from database import Note, session

# ... skipped other imports, unchanged.

class NoteWindow(QWidget):
    def __init__(self, note=None):
        super().__init__()

        # ... modify the close_btn handler to use delete.
        self.close_btn.clicked.connect(self.delete)


        # ... rest of the code hidden.

        # If no note is provided, create one.
        if note is None:
            self.note = Note()
            self.save()
        else:
            self.note = note
            self.load()

    # ... add the following to the end of the class definition.

    def load(self):
        self.move(self.note.x, self.note.y)
        self.text.setText(self.note.text)

    def save(self):
        self.note.x = self.x()
        self.note.y = self.y()
        self.note.text = self.text.toPlainText()
        # Write the data to the database, adding the Note object to the
        # current session and committing the changes.
        session.add(self.note)
        session.commit()

    def delete(self):
        session.delete(self.note)
        session.commit()
        del active_notewindows[id(self)]
        self.close()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;load()&lt;/code&gt; method takes the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; position from the &lt;code&gt;Note&lt;/code&gt; object stored in &lt;code&gt;self.note&lt;/code&gt; and updates the &lt;code&gt;NoteWindow&lt;/code&gt; position and content to match. The &lt;code&gt;save()&lt;/code&gt; method takes the &lt;code&gt;NoteWindow&lt;/code&gt; position and content and sets that onto the &lt;code&gt;Note&lt;/code&gt; object. It then adds the note to the current database session and commits the changes.&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;  Each commit starts a new session. Adding the &lt;code&gt;Note&lt;/code&gt; to the session is indicating that we want its changes persisted.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;delete()&lt;/code&gt; method handles deletion of the current note. This involves 3 things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;passing the &lt;code&gt;Note&lt;/code&gt; object to &lt;code&gt;session.delete&lt;/code&gt; to remove it from the database,&lt;/li&gt;
&lt;li&gt;deleting the reference to our window from the &lt;code&gt;active_notewindows&lt;/code&gt; (so the object will be tidied up)&lt;/li&gt;
&lt;li&gt;calling &lt;code&gt;.close()&lt;/code&gt; to hide the window immediately.&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;  Usually (2) will cause the object to be cleaned up, and that will close the window indirectly. But that may be delayed, which would mean sometimes the close button doesn't seem to work straight away. We call &lt;code&gt;.close()&lt;/code&gt; to make it immediate.&lt;/p&gt;
&lt;p&gt;We need to modify the &lt;code&gt;close_btn.clicked&lt;/code&gt; signal to point to our &lt;code&gt;delete&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Next we've added a &lt;code&gt;load()&lt;/code&gt; call to the &lt;code&gt;__init__&lt;/code&gt; when a &lt;code&gt;Note&lt;/code&gt; object is passed. We also call &lt;code&gt;.save()&lt;/code&gt; for newly created notes to persist them immediately, so our delete handler will work before editing.&lt;/p&gt;
&lt;p&gt;Finally, we need to handle saving the note whenever it changes. We have two ways
that the note can change -- when it's moved, or when it's edited. For the first we &lt;em&gt;could&lt;/em&gt; do this on each mouse move, but it's a bit redundant. We only care where the note ends up while dragging -- that is, where it is when the mouse is released. We can get this through the &lt;code&gt;mouseReleaseEvent&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 database import Note, session

# ... skipped other imports, unchanged.

class NoteWindow(QWidget):

    # ... add the mouseReleaseEvent to the events on the NoteWindow.

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()

    def mouseReleaseEvent(self, e):
        self.save()

    # ... the load and save methods are under here, unchanged.

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That's all there is to it: when the mouse button is released, we save the current content and position by calling &lt;code&gt;.save()&lt;/code&gt;.&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 might be wondering why we don't just save the position at this point? Usually it's better to implement a single load &amp;amp; save (persist/restore) handler that can be called for all situations. It avoids needing implementations for each case.&lt;/p&gt;
&lt;p&gt;There have been a lot of partial code changes in this section, so here is the complete current 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 sys

from database import Note, session
from PySide6.QtCore import Qt
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMenu,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)

# Store references to the NoteWindow objects in this, keyed by id.
active_notewindows = {}


class NoteWindow(QWidget):
    def __init__(self, note=None):
        super().__init__()

        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()

        buttons = QHBoxLayout()
        self.close_btn = QPushButton("&amp;times;")
        self.close_btn.setStyleSheet(
            "font-weight: bold; font-size: 25px; width: 25px; height: 25px;"
        )
        self.close_btn.clicked.connect(self.delete)
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        buttons.addStretch()  # Add stretch on left to push button right.
        buttons.addWidget(self.close_btn)
        layout.addLayout(buttons)

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)

        self.text.textChanged.connect(self.save)

        # Store a reference to this note in the active_notewindows
        active_notewindows[id(self)] = self

        # If no note is provided, create one.
        if note is None:
            self.note = Note()
            self.save()
        else:
            self.note = note
            self.load()

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()

    def mouseReleaseEvent(self, e):
        self.save()

    def load(self):
        self.move(self.note.x, self.note.y)
        self.text.setText(self.note.text)

    def save(self):
        self.note.x = self.x()
        self.note.y = self.y()
        self.note.text = self.text.toPlainText()
        # Write the data to the database, adding the Note object to the
        # current session and committing the changes.
        session.add(self.note)
        session.commit()

    def delete(self):
        session.delete(self.note)
        session.commit()
        del active_notewindows[id(self)]
        self.close()


def create_notewindow():
    note = NoteWindow()
    note.show()


create_notewindow()

# Create the icon
icon = QIcon("sticky-note.png")

# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)


def handle_tray_click(reason):
    # If the tray is left-clicked, create a new note.
    if (
        QSystemTrayIcon.ActivationReason(reason)
        == QSystemTrayIcon.ActivationReason.Trigger
    ):
        create_notewindow()


tray.activated.connect(handle_tray_click)


# Don't automatically close app when the last window is closed.
app.setQuitOnLastWindowClosed(False)

# Create the menu
menu = QMenu()
# Add the Add Note option to the menu.
add_note_action = QAction("Add note")
add_note_action.triggered.connect(create_notewindow)
menu.addAction(add_note_action)

# Add a Quit option to the menu.
quit_action = QAction("Quit")
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)
# Add the menu to the tray
tray.setContextMenu(menu)


app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the application at this point it will be persisting data to the database as you edit it.&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 want to look at the contents of the SQLite database I can recommend &lt;a href="https://sqlitebrowser.org/"&gt;DB Browser for SQLite&lt;/a&gt;. It's open source &amp;amp; free.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The note data persisted to the SQLite database" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-sqlite-browser.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-sqlite-browser.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-sqlite-browser.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-sqlite-browser.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/examples/qt/desktop-notes/notes-sqlite-browser.png?tr=w-600 600w" loading="lazy" width="1668" height="988"/&gt;
&lt;em&gt;The note data persisted to the SQLite database&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="restoring-notes-on-startup"&gt;Restoring Notes on Startup&lt;/h2&gt;
&lt;p&gt;So our notes are being created, added to the database, updated and deleted. The last piece of the puzzle is restoring the previous state at start up.&lt;/p&gt;
&lt;p&gt;We already have all the bits in place for this, we just need to handle the startup itself. To recreate the notes we can query the database to get a list of &lt;code&gt;Note&lt;/code&gt; objects and then iterate through this, creating new &lt;code&gt;NoteWindow&lt;/code&gt; instances (using our &lt;code&gt;create_notewindow&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;def create_notewindow(note=None):
    note = NoteWindow(note)
    note.show()


existing_notes = session.query(Note).all()

if existing_notes:
    for note in existing_notes:
        create_notewindow(note)
else:
    create_notewindow()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;First we've modified the &lt;code&gt;create_notewindow&lt;/code&gt; function to accept an (optional) &lt;code&gt;Note&lt;/code&gt; object which is passed through to the created &lt;code&gt;NoteWindow&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using the session we query &lt;code&gt;session.query(Note).all()&lt;/code&gt; to get all the &lt;code&gt;Note&lt;/code&gt; objects. If there any, we iterate them creating them. If not, we create a single note with no associated &lt;code&gt;Note&lt;/code&gt; object (this will be created inside the &lt;code&gt;NoteWindow&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;That's it! The full final code 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 sys

from database import Note, session
from PySide6.QtCore import Qt
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QMenu,
    QPushButton,
    QSystemTrayIcon,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

app = QApplication(sys.argv)

# Store references to the NoteWindow objects in this, keyed by id.
active_notewindows = {}


class NoteWindow(QWidget):
    def __init__(self, note=None):
        super().__init__()

        self.setWindowFlags(
            self.windowFlags()
            | Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
        )
        self.setStyleSheet(
            "background: #FFFF99; color: #62622f; border: 0; font-size: 16pt;"
        )
        layout = QVBoxLayout()

        buttons = QHBoxLayout()
        self.close_btn = QPushButton("&amp;times;")
        self.close_btn.setStyleSheet(
            "font-weight: bold; font-size: 25px; width: 25px; height: 25px;"
        )
        self.close_btn.clicked.connect(self.delete)
        self.close_btn.setCursor(Qt.CursorShape.PointingHandCursor)
        buttons.addStretch()  # Add stretch on left to push button right.
        buttons.addWidget(self.close_btn)
        layout.addLayout(buttons)

        self.text = QTextEdit()
        layout.addWidget(self.text)
        self.setLayout(layout)

        self.text.textChanged.connect(self.save)

        # Store a reference to this note in the active_notewindows
        active_notewindows[id(self)] = self

        # If no note is provided, create one.
        if note is None:
            self.note = Note()
            self.save()
        else:
            self.note = note
            self.load()

    def mousePressEvent(self, e):
        self.previous_pos = e.globalPosition()

    def mouseMoveEvent(self, e):
        delta = e.globalPosition() - self.previous_pos
        self.move(self.x() + delta.x(), self.y() + delta.y())
        self.previous_pos = e.globalPosition()

    def mouseReleaseEvent(self, e):
        self.save()

    def load(self):
        self.move(self.note.x, self.note.y)
        self.text.setText(self.note.text)

    def save(self):
        self.note.x = self.x()
        self.note.y = self.y()
        self.note.text = self.text.toPlainText()
        # Write the data to the database, adding the Note object to the
        # current session and committing the changes.
        session.add(self.note)
        session.commit()

    def delete(self):
        session.delete(self.note)
        session.commit()
        del active_notewindows[id(self)]
        self.close()


def create_notewindow(note=None):
    note = NoteWindow(note)
    note.show()


existing_notes = session.query(Note).all()

if existing_notes:
    for note in existing_notes:
        create_notewindow(note)
else:
    create_notewindow()

# Create the icon
icon = QIcon("sticky-note.png")

# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)


def handle_tray_click(reason):
    # If the tray is left-clicked, create a new note.
    if (
        QSystemTrayIcon.ActivationReason(reason)
        == QSystemTrayIcon.ActivationReason.Trigger
    ):
        create_notewindow()


tray.activated.connect(handle_tray_click)


# Don't automatically close app when the last window is closed.
app.setQuitOnLastWindowClosed(False)

# Create the menu
menu = QMenu()
# Add the Add Note option to the menu.
add_note_action = QAction("Add note")
add_note_action.triggered.connect(create_notewindow)
menu.addAction(add_note_action)

# Add a Quit option to the menu.
quit_action = QAction("Quit")
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)
# Add the menu to the tray
tray.setContextMenu(menu)


app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the app now, you can create new notes as before, but when you exit (using the Quit option from the tray) and restart, the previous notes will reappear. If you close the notes, they will be deleted. On startup, if there are no notes in the database an initial note will be created for you.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;That's it! We have a fully functional desktop sticky notes application built with Python, PySide6 and SQLAlchemy. You can use it to keep simple bits of text until you need them again. We've learnt how to build an application up step by step from a basic outline window. We've added basic styles using QSS and used Qt window flags to create frameless, always-on-top windows. We've also seen how to create a system tray application with &lt;code&gt;QSystemTrayIcon&lt;/code&gt;, adding context menus and default behaviors (via a left mouse click). Finally, we've created a simple data model using SQLAlchemy and SQLite, and hooked that into our UI to persist the note state between sessions.&lt;/p&gt;
&lt;p&gt;Try and extend this example further, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add multicolor notes, using a Python &lt;code&gt;list&lt;/code&gt; of hex colors and &lt;code&gt;random.choice&lt;/code&gt; to select a new color each time a note is created. Can you persist this in the database too?&lt;/li&gt;
&lt;li&gt;Add an option in the tray to show/hide all notes. Remember we have all the &lt;code&gt;NoteWindow&lt;/code&gt; objects in &lt;code&gt;active_notewindows&lt;/code&gt;. You can show and hide windows in Qt using &lt;code&gt;.show()&lt;/code&gt; and &lt;code&gt;.hide()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think about some additional features you'd like or expect to see in a desktop notes application and see if you can add them yourself!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="sticky-notes"/><category term="qt6"/><category term="app"/><category term="notes"/><category term="notetaking"/><category term="pyside"/><category term="sql"/><category term="database"/><category term="system-tray"/><category term="python"/><category term="qt"/><category term="databases"/></entry><entry><title>Getting Started With PyCharm for Python GUI Development — The Python-Specific Integrated Development Environment</title><link href="https://www.pythonguis.com/tutorials/getting-started-pycharm/" rel="alternate"/><published>2025-01-04T06:00:00+00:00</published><updated>2025-01-04T06:00:00+00:00</updated><author><name>Lalin Paranawithana</name></author><id>tag:www.pythonguis.com,2025-01-04:/tutorials/getting-started-pycharm/</id><summary type="html">Setting up a good development environment for Python GUI programming can make the coding process friendly and provide the necessary tools to maintain the codebase for many years to come. Choosing the right tools and being comfortable with them is a critical step for any Python developer.</summary><content type="html">
            &lt;p&gt;Setting up a good development environment for Python GUI programming can make the coding process friendly and provide the necessary tools to maintain the codebase for many years to come. Choosing the right tools and being comfortable with them is a critical step for any Python developer.&lt;/p&gt;
&lt;p&gt;Code editors are among the most common and used development tools. They're where we write the code. While most modern code editors support features like syntax highlighting, which shows visual cues to differentiate parts of the source code, we often need to rely on external tools to help us debug and look for errors, install packages, manage Python environments, and so on.&lt;/p&gt;
&lt;p&gt;To have all these features in a single program, you'll need an integrated development environment (IDE). Although there are several Python IDEs, in this article, we'll be exploring PyCharm, which is a powerful IDE for Python developers. We'll walk through setting up PyCharm and building a simple PyQt6 GUI application from scratch.&lt;/p&gt;
&lt;h2 id="install-python"&gt;Install Python&lt;/h2&gt;
&lt;p&gt;Before installing PyCharm, we need to make sure that Python is installed on the development machine. If it isn't installed yet, then we can go to the official &lt;a href="https://www.python.org/downloads/"&gt;download&lt;/a&gt; page and grab the specific installer for either Windows or macOS.&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;  Make sure to select the &lt;em&gt;Add Python to PATH&lt;/em&gt; option during the installation process.&lt;/p&gt;
&lt;p&gt;On Linux systems, you can check if Python is already installed by running &lt;code&gt;python3 --version&lt;/code&gt; in a terminal. If this command returns an error, then you need to install Python from your distribution's repository or from the &lt;a href="https://www.python.org/downloads/source/"&gt;source&lt;/a&gt;.&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;  If you're running a Debian-based distribution, such as Ubuntu and Linux Mint, you can install Python by running &lt;code&gt;sudo apt install python3&lt;/code&gt;. It may be necessary to run &lt;code&gt;sudo apt install python3-pip python3-venv&lt;/code&gt;, so that PyCharm can use &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;venv&lt;/code&gt; to install packages and manage virtual environments.&lt;/p&gt;
&lt;h2 id="install-pycharm"&gt;Install PyCharm&lt;/h2&gt;
&lt;p&gt;We have a few different ways to install PyCharm. The recommended way is to head over to &lt;a href="https://www.jetbrains.com/pycharm/"&gt;the official download page&lt;/a&gt; and grab the standalone installer for Windows, macOS, or Linux. Then, you'll need to run the installer and follow the on-screen instructions.&lt;/p&gt;
&lt;p&gt;If you're using a &lt;a href="https://snapcraft.io/docs/installing-snapd"&gt;Linux distro that supports Snaps&lt;/a&gt;, you can also install it by running &lt;code&gt;sudo snap install pycharm-community --classic&lt;/code&gt; in your terminal.&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;  PyCharm has a Community edition, which is free and open-source. It also has a Professional edition, which is paid. While this tutorial uses PyCharm Community edition, the covered topics apply to both editions. Check out the &lt;a href="https://www.jetbrains.com/products/compare/?product=pycharm&amp;amp;product=pycharm-ce"&gt;comparison&lt;/a&gt; between both editions for the details.&lt;/p&gt;
&lt;p&gt;Alternatively, you could install PyCharm via the &lt;a href="https://www.jetbrains.com/toolbox-app/"&gt;Toolbox&lt;/a&gt; app, which makes it easier to update or rollback to any version of PyCharm. This app can also be used to install and manage other JetBrains products.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; For more platform-specific information, check out PyCharm's &lt;a href="https://www.jetbrains.com/help/pycharm/installation-guide.html"&gt;installation guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="create-a-new-pyqt6-project-in-pycharm"&gt;Create a New PyQt6 Project in PyCharm&lt;/h2&gt;
&lt;p&gt;To demonstrate PyCharm's workflow, we'll create a basic Python GUI app using &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;PyQt&lt;/a&gt;. Before we start writing code, we need to first launch PyCharm and create a new project. Creating a project helps you differentiate between files, code, packages, and settings.&lt;/p&gt;
&lt;p&gt;Go ahead and launch PyCharm. Depending on your operating system, you can run PyCharm by going to the &lt;em&gt;Start Menu&lt;/em&gt; on Windows, &lt;em&gt;Launchpad&lt;/em&gt; on macOS, or by running the &lt;code&gt;pycharm&lt;/code&gt; executable (under &lt;code&gt;install-folder/bin&lt;/code&gt;) on Linux. However, if you installed PyCharm using Toolbox, then open the Toolbox app and launch PyCharm from there.&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;  Read the PyCharm documentation to learn how to &lt;a href="https://www.jetbrains.com/help/pycharm/working-with-the-ide-features-from-command-line.html"&gt;run PyCharm from the command-line interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When we launch PyCharm, the first thing we'll see is its &lt;em&gt;Welcome to PyCharm&lt;/em&gt; window:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Welcome window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-elcome-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-elcome-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-elcome-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-elcome-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-elcome-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Welcome window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here's where we can create new projects or open existing ones&amp;mdash;either stored locally on our computer or in online code repositories like GitHub.&lt;/p&gt;
&lt;p&gt;To continue, click the &lt;em&gt;New Project&lt;/em&gt; button and then choose a project's name and location folder in the &lt;em&gt;New Project&lt;/em&gt; window. You can also define the Python environment to use but keep the default configuration for now:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's New Project window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-new-project-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-new-project-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-new-project-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-new-project-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-new-project-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's New Project window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Finally, click the &lt;em&gt;Create&lt;/em&gt; button on the lower, right corner to create the project and open it in PyCharm's main window, also known as &lt;em&gt;Project Window&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="get-familiar-with-pycharms-interface"&gt;Get Familiar With PyCharm's Interface&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;Project Window&lt;/em&gt; is where we'll spend most of our time in PyCharm. It's where we manage files, write code, install packages, and do everything related to the project we are working on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article covers PyCharm's &lt;a href="https://www.jetbrains.com/help/pycharm/new-ui.html"&gt;modern user interface&lt;/a&gt;. You can also use the &lt;a href="https://www.jetbrains.com/help/pycharm/guided-tour-around-the-user-interface.html#-85wxb7_31"&gt;classic&lt;/a&gt; user interface by going to &lt;em&gt;File -&amp;gt; Settings&lt;/em&gt; and disabling &lt;em&gt;New UI&lt;/em&gt; under &lt;em&gt;Appearance &amp;amp; behavior&lt;/em&gt;. On macOS, you can access the &lt;em&gt;Settings&lt;/em&gt; menu from the &lt;em&gt;Menu Bar&lt;/em&gt; at the top of your screen.&lt;/p&gt;
&lt;p&gt;Let's examine the components of PyCharm's interface and see what they do:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Project Window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-project-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-project-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-project-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-project-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-project-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Project Window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Project Window&lt;/em&gt; interface has the following four main components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Window Header&lt;/li&gt;
&lt;li&gt;Editor&lt;/li&gt;
&lt;li&gt;Sidebar&lt;/li&gt;
&lt;li&gt;Status bar&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the following sections, we'll explore these UI components in more detail.&lt;/p&gt;
&lt;h3&gt;Window Header&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;Window Header&lt;/em&gt; is at the very top of PyCharm's &lt;em&gt;Project Window&lt;/em&gt; and gives us quick access to some commonly used features, most notably the &lt;em&gt;Run&lt;/em&gt; button. Using the &lt;em&gt;Run&lt;/em&gt; button, we can run or debug our Python project directly from PyCharm &amp;mdash; without having to type the relevant Python command in a terminal.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Window Header&lt;/em&gt; looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Window Header" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-window-header.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-window-header.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-window-header.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-window-header.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-window-header.png?tr=w-600 600w" loading="lazy" width="1366" height="50"/&gt;
&lt;em&gt;PyCharm's Window Header&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Other significant components of the &lt;em&gt;Window Header&lt;/em&gt; are the &lt;em&gt;Main Menu&lt;/em&gt; and the &lt;em&gt;Project widget&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;PyCharm's &lt;em&gt;Main Menu&lt;/em&gt; is accessible by clicking on the &lt;em&gt;hamburger menu&lt;/em&gt;, whereas the &lt;em&gt;Project widget&lt;/em&gt; displays the name of the current project and allows us to switch between projects as well as create new ones.&lt;/p&gt;
&lt;p&gt;On macOS, however, we don't have a &lt;em&gt;hamburger menu&lt;/em&gt; in the &lt;em&gt;Window Header&lt;/em&gt; but we can still access PyCharm's &lt;em&gt;Main Menu&lt;/em&gt; through the &lt;em&gt;Menu Bar&lt;/em&gt; at the top of the screen, like with other macOS apps.&lt;/p&gt;
&lt;h3&gt;Editor&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;Editor&lt;/em&gt; is where we actually read, write, and edit code. It has a scrollbar on the right to help us navigate large files. It also has a &lt;em&gt;Gutter&lt;/em&gt; on the left, which displays line numbers for the file. The &lt;em&gt;Gutter&lt;/em&gt; also provides us with context-dependent actions like running code or temporarily hiding functions and classes:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Editor window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-editor-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-editor-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-editor-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-editor-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-editor-window.png?tr=w-600 600w" loading="lazy" width="822" height="635"/&gt;
&lt;em&gt;PyCharm's Editor window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At the top, the &lt;em&gt;Editor&lt;/em&gt; has a tabbed interface to display the open files and switch between them at any time. To switch to another open file, we can either click the relevant tab or press &lt;code&gt;CTRL + TAB&lt;/code&gt; to scroll through all of them. If necessary, we can also open files from external folders that are outside the project's folder by &lt;em&gt;attaching&lt;/em&gt; that folder to the current project.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Editor&lt;/em&gt; can open any plaintext file format, such as &lt;code&gt;.py&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt;, for editing. It also supports viewing both raster and vector image formats, such as PNG and SVG. However, note that most binary files, such as video, audio, and executable files are not supported by the &lt;em&gt;Editor&lt;/em&gt; and will be opened outside of PyCharm.&lt;/p&gt;
&lt;h3&gt;Sidebar and Tool Windows&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;Sidebar&lt;/em&gt; has buttons to open and close &lt;strong&gt;tool windows&lt;/strong&gt;, which are displayed at the bottom or left side of PyCharm's &lt;em&gt;Project Window&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's sidebar and the Project tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-sidebar-project-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sidebar-project-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sidebar-project-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sidebar-project-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sidebar-project-tool-window.png?tr=w-600 600w" loading="lazy" width="493" height="639"/&gt;
&lt;em&gt;PyCharm's sidebar and the Project tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Project&lt;/em&gt; tool window is normally open by default. It displays the folder structure of the current project and allows us to open, create, and delete files and folders. If you have any external folders attached to the current project, the Project tool window will also display the structure of those folders.&lt;/p&gt;
&lt;p&gt;PyCharm also has tool windows for running code, installing packages from PyPI using a graphical interface, and using version control software. Throughout the rest of this article, we'll learn how to use these tool windows as we build our sample Python GUI app.&lt;/p&gt;
&lt;h3&gt;Status Bar&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;Status Bar&lt;/em&gt; is the horizontal bar at the bottom of PyCharm's workspace. It indicates the status of both our project and the entire IDE:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's status bar" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-status-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-status-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-status-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-status-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-status-bar.png?tr=w-600 600w" loading="lazy" width="1366" height="34"/&gt;
&lt;em&gt;PyCharm's status bar&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It provides information about the file that's currently opened in the &lt;em&gt;Editor&lt;/em&gt;, such as its encoding, line separator, cursor position, and indentation type. It also gives quick access to the &lt;em&gt;Python Interpreter&lt;/em&gt; settings, which we'll cover in more detail later.&lt;/p&gt;
&lt;h2 id="install-pyqt6-in-pycharm"&gt;Install PyQt6 in PyCharm&lt;/h2&gt;
&lt;p&gt;One thing we need to do before starting our GUI app is to install the &lt;a href="https://www.pythonguis.com/pyqt6/"&gt;PyQt&lt;/a&gt; library. We'll use this library to build the app's GUI. This library doesn't come with Python but can be installed from the &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; software repository using the &lt;code&gt;pip&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;However, PyCharm simplifies the installation by providing a tool window for us to search, install, and manage Python packages. To open the &lt;em&gt;Python Packages&lt;/em&gt; tool window, go to the &lt;em&gt;Sidebar&lt;/em&gt; and look for the &lt;em&gt;Python Packages&lt;/em&gt; button, which is shown in blue in the following screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Python Packages tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Python Packages tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Using the search field, look for the &lt;code&gt;PyQt6&lt;/code&gt; package. We can have a look at the &lt;code&gt;README&lt;/code&gt; file (on the right side) for each package by clicking on the package's name (on the left side). Once we've found the package, we can click &lt;em&gt;Install&lt;/em&gt; next to the package's name and choose the package version we want to install.&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;  A &lt;code&gt;README&lt;/code&gt; file typically provides information about what a package does, how to install and use the package, what license the package is distributed under, and so on.&lt;/p&gt;
&lt;p&gt;When we clear the search field, &lt;code&gt;PyQt6&lt;/code&gt; should now be listed as &lt;em&gt;Installed&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Python Packages tool window with PyQt6 ready to be installed" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-ready.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-ready.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-ready.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-ready.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-ready.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Python Packages tool window with PyQt6 ready to be installed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If a newer version of an installed package is available, then PyCharm will indicate it by displaying a link with the current and next version (next to the package name) that we can click to upgrade:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Python Packages tool window with PyQt6 installed" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-installed.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-installed.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-installed.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-installed.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-windowpyqt6-installed.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Python Packages tool window with PyQt6 installed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can also uninstall a Python package. To do this, click the package's name and go to the &lt;em&gt;kebab menu&lt;/em&gt; on the top-right corner of the &lt;code&gt;README&lt;/code&gt; preview section. Then, click &lt;em&gt;Delete Package&lt;/em&gt; to uninstall the package:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Python Packages tool window showing how to uninstall a package" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window-uninstall-package.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window-uninstall-package.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window-uninstall-package.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window-uninstall-package.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-python-packages-tool-window-uninstall-package.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Python Packages tool window showing how to uninstall a package&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Uninstalling Python packages from a virtual environment isn't something that we do often but PyCharm has a nice interface to do it if we need to.&lt;/p&gt;
&lt;h2 id="write-a-pyqt6-gui-application-in-pycharm"&gt;Write a PyQt6 GUI Application in PyCharm&lt;/h2&gt;
&lt;p&gt;In this section, we will focus on writing and editing code in PyCharm to build our sample GUI app. For its GUI, the app will have one window with a &lt;em&gt;Press Me&lt;/em&gt; button. To start coding though, we need a Python file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This guide will not cover PyQt extensively. To learn more about this library, check out the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;Creating your first app with PyQt6&lt;/a&gt; tutorial.&lt;/p&gt;
&lt;p&gt;To create a file, go to the &lt;em&gt;Projects&lt;/em&gt; tool window and right-click the project's folder to open its context menu. Then, go to &lt;em&gt;New&lt;/em&gt; and select &lt;em&gt;Python File&lt;/em&gt; as the desired file type:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's context menu to create a new Python file" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-create-new-python-file.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-new-python-file.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-new-python-file.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-new-python-file.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-new-python-file.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's context menu to create a new Python file&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This action will open a dialog where PyCharm will ask you to enter a filename. For this project, we will just name the file as &lt;code&gt;app&lt;/code&gt; and press &lt;em&gt;ENTER&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's dialog to name a new Python file" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-dialog-name-new-python-file.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-dialog-name-new-python-file.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-dialog-name-new-python-file.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-dialog-name-new-python-file.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-dialog-name-new-python-file.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's dialog to name a new Python file&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Project&lt;/em&gt; tool window will now list a file named &lt;code&gt;app.py&lt;/code&gt; under the project's folder. To open that file in the &lt;em&gt;Editor&lt;/em&gt;, double-click on the filename and you'll see a blank Python file:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The app.py file opened in PyCharm's Editor" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/file-opened-pycharm-ditor.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/file-opened-pycharm-ditor.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/file-opened-pycharm-ditor.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/file-opened-pycharm-ditor.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/file-opened-pycharm-ditor.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;The &lt;code&gt;app.py&lt;/code&gt; file opened in PyCharm's Editor&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Editor&lt;/em&gt; works like any other standard text editor or code editor. We enter text using the keyboard and press &lt;code&gt;ENTER&lt;/code&gt; to create a new line. We can use the &lt;em&gt;Gutter&lt;/em&gt; to track the line number we are currently on.&lt;/p&gt;
&lt;p&gt;Now that we have a Python file in our project folder, we can start coding. First, we will import the required Python modules, packages, and classes:&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 QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As we type the &lt;code&gt;import&lt;/code&gt; statements in the &lt;em&gt;Editor&lt;/em&gt;, PyCharm will offer suggestions for matching keywords and Python packages using a pop-up:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's code suggestions pop-up" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-code-suggestions-pop-up.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-suggestions-pop-up.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-suggestions-pop-up.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-suggestions-pop-up.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-suggestions-pop-up.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's code suggestions pop-up&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can use the Up and Down arrow keys to navigate the list of suggestions. Then, press &lt;code&gt;TAB&lt;/code&gt; to select the highlighted suggestion.&lt;/p&gt;
&lt;h3&gt;Using PyCharm's Error Detection and Code Fixes&lt;/h3&gt;
&lt;p&gt;Another useful feature of PyCharm is its ability to detect errors and provide fixes for problematic code. The following code block deliberately includes a few errors to demonstrate this feature:&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;QApplication
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code, we haven't instantiated &lt;code&gt;QApplication&lt;/code&gt; correctly. This error produces a cascade of errors that PyCharm nicely spots for us:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's error detection feature" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-error-detection-feature.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-detection-feature.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-detection-feature.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-detection-feature.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-detection-feature.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's error detection feature&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;PyCharm detects a few errors in the code we just typed. It indicates the error by highlighting the relevant piece of code with a squiggly line.&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 color of the squiggly line indicates the severity of the problem PyCharm detected. Any problem highlighted in red is a critical error that prevents the program from running and everything else is marked in yellow.&lt;/p&gt;
&lt;p&gt;To discover what an error is about, we can drag the mouse pointer to the highlighted section and hover over it to see a tooltip as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's error tips: statement with no effect" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-statement-with-no-effect.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-statement-with-no-effect.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-statement-with-no-effect.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-statement-with-no-effect.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-statement-with-no-effect.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's error tips: statement with no effect&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;PyCharm's tooltip says that the statement &lt;code&gt;QApplication&lt;/code&gt; has no effect. This is because we are just referencing a class without creating a concrete object. As a result, PyCharm flags a critical error in the last line because the &lt;code&gt;app&lt;/code&gt; name hasn't been assigned yet.&lt;/p&gt;
&lt;p&gt;We can fix both errors by instantiating &lt;code&gt;QApplication&lt;/code&gt; to create an object named &lt;code&gt;app&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)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've taken care of two problems detected by PyCharm, but there is still one critical error remaining. If we hover the mouse's pointer over the highlighted object, we'll see that PyCharm is complaining about &lt;code&gt;window = MainWindow()&lt;/code&gt; because we are using a class that we haven't defined yet:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's error tips: unresolved reference" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-unresolved-reference.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-unresolved-reference.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-unresolved-reference.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-unresolved-reference.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-error-tips-unresolved-reference.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's error tips: unresolved reference&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To fix the issue, right-click the highlighted object to open the associated &lt;em&gt;Context Menu&lt;/em&gt;. Once you see the menu, select &lt;em&gt;Show Context Actions&lt;/em&gt;. This will open another pop-up where PyCharm offers smart suggestions to fix the problem.&lt;/p&gt;
&lt;p&gt;For this particular problem, we'll choose the option &lt;em&gt;Create class 'MainWindow' in module app.py&lt;/em&gt; as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Context Actions pop-up" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-context-actions-pop-up.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-context-actions-pop-up.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-context-actions-pop-up.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-context-actions-pop-up.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-context-actions-pop-up.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Context Actions pop-up&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;PyCharm automatically generates an empty class named &lt;code&gt;MainWindow&lt;/code&gt;. Update the class as follows in order to specify how the app's GUI will look:&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 MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")
        button = QPushButton("Press Me!")
        self.setFixedSize(QSize(400, 300))
        self.setCentralWidget(button)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Complete PyQt6 Application Code&lt;/h3&gt;
&lt;p&gt;With this update done, the complete code for &lt;code&gt;app.py&lt;/code&gt; should look something 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;import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")
        button = QPushButton("Press Me!")
        self.setFixedSize(QSize(400, 300))
        self.setCentralWidget(button)

app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you have a working PyQt6 desktop application. The next step is to run the app using PyCharm's features but before that, let's summarize what we've learned so far.&lt;/p&gt;
&lt;p&gt;We learned how to create a file using the &lt;em&gt;Project&lt;/em&gt; tool window and then edit it using the Editor. We also learned how to use some of the assistive coding features provided by PyCharm to get code suggestions, fix problems in our code, and generate boilerplate code for a faster coding experience.&lt;/p&gt;
&lt;h2 id="run-python-gui-code-in-pycharm"&gt;Run Python GUI Code in PyCharm&lt;/h2&gt;
&lt;p&gt;PyCharm provides a convenient way to run code through its interface. To run our PyQt6 app, right-click on the &lt;em&gt;Editor's&lt;/em&gt; work area to open the &lt;em&gt;Context Menu&lt;/em&gt;. Then, select the &lt;em&gt;Run 'app'&lt;/em&gt; option:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Running code with PyCharm's Run option from the Editor's context menu" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-option-editor-context-menu.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-option-editor-context-menu.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-option-editor-context-menu.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-option-editor-context-menu.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-option-editor-context-menu.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Running code with PyCharm's Run option from the Editor's context menu&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The option to run code using the Run option in the context menu can be appropriate for short apps or scripts. However, when you have a much larger project with more than one file, it's often convenient to configure a default Python file for a project to run.&lt;/p&gt;
&lt;p&gt;To do so, right-click on the &lt;code&gt;app.py&lt;/code&gt; file in the &lt;em&gt;Editor&lt;/em&gt; to show the &lt;em&gt;Context Menu&lt;/em&gt;. Then, select &lt;em&gt;Modify Run Configuration...&lt;/em&gt; as in the following screenshot:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Modifying the Run Configuration in PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/modifying-run-configuration-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/modifying-run-configuration-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/modifying-run-configuration-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/modifying-run-configuration-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/modifying-run-configuration-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Modifying the Run Configuration in PyCharm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you select the &lt;em&gt;Modify Run Configuration...&lt;/em&gt;, you get presented with the &lt;em&gt;Edit Run Configuration&lt;/em&gt; dialog:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Edit Run Configuration dialog" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-edit-run-configuration-dialog.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-edit-run-configuration-dialog.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-edit-run-configuration-dialog.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-edit-run-configuration-dialog.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-edit-run-configuration-dialog.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Edit Run Configuration dialog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;Edit Run Configuration&lt;/em&gt; dialog, we can modify the path to the Python script or set additional command-line arguments to pass when running the project. We can also set a name for this configuration at the top of the dialog and click the &lt;em&gt;OK&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;Now, we'll see the &lt;code&gt;app&lt;/code&gt; configuration next to the green &lt;em&gt;Run&lt;/em&gt; button on the &lt;em&gt;Window Header&lt;/em&gt;. This indicates which script PyCharm will run by default. If we have more than one &lt;em&gt;Run Configuration&lt;/em&gt;, then we can switch between them by clicking on the dropdown menu next to the &lt;em&gt;Run&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;Once we have set the project's default &lt;em&gt;Run Configuration&lt;/em&gt;, we can run the associated script regardless of which file we are currently working on the &lt;em&gt;Editor&lt;/em&gt; by clicking on the &lt;em&gt;Run&lt;/em&gt; button in the &lt;em&gt;Window Header&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Running code with PyCharm's Run button from the Window Header" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-button-window-header.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-button-window-header.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-button-window-header.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-button-window-header.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/running-code-pycharm-run-button-window-header.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Running code with PyCharm's Run button from the Window Header&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To terminate the app's execution from the PyCharm interface, click the &lt;em&gt;Stop&lt;/em&gt; button in the &lt;em&gt;Window Header&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="configure-a-python-virtual-environment-in-pycharm"&gt;Configure a Python Virtual Environment in PyCharm&lt;/h2&gt;
&lt;p&gt;Python's best practices recommend avoiding the system interpreter in favor of using a virtual environment. A Python virtual environment is an isolated copy of the system interpreter that we create for development purposes.&lt;/p&gt;
&lt;p&gt;For most large projects, you should create a dedicated virtual environment. As we noted earlier, PyCharm creates a virtual environment by default whenever we create a new project. This allows us to manage the project's dependencies separately because they are installed in the dedicated Python environments rather than globally in your system.&lt;/p&gt;
&lt;p&gt;We can see the installed packages in our current Python environment by clicking on the Python interpreter selector (in the &lt;em&gt;Status Bar&lt;/em&gt;) and clicking on the &lt;em&gt;Manage Packages...&lt;/em&gt; option. This action opens the &lt;em&gt;Python Packages&lt;/em&gt; tool window, which we previously used to install PyQt6:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Managing Python packages in PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/managing-python-packages-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/managing-python-packages-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/managing-python-packages-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/managing-python-packages-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/managing-python-packages-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Managing Python packages in PyCharm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We can create a new Python virtual environment for our project by going to &lt;em&gt;Add New Interpreter &amp;gt; Add Local Interpreter&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Creating a new Python virtual environment in PyCharm - Step 1" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step1.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step1.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step1.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step1.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step1.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Creating a new Python virtual environment in PyCharm - Step 1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After clicking on the &lt;em&gt;Add Local Interpreter&lt;/em&gt; option, you get presented with the &lt;em&gt;Add Python Interpreter&lt;/em&gt; dialog. There, you'll find several options to create working environments for Python coding. For the purposes of this guide, we'll only look at creating a standard &lt;em&gt;Virtualenv Environment&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Creating a new Python virtual environment in PyCharm - Step 2" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step2.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step2.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step2.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step2.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-python-virtual-environment-pycharm-step2.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Creating a new Python virtual environment in PyCharm - Step 2&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Specify where to create the Python virtual environment and select a base interpreter from the dropdown list if you have more than one version of Python. Then, click the &lt;em&gt;OK&lt;/em&gt; button to create the new Python environment for the active project. We can see the current Python environment by looking at the Python interpreter selector in the right bottom corner of the &lt;em&gt;Status Bar&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Because this is a brand new virtual environment, trying to run the app now will fail because we haven't installed PyQt6 in this Python environment.&lt;/p&gt;
&lt;p&gt;To switch back to the previous Python environment, click on the interpreter selector and select the relevant interpreter from the list:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Changing the Python virtual environment in PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/changing-python-virtual-environment-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/changing-python-virtual-environment-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/changing-python-virtual-environment-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/changing-python-virtual-environment-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/changing-python-virtual-environment-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Changing the Python virtual environment in PyCharm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To get back to the original Python environment, select the &lt;em&gt;Python 3.10 (PyQt6app)&lt;/em&gt; menu option in the dropdown list.&lt;/p&gt;
&lt;h2 id="use-git-for-version-control-in-pycharm"&gt;Use Git for Version Control in PyCharm&lt;/h2&gt;
&lt;p&gt;Version control systems (VCS) allow us to track changes to a project's codebase. They are useful because they let us know what has changed in the app and who is responsible, which is particularly important when working with teams. VCSs also make it easy to revert any changes we make, giving us more freedom to test out new features.&lt;/p&gt;
&lt;p&gt;PyCharm has built-in support for several VCSs. In this tutorial, we'll focus on Git, which is one of the most popular. In this section, we'll go over how to create and manage a Git repository for a Python GUI project in PyCharm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: To use Git-related features in PyCharm, Git needs to be installed. If you haven't done so already, please follow this guide to &lt;a href="https://www.pythonguis.com/tutorials/git-github-python/"&gt;install and set up Git&lt;/a&gt; on your computer.&lt;/p&gt;
&lt;h3&gt;Track Changes in a Project&lt;/h3&gt;
&lt;p&gt;You can use Git via its command-line interface. However, PyCharm allows us to perform common version control tasks through a graphical interface.&lt;/p&gt;
&lt;p&gt;To enable version control for the current project, go to the &lt;em&gt;Version Control&lt;/em&gt; tool window at the bottom of the &lt;em&gt;Sidebar&lt;/em&gt; and click on &lt;em&gt;Create Git Repository&lt;/em&gt; link:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Version Control tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Version Control tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you click the &lt;em&gt;Create Git Repository&lt;/em&gt; link, you get the &lt;em&gt;Create Git Repository&lt;/em&gt; dialog:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Create Git Repository dialog" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-create-git-repository-dialog.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-git-repository-dialog.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-git-repository-dialog.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-git-repository-dialog.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-create-git-repository-dialog.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Create Git Repository dialog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you create the Git repository, the tool window will update its view to provide additional information. It should be mostly blank right now because we haven't started tracking any files.&lt;/p&gt;
&lt;p&gt;To start tracking project files, we need to commit to the Git repository. To do that, a tool window named &lt;em&gt;Commit&lt;/em&gt; will appear in the &lt;em&gt;Sidebar&lt;/em&gt; (near the top).&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;  A commit is a snapshot of the current state of all the files in a project. Each time we make a significant change to our codebase, we make a commit to mark what changed and by whom.&lt;/p&gt;
&lt;p&gt;Open the tool window and look at the section named &lt;em&gt;Changes&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Commit tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-commit-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-commit-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-commit-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-commit-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-commit-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Commit tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Changes&lt;/em&gt; section under &lt;em&gt;Commit&lt;/em&gt; shows any project files that have changed or aren't being tracked. Because we aren't tracking any files for now, all of our project files appear under &lt;em&gt;Unversioned Files&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;We can select which files we would like to include in a commit by marking the &lt;em&gt;checkbox&lt;/em&gt; next to each filename. For this project, select all of the &lt;em&gt;Unversioned Files&lt;/em&gt; in the first commit.&lt;/p&gt;
&lt;p&gt;There's a text box right below the &lt;em&gt;Changes&lt;/em&gt; section where you can type a commit message to describe what changed in these files. Because this will be our first commit for the current project, we can write a message like "Initial commit":&lt;/p&gt;
&lt;p&gt;&lt;img alt="Select the files to commit and add a commit message in PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/select-files-commit-add-commit-message-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/select-files-commit-add-commit-message-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/select-files-commit-add-commit-message-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/select-files-commit-add-commit-message-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/select-files-commit-add-commit-message-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Select the files to commit and add a commit message in PyCharm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once we've selected the files to include in the commit and typed the commit message, we can make the commit by pressing the &lt;em&gt;Commit&lt;/em&gt; button underneath. The commit we made should now appear in the &lt;em&gt;Version Control&lt;/em&gt; tool window:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Version Control tool window showing a commit" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Version Control tool window showing a commit&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We'll make one more commit to this project by changing the text of the button in &lt;code&gt;app.py&lt;/code&gt; from &lt;code&gt;"Press Me!"&lt;/code&gt; to &lt;code&gt;"Press This Button!"&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;class MainWindow(QMainWindow):
    def __init__(self):
        ...
        button = QPushButton("Press This Button!")
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When a project is version-controlled, PyCharm will indicate when a line has been added, modified, or deleted from a file by showing a colored indicator next to the line number in the gutter. The indicator disappears once we commit the changed file to the repository again:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm showing a colored indicator for modified lines" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-showing-colored-indicator-modified-lines.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-colored-indicator-modified-lines.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-colored-indicator-modified-lines.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-colored-indicator-modified-lines.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-colored-indicator-modified-lines.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm showing a colored indicator for modified lines&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Go to the &lt;em&gt;Commit&lt;/em&gt; tool window, select &lt;code&gt;app.py&lt;/code&gt; under &lt;em&gt;Changes&lt;/em&gt;, type a commit message like "Changed button text," and then press the Commit button again. You'll see the new commit appear above the previous one in the &lt;em&gt;Version Control&lt;/em&gt; tool window:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Version Control tool window showing the commit history" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit-history.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit-history.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit-history.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit-history.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-commit-history.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Version Control tool window showing the commit history&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is called the &lt;strong&gt;commit history&lt;/strong&gt;, where we can see all the changes or commits that we've made in a project.&lt;/p&gt;
&lt;h3&gt;Push a PyCharm Project to GitHub&lt;/h3&gt;
&lt;p&gt;We've created a local Git repository and want to publish the project to a remote repository on GitHub. Before doing that, we need to authenticate so that PyCharm can access, create, and delete remote repositories on our GitHub account.&lt;/p&gt;
&lt;p&gt;We could authenticate manually using SSH. However, PyCharm simplifies the process by integrating GitHub and GitLab right into the IDE. To authenticate, go to &lt;em&gt;Settings&lt;/em&gt; and look for &lt;em&gt;GitHub&lt;/em&gt; under &lt;em&gt;Version Control&lt;/em&gt;. Then, click on the plus (+) sign and select the &lt;em&gt;Log In via GitHub&lt;/em&gt; option:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Log in to GitHub through PyCharm's UI" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/login-github-through-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/login-github-through-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/login-github-through-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/login-github-through-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/login-github-through-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Log in to GitHub through PyCharm's UI&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A browser window should open and ask you to confirm whether you want to authorize PyCharm to access your GitHub account. Once you confirm by selecting &lt;em&gt;Authorize in GitHub&lt;/em&gt;, you will be redirected to GitHub:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Authorize PyCharm in GitHub" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/authorize-pycharm-github.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/authorize-pycharm-github.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/authorize-pycharm-github.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/authorize-pycharm-github.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/authorize-pycharm-github.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Authorize PyCharm in GitHub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you aren't already signed in, you will be shown the login page where you can sign up too if you don't already have a GitHub account:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitHub's log in page for authorizing PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/github-login-page-authorizing-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-login-page-authorizing-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-login-page-authorizing-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-login-page-authorizing-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-login-page-authorizing-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;GitHub's log in page for authorizing PyCharm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you provide your GitHub credentials and click the &lt;em&gt;Sign in&lt;/em&gt; button, you'll get your GitHub account listed in the &lt;em&gt;Version Control&lt;/em&gt; screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitHub account authorized in PyCharm" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/github-account-authorized-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-account-authorized-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-account-authorized-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-account-authorized-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/github-account-authorized-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;GitHub account authorized in PyCharm&lt;/em&gt;&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;  PyCharm only needs to be authorized once for each user on a computer.&lt;/p&gt;
&lt;p&gt;Once PyCharm is authorized, we also need to create a new, empty remote repository. Go back to the GitHub website and click on the plus sign (+) at the top of your &lt;em&gt;Dashboard&lt;/em&gt; to create a new repository:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Creating a new GitHub repository from the Dashboard" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/creating-new-github-repository-from-dashboard.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-github-repository-from-dashboard.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-github-repository-from-dashboard.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-github-repository-from-dashboard.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-new-github-repository-from-dashboard.png?tr=w-600 600w" loading="lazy" width="527" height="419"/&gt;
&lt;em&gt;Creating a new GitHub repository from the Dashboard&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's give this repository a unique name related to the project we are working on:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create a new repository page on GitHub" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/create-new-repository-page-github.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/create-new-repository-page-github.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/create-new-repository-page-github.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/create-new-repository-page-github.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/create-new-repository-page-github.png?tr=w-600 600w" loading="lazy" width="1141" height="1145"/&gt;
&lt;em&gt;Create a new repository page on GitHub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Depending on who you plan to share this project with, you can also set the repository's visibility to be &lt;em&gt;public&lt;/em&gt; or &lt;em&gt;private&lt;/em&gt;. Then, click the &lt;em&gt;Create Repository&lt;/em&gt; button and copy the remote repository URL:&lt;/p&gt;
&lt;p&gt;&lt;img alt="New repository on GitHub" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/new-repository-github.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/new-repository-github.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/new-repository-github.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/new-repository-github.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/new-repository-github.png?tr=w-600 600w" loading="lazy" width="1893" height="690"/&gt;
&lt;em&gt;New repository on GitHub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Finally, click on the &lt;em&gt;VCS widget&lt;/em&gt; in PyCharm's &lt;em&gt;Window Header&lt;/em&gt; to &lt;em&gt;Push&lt;/em&gt; all the commits we've made in the local repository to the remote repository we just created on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pushing to GitHub through PyCharm's UI" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pushing-github-through-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-github-through-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-github-through-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-github-through-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-github-through-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Pushing to GitHub through PyCharm's UI&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then, we need to specify where the remote repository is located. So, click on &lt;em&gt;Define Remote&lt;/em&gt; and paste the repository's URL into the corresponding input box:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Setting the GitHub repository's URL through PyCharm's UI" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/setting-github-repository-url-through-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/setting-github-repository-url-through-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/setting-github-repository-url-through-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/setting-github-repository-url-through-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/setting-github-repository-url-through-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Setting the GitHub repository's URL through PyCharm's UI&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;em&gt;OK&lt;/em&gt; and then select the &lt;em&gt;Push&lt;/em&gt; button:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pushing commits to GitHub through PyCharm's UI" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pushing-commits-github-through-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-commits-github-through-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-commits-github-through-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-commits-github-through-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pushing-commits-github-through-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Pushing commits to GitHub through PyCharm's UI&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, any commits we made to our project will be visible in the remote repository on GitHub. If we go back to GitHub, we can see that it's no longer an empty repository:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Populated repository on GitHub" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/populated-repository-github.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/populated-repository-github.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/populated-repository-github.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/populated-repository-github.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/populated-repository-github.png?tr=w-600 600w" loading="lazy" width="1805" height="596"/&gt;
&lt;em&gt;Populated repository on GitHub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Version Control&lt;/em&gt; tool window should also display the name we set for the remote next to the latest commit:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Version Control tool window showing the remote repository" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-remote-repository.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-remote-repository.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-remote-repository.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-remote-repository.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-version-control-tool-window-showing-remote-repository.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Version Control tool window showing the remote repository&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To make the next commit, make your changes and go to the &lt;em&gt;Commit&lt;/em&gt; tool window, where you can click the &lt;em&gt;Commit and Push...&lt;/em&gt; button to do both actions in one go:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Committing new changes through PyCharm's UI" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/committing-new-changes-through-pycharm.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/committing-new-changes-through-pycharm.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/committing-new-changes-through-pycharm.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/committing-new-changes-through-pycharm.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/committing-new-changes-through-pycharm.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Committing new changes through PyCharm's UI&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We can also go to the &lt;em&gt;VCS widget&lt;/em&gt; and select &lt;em&gt;Push&lt;/em&gt; to do this at any time.&lt;/p&gt;
&lt;h2 id="explore-other-useful-pycharm-features-for-gui-development"&gt;Explore Other Useful PyCharm Features for GUI Development&lt;/h2&gt;
&lt;p&gt;PyCharm has several features that help us write Python GUI code more efficiently and make development smoother. In this section, we'll explore some of these features and learn how to use them.&lt;/p&gt;
&lt;h3&gt;Search Everywhere&lt;/h3&gt;
&lt;p&gt;Projects can become quite large spanning multiple files and folders. By using the &lt;em&gt;Search Everywhere&lt;/em&gt; feature, we can look through the whole project for files, classes, lines of code and even specific actions like &lt;em&gt;Commit&lt;/em&gt; or &lt;em&gt;Push&lt;/em&gt;. This feature also allows us to quickly find PyCharm's &lt;em&gt;Settings&lt;/em&gt; and tool windows without using our mouse.&lt;/p&gt;
&lt;p&gt;To use &lt;em&gt;Search Everywhere&lt;/em&gt;, double-tap the &lt;em&gt;SHIFT&lt;/em&gt; key. You'll get presented with the &lt;em&gt;search window&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Search Everywhere window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-search-everywhere-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-search-everywhere-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-search-everywhere-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-search-everywhere-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-search-everywhere-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Search Everywhere window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To specify what you are looking for, press &lt;em&gt;TAB&lt;/em&gt; to switch between &lt;em&gt;Classes&lt;/em&gt;, &lt;em&gt;Files&lt;/em&gt;, &lt;em&gt;Symbols&lt;/em&gt;, &lt;em&gt;Actions&lt;/em&gt;, and so on. It also supports evaluating mathematical expressions and filtering out certain file types via the &lt;em&gt;Filter&lt;/em&gt; button at the top-right corner.&lt;/p&gt;
&lt;h3&gt;Code With Me&lt;/h3&gt;
&lt;p&gt;There are times when we need to give temporary access to other developers so that they can review our code and provide assistance. In these cases, we can use PyCharm's &lt;em&gt;Code With Me&lt;/em&gt; feature to create a shared session between a host and one or more guests.&lt;/p&gt;
&lt;p&gt;The guests can see what the host is doing in PyCharm and can be allowed to edit files in the project in real time. We can also share the camera and microphone so we can discuss with others who joined that session.&lt;/p&gt;
&lt;p&gt;To start a session, click on the &lt;em&gt;Code With Me&lt;/em&gt; icon at the top-right of the &lt;em&gt;window header&lt;/em&gt; next to the &lt;em&gt;Run&lt;/em&gt; button:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Code With Me feature" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-feature.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-feature.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-feature.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-feature.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-feature.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Code With Me feature&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;After selecting the &lt;em&gt;Start Session...&lt;/em&gt; option from the menu, PyCharm will allow you to configure what guests can and can't do in this session:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Code With Me: Start Session dialog" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-start-session-dialog.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-start-session-dialog.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-start-session-dialog.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-start-session-dialog.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-code-with-me-start-session-dialog.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Code With Me: Start Session dialog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can use &lt;em&gt;Code With Me&lt;/em&gt; for free in PyCharm Community edition but there are limits on the number of guests allowed and the session time. You can &lt;a href="https://www.jetbrains.com/code-with-me/buy/?section=personal&amp;amp;billing=yearly"&gt;learn more about the specific limits for the Community plan of &lt;em&gt;Code With Me&lt;/em&gt;&lt;/a&gt; on their website.&lt;/p&gt;
&lt;p&gt;Once we set &lt;em&gt;permissions&lt;/em&gt; for our guests, PyCharm will create a link we need to send to the guests we are inviting. To copy the link, click on the &lt;em&gt;Code With Me&lt;/em&gt; icon again and select &lt;em&gt;Copy Session Link&lt;/em&gt; from the menu.&lt;/p&gt;
&lt;p&gt;When a guest clicks the link, we will receive a &lt;em&gt;popup&lt;/em&gt; in PyCharm asking us to either &lt;em&gt;accept&lt;/em&gt; or &lt;em&gt;decline&lt;/em&gt; the guest. If we &lt;em&gt;accept&lt;/em&gt;, they will be able to use a lightweight installation of PyCharm just using their web browser through which we can collaborate.&lt;/p&gt;
&lt;p&gt;It is always recommended to use Git for collaborating with others on a project. &lt;em&gt;Code With Me&lt;/em&gt; can't tell who made a change or why that change was made. More importantly, it requires an active connection to the host's computer to make changes to the project and guests have limited access to PyCharm's features.&lt;/p&gt;
&lt;h3&gt;Sync Python Requirements&lt;/h3&gt;
&lt;p&gt;Earlier in this guide, we installed the &lt;code&gt;PyQt6&lt;/code&gt; package to build the interface of our sample app. Most software we develop depends on third-party Python packages for essential features. In collaborative contexts, we often need to keep track of our project's dependencies so that everyone working with us knows what to install.&lt;/p&gt;
&lt;p&gt;In Python, developers typically create a &lt;code&gt;requirements.txt&lt;/code&gt; file that lists all the needed packages and their recommended versions. You can create this file manually. However, PyCharm provides tools to generate a &lt;code&gt;requirements.txt&lt;/code&gt; file using its UI.&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;  As a best practice, you should update a project's &lt;code&gt;requirements.txt&lt;/code&gt; file whenever the dependencies change.&lt;/p&gt;
&lt;p&gt;To generate the file, use &lt;em&gt;Search Everywhere&lt;/em&gt; to find the &lt;em&gt;Sync Python Requirements&lt;/em&gt; action:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Searching for the Sync Python Requirements action in PyCharm's Search Everywhere" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/searching-sync-python-requirements-action-pycharm-search-everywhere.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/searching-sync-python-requirements-action-pycharm-search-everywhere.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/searching-sync-python-requirements-action-pycharm-search-everywhere.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/searching-sync-python-requirements-action-pycharm-search-everywhere.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/searching-sync-python-requirements-action-pycharm-search-everywhere.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Searching for the Sync Python Requirements action in PyCharm's Search Everywhere&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Selecting the &lt;em&gt;Sync Python Requirements&lt;/em&gt; action will open a dialog where we can specify how to handle package versions and manage the requirements file:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Sync Python Requirements dialog" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-sync-python-requirements-dialog.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sync-python-requirements-dialog.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sync-python-requirements-dialog.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sync-python-requirements-dialog.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-sync-python-requirements-dialog.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Sync Python Requirements dialog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once we click the &lt;em&gt;OK&lt;/em&gt; button, PyCharm will create the file and populate it with the names and versions of the required Python packages.&lt;/p&gt;
&lt;p&gt;If we look at the &lt;em&gt;Project&lt;/em&gt; tool window, we'll see the new &lt;code&gt;requirements.txt&lt;/code&gt; file in the project directory. If we open the file, we will see &lt;code&gt;PyQt6&lt;/code&gt; listed with its package version:&lt;/p&gt;
&lt;p&gt;&lt;img alt="The requirements.txt file listed on PyCharm's Project tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/requirements-file-pycharm-project-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/requirements-file-pycharm-project-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/requirements-file-pycharm-project-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/requirements-file-pycharm-project-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/requirements-file-pycharm-project-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; file listed on PyCharm's Project tool window&lt;/em&gt;&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;  Remember to run the &lt;em&gt;Sync Python Requirements&lt;/em&gt; action again whenever you install or update Python packages in your project.&lt;/p&gt;
&lt;h3&gt;TODOs&lt;/h3&gt;
&lt;p&gt;Keeping track of changes already made in a project is important, but so is tracking what we still need to do. We can keep track of what needs to be done using &lt;strong&gt;TODOs&lt;/strong&gt;. A TODO is a comment in our code that we start with &lt;code&gt;# TODO&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A TODO comment on PyCharm's editor and TODO tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/todo-comment-pycharm-editor-todo-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/todo-comment-pycharm-editor-todo-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/todo-comment-pycharm-editor-todo-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/todo-comment-pycharm-editor-todo-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/todo-comment-pycharm-editor-todo-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;A TODO comment on PyCharm's editor and TODO tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;PyCharm will track these comments across the whole project, and we can view them all at any time in the &lt;em&gt;TODO&lt;/em&gt; tool window. This tool window has several options to filter the TODOs:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's TODO filtering options" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-todo-filtering-options.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-todo-filtering-options.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-todo-filtering-options.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-todo-filtering-options.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-todo-filtering-options.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's TODO filtering options&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We can see every single TODO across the project by selecting the &lt;em&gt;Project&lt;/em&gt; tab or just the ones in the current file with &lt;em&gt;Current File&lt;/em&gt;. We can be even more specific by looking only at files that were recently viewed, changed, or are currently open by selecting the &lt;em&gt;Scope Based&lt;/em&gt; option.&lt;/p&gt;
&lt;p&gt;When we use Git for version control, PyCharm will also show the number of remaining TODOs in each commit:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm showing the number of remaining TODOs" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-showing-remaining-todos.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-remaining-todos.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-remaining-todos.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-remaining-todos.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-showing-remaining-todos.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm showing the number of remaining TODOs&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Scratches&lt;/h3&gt;
&lt;p&gt;Scratches are another interesting PyCharm feature that allows us to write temporary notes or draft code without touching any of the project files. To add a scratch, go to the &lt;em&gt;Project&lt;/em&gt; tool window and right-click on &lt;em&gt;Scratches and Consoles&lt;/em&gt; entry:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Creating a Scratch file from PyCharm's Project tool window" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/creating-scratch-file-from-pycharm-project-tool-window.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-scratch-file-from-pycharm-project-tool-window.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-scratch-file-from-pycharm-project-tool-window.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-scratch-file-from-pycharm-project-tool-window.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/creating-scratch-file-from-pycharm-project-tool-window.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;Creating a Scratch file from PyCharm's Project tool window&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;New -&amp;gt; Scratch File&lt;/em&gt; option will open a dialog where you can select the type of scratch file you want to create.&lt;/p&gt;
&lt;h3&gt;Plugin Marketplace&lt;/h3&gt;
&lt;p&gt;While PyCharm is already packed with features to aid development, it is also highly extensible thanks to its &lt;em&gt;Plugin marketplace&lt;/em&gt;. We can customize our PyCharm installation by getting themes and other plugins that provide additional functionality.&lt;/p&gt;
&lt;p&gt;To use the &lt;em&gt;Plugin marketplace&lt;/em&gt;, click on the &lt;em&gt;Settings&lt;/em&gt; icon on the &lt;em&gt;Window Header&lt;/em&gt; and select &lt;em&gt;Plugins&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyCharm's Plugin marketplace" src="https://www.pythonguis.com/static/tutorials/developer/getting-started-pycharm/pycharm-plugin-marketplace.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-plugin-marketplace.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-plugin-marketplace.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-plugin-marketplace.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/developer/getting-started-pycharm/pycharm-plugin-marketplace.png?tr=w-600 600w" loading="lazy" width="1366" height="768"/&gt;
&lt;em&gt;PyCharm's Plugin marketplace&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;Plugin marketplace&lt;/em&gt;, we can see installed plugins as well as those that are available in the marketplace. To install a plugin, click the Install button and wait for PyCharm to download and install it.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this tutorial, you have learned how to install and set up the PyCharm IDE for Python GUI development with PyQt6. You've also learned how to write and run Python code in PyCharm as well as how to install external Python packages like PyQt6 using the IDE's graphical interface. You are now able to create and configure a Python virtual environment and use Git for version control in your projects.&lt;/p&gt;
&lt;p&gt;Finally, you explored several additional useful features for working with GUI applications in PyCharm, including Search Everywhere, Code With Me, requirements syncing, TODOs, scratches, and the plugin marketplace. With this knowledge, you are now ready to confidently use PyCharm to develop your own Python GUI apps and desktop applications.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="pycharm"/><category term="ide"/><category term="pyqt6"/><category term="virtual-environments"/><category term="git"/><category term="qt"/><category term="qt6"/></entry><entry><title>Saving and Restoring Application Settings with QSettings in PySide6 — Learn how to use QSettings to remember user preferences, window sizes, and configuration options between sessions</title><link href="https://www.pythonguis.com/faq/pyside6-qsettings-how-to-use-qsettings/" rel="alternate"/><published>2024-10-09T09:00:00+00:00</published><updated>2024-10-09T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2024-10-09:/faq/pyside6-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 PySide6) 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 PySide6 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 PySide6.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 PySide6 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 PySide6.QtWidgets import (
    QApplication, QMainWindow, QComboBox,
    QVBoxLayout, QWidget, QLabel
)
from PySide6.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 new to creating windows in PySide6, our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyside6-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;.&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.&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 PySide6.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 PySide6 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. For a practical example of saving and restoring window geometry specifically, see our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/restore-window-geometry-pyqt/"&gt;restoring window geometry with QSettings&lt;/a&gt;. You can also learn more about building complete application interfaces with &lt;a href="https://www.pythonguis.com/tutorials/pyside6-signals-slots-events/"&gt;signals, slots, and events&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/tutorials/pyside6-widgets/"&gt;widgets in PySide6&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.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="python"/><category term="intermediate"/><category term="settings"/><category term="qsettings"/><category term="qt"/><category term="qt6"/></entry><entry><title>Introduction to the QGraphics Framework in PySide6 — Creating vector interfaces using the QGraphics View framework</title><link href="https://www.pythonguis.com/tutorials/pyside6-qgraphics-vector-graphics/" rel="alternate"/><published>2024-09-27T07:00:00+00:00</published><updated>2024-09-27T07:00:00+00:00</updated><author><name>Salem Al Bream</name></author><id>tag:www.pythonguis.com,2024-09-27:/tutorials/pyside6-qgraphics-vector-graphics/</id><summary type="html">The Qt Graphics View Framework allows you to develop &lt;em&gt;fast&lt;/em&gt; and &lt;em&gt;efficient&lt;/em&gt; 2D vector graphic scenes. Scenes can contain &lt;em&gt;millions&lt;/em&gt; of items, each with their own features and behaviors. By using the Graphics View via PySide6 you get access to this highly performant graphics layer in Python. Whether you're integrating vector graphics views into an existing PySide6 application, or simply want a powerful vector graphics interface for Python, Qt's Graphics View is what you're looking for.</summary><content type="html">
            &lt;p&gt;The Qt Graphics View Framework allows you to develop &lt;em&gt;fast&lt;/em&gt; and &lt;em&gt;efficient&lt;/em&gt; 2D vector graphic scenes. Scenes can contain &lt;em&gt;millions&lt;/em&gt; of items, each with their own features and behaviors. By using the Graphics View via PySide6 you get access to this highly performant graphics layer in Python. Whether you're integrating vector graphics views into an existing PySide6 application, or simply want a powerful vector graphics interface for Python, Qt's Graphics View is what you're looking for.&lt;/p&gt;
&lt;p&gt;Some common uses of the Graphics View include data visualization, mapping applications, 2D design tools, modern data dashboards and even 2D games.&lt;/p&gt;
&lt;p&gt;In this tutorial we'll take our first steps looking at the Qt Graphics View framework, building a scene with some simple vector items. This will allow us to familiarize ourselves with the API and coordinate system, which we'll use later to build more complex examples.&lt;/p&gt;
&lt;h2 id="what-is-the-qt-graphics-view-framework"&gt;What is the Qt Graphics View Framework?&lt;/h2&gt;
&lt;p&gt;The Graphics View framework consists of 3 main parts &lt;code&gt;QGraphicsView&lt;/code&gt;, &lt;code&gt;QGraphicsScene&lt;/code&gt;, and &lt;code&gt;QGraphicsItem&lt;/code&gt;, each with different responsibilities.&lt;/p&gt;
&lt;p&gt;The framework can be interpreted using the Model-View paradigm, with the &lt;code&gt;QGraphicsScene&lt;/code&gt; as the &lt;em&gt;Model&lt;/em&gt; and the &lt;code&gt;QGraphicsView&lt;/code&gt; as the &lt;em&gt;View&lt;/em&gt;. Each scene can have multiple views.
The &lt;em&gt;QGraphicsItems&lt;/em&gt; within the scene can be considered as items within the model, holding the visual data that the scene combines to define the complete image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QGraphicsScene&lt;/code&gt; is the central component that glues everything together. It acts as a &lt;em&gt;whiteboard&lt;/em&gt; on which all items are drawn (circles, rectangles, lines, pixmaps,
etc). The &lt;code&gt;QGraphicsView&lt;/code&gt; has the responsibility of rendering a given scene -- or part of it, with some transformation (scaling, rotating, shearing) -- to display it to the user. The view is a standard Qt widget and can be placed inside any &lt;a href="https://www.pythonguis.com/tutorials/pyside6-layouts/"&gt;Qt layout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QGraphicsScene&lt;/code&gt; provides some important functionalities out of the box, so we can use them to develop advanced applications without struggling with low-level details. For example --&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Collision Detection&lt;/strong&gt;, detect when a graphics item has collided with another item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Item Selection&lt;/strong&gt;, gives us the ability to deal with multiple items at the same time, for example, the user can select multiple items, and when pressing delete, a function asks the scene to give the list for all selected items, and then delete them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Items discovery&lt;/strong&gt;, the scene can tell us what items are present (or part of them) at a specific point or inside some defined region, for example, if the user adds an item that intersects with a forbidden area, the program will detect them and give them another (mostly red) color.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events Propagation&lt;/strong&gt;, the scene receives the events and then propagates them to items.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To define a &lt;code&gt;QGraphicsScene&lt;/code&gt; you define its boundaries or &lt;em&gt;sceneRect&lt;/em&gt; which defines the x &amp;amp; y origins and dimensions of the scene. If you don't provide a &lt;em&gt;sceneRect&lt;/em&gt; it will default to the minimum bounding rectangle for all child items -- updating as items are added, moved or removed. This is flexible but less efficient.&lt;/p&gt;
&lt;p&gt;Items in the scene are represented by &lt;code&gt;QGraphicsItem&lt;/code&gt; objects. These are the basic building block of any 2D scene, representing a shape, pixmap or SVG image to be displayed in the scene. Each item has a relative position inside the &lt;code&gt;sceneRect&lt;/code&gt; and can have different transformation effects (scale, translate, rotate, shear).&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;QGraphicsView&lt;/code&gt; is the renderer of the scene, taking the scene and displaying it -- either wholly or in part -- to the user. The view itself can have transformations (scale, translate, rotate and shear) applied to modify the display without affecting the underlying scene. By default the view will forward mouse and keyboard events to the scene allowing for user interaction. This can be disabled by calling &lt;code&gt;view.setInteractive(False)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="creating-a-simple-qgraphicsscene"&gt;Creating a Simple QGraphicsScene&lt;/h2&gt;
&lt;p&gt;Let's start by creating a simple scene. The following code creates a &lt;code&gt;QGraphicsScene&lt;/code&gt;, defining a 400 x 200 scene, and then displays it in a &lt;code&gt;QGraphicsView&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 PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QApplication

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this example you'll see an empty window.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Empty QGraphicsScene displayed in a QGraphicsView window" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;The empty graphics scene, shown in a QGraphicsView window.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not very exciting yet -- but this is our &lt;code&gt;QGraphicsView&lt;/code&gt; displaying our &lt;em&gt;empty&lt;/em&gt; scene.&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;  As mentioned earlier, &lt;code&gt;QGraphicsView&lt;/code&gt; is a &lt;em&gt;widget&lt;/em&gt;. In Qt any widgets without a parent display as windows. This is why our &lt;code&gt;QGraphicsView&lt;/code&gt; appears as a window on the desktop.&lt;/p&gt;
&lt;h3&gt;Adding QGraphicsItems to the Scene&lt;/h3&gt;
&lt;p&gt;Let's start adding some items to the scene. There are a number of built-in &lt;em&gt;graphics items&lt;/em&gt; which you can customize and add to your scene.
In the example below we use &lt;code&gt;QGraphicsRectItem&lt;/code&gt; which draws a rectangle. We create the item passing in its dimensions, and then set its position,
pen and brush before adding it to the scene.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)

scene.addItem(rect)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running the above you'll see a single, rather ugly colored, rectangle in the scene.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A single QGraphicsRectItem rectangle in the scene" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;A single rectangle in the scene&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Adding more items is simply a case of creating the objects, customizing them and then adding them to the scene. In the example
below we add a circle, using &lt;code&gt;QGraphicsEllipseItem&lt;/code&gt; -- a circle is just an ellipse with equal height and width.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)

ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)

brush = QBrush(Qt.blue)
ellipse.setBrush(brush)

pen = QPen(Qt.green)
pen.setWidth(5)
ellipse.setPen(pen)

# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)


view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The above code will give the following result.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene with two items &amp;mdash; a rectangle and an ellipse" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;A scene with two items&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Controlling Item Stacking Order with ZValue&lt;/h3&gt;
&lt;p&gt;The order you add items affects the stacking order in the scene -- items added later will always appear &lt;em&gt;on top&lt;/em&gt; of items
added first. However, if you need more control you can &lt;em&gt;set&lt;/em&gt; the stacking order using &lt;code&gt;.setZValue&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;ellipse.setZValue(500)
rect.setZValue(200)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now the circle (ellipse) appears above the rectangle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Using setZValue to control item stacking order in the scene" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;Using ZValue to order items in the scene&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Try experimenting with setting the Z value of the two items -- you can set it &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;after&lt;/em&gt; the items are in the scene, and can change it 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;  Z in this context refers to the Z coordinate. The X &amp;amp; Y coordinates are the horizontal and vertical position in the scene respectively. The Z coordinate determines the relative position of items toward the front and back of the scene -- coming "out" of the screen towards the viewer.&lt;/p&gt;
&lt;p&gt;There are also the convenience methods &lt;code&gt;.stackBefore()&lt;/code&gt; and &lt;code&gt;.stackAfter()&lt;/code&gt; which allow you to stack your &lt;code&gt;QGraphicsItem&lt;/code&gt; behind, or in front of another item in the scene.&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;ellipse.stackAfter(rect)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Making Items Moveable and Selectable&lt;/h3&gt;
&lt;p&gt;Our two &lt;code&gt;QGraphicsItem&lt;/code&gt; objects are currently fixed in position where we place them, but they don't have to be! As already mentioned Qt's Graphics View framework allows items to respond to user input, for example allowing them to be dragged and dropped around the scene at will. Simple functionality like this is actually already built in, you just need to enable it on each &lt;code&gt;QGraphicsItem&lt;/code&gt;. To do that we need to set the flag &lt;code&gt;QGraphicsItem.GraphicsItemFlags.ItemIsMoveable&lt;/code&gt; on the item.&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;  The full list of &lt;a href="https://doc.qt.io/qt-5/qgraphicsitem.html#GraphicsItemFlag-enum"&gt;graphics item flags is available here&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 PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)

ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)

brush = QBrush(Qt.blue)
ellipse.setBrush(brush)

pen = QPen(Qt.green)
pen.setWidth(5)
ellipse.setPen(pen)

# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)

ellipse.setFlag(QGraphicsItem.ItemIsMovable)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the above example we've set &lt;code&gt;ItemIsMovable&lt;/code&gt; on the &lt;em&gt;ellipse&lt;/em&gt; only. You can drag the ellipse around the scene -- including behind the rectangle -- but the rectangle itself will remain locked in place. Experiment with adding more items and configuring the moveable status.&lt;/p&gt;
&lt;p&gt;If you want an item to be &lt;em&gt;selectable&lt;/em&gt; you can enable this by setting the &lt;code&gt;ItemIsSelectable&lt;/code&gt; flag, for example here using &lt;code&gt;.setFlags()&lt;/code&gt; to set multiple flags at the same 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;ellipse.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you click on the ellipse you'll now see it surrounded by a dashed line to indicate that it is selected. We'll look at how to use item selection in more detail in a later tutorial.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A selected QGraphicsItem highlighted with dashed lines" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;A selected item in the scene, highlighted with dashed lines&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="creating-items-directly-on-the-scene"&gt;Creating Items Directly on the Scene&lt;/h2&gt;
&lt;p&gt;So far we've been creating items by creating the objects and &lt;em&gt;then&lt;/em&gt; adding them to the scene. But you can also create an object &lt;em&gt;in&lt;/em&gt; the scene directly by calling one of the helper methods on the scene itself, e.g. &lt;code&gt;scene.addEllipse()&lt;/code&gt;. This &lt;em&gt;creates&lt;/em&gt; the object and &lt;em&gt;returns&lt;/em&gt; it so you can modify it as before.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PySide6.QtGui import QBrush, QPen
from PySide6.QtCore import Qt

app = QApplication(sys.argv)

scene = QGraphicsScene(0, 0, 400, 200)

rect = scene.addRect(0, 0, 200, 50)
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.cyan)
pen.setWidth(10)
rect.setPen(pen)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Feel free to use whichever form you find most comfortable in your code.&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 only use this approach for the built-in &lt;code&gt;QGraphicsItem&lt;/code&gt; object types.&lt;/p&gt;
&lt;h2 id="building-a-complex-scene-with-text-polygons-and-images"&gt;Building a Complex Scene with Text, Polygons, and Images&lt;/h2&gt;
&lt;p&gt;So far we've built a simple scene using the basic &lt;code&gt;QGraphicsRectItem&lt;/code&gt; and &lt;code&gt;QGraphicsEllipseItem&lt;/code&gt; shapes. Now let's use some other &lt;code&gt;QGraphicsItem&lt;/code&gt; objects to build a more complex scene, including lines, text and &lt;code&gt;QPixmap&lt;/code&gt; (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;from PySide6.QtCore import QPointF, Qt
from PySide6.QtWidgets import QGraphicsRectItem, QGraphicsScene, QGraphicsView, QApplication
from PySide6.QtGui import QBrush, QPainter, QPen, QPixmap, QPolygonF
import sys

app = QApplication(sys.argv)

scene = QGraphicsScene(0, 0, 400, 200)

rectitem = QGraphicsRectItem(0, 0, 360, 20)
rectitem.setPos(20, 20)
rectitem.setBrush(QBrush(Qt.red))
rectitem.setPen(QPen(Qt.cyan))
scene.addItem(rectitem)

textitem = scene.addText("QGraphics is fun!")
textitem.setPos(100, 100)

scene.addPolygon(
    QPolygonF(
        [
            QPointF(30, 60),
            QPointF(270, 40),
            QPointF(400, 200),
            QPointF(20, 150),
        ]),
    QPen(Qt.darkGreen),
)

pixmap = QPixmap("cat.jpg")
pixmapitem = scene.addPixmap(pixmap)
pixmapitem.setPos(250, 70)

view = QGraphicsView(scene)
view.setRenderHint(QPainter.Antialiasing)
view.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the example above you'll see the following scene.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene with multiple items including rectangle, polygon, text and pixmap" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;Scene with multiple items including a rectangle, polygon, text and a pixmap.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's step through the code looking at the interesting bits.&lt;/p&gt;
&lt;p&gt;Polygons are defined using a series of &lt;code&gt;QPointF&lt;/code&gt; objects which give the coordinates relative to the &lt;em&gt;item's&lt;/em&gt; position. So, for example if you create a polygon object with a point at 30, 20 and then move this polygon object's X &amp;amp; Y coordinates to 50, 40 then the point will be displayed at 80, 60 in the scene.&lt;/p&gt;
&lt;p&gt;Points inside an item are always relative to the item itself, and item coordinates are always relative to the scene -- or the item's parent, if it has one. We'll take a closer look at the Graphics View coordinate system in the next tutorial.&lt;/p&gt;
&lt;p&gt;To add an image to the scene we can open it from a file using &lt;code&gt;QPixmap()&lt;/code&gt;. This creates a &lt;code&gt;QPixmap&lt;/code&gt; object, which can then in turn be added to the scene using &lt;code&gt;scene.addPixmap(pixmap)&lt;/code&gt;. This returns a &lt;code&gt;QGraphicsPixmapItem&lt;/code&gt; which is the &lt;code&gt;QGraphicsItem&lt;/code&gt; type for the pixmap -- a wrapper that handles displaying the pixmap in the scene. You can use this object to perform any changes to the item in the scene. For more on working with images and pixmaps in PySide6, see our guide on &lt;a href="https://www.pythonguis.com/faq/adding-images-to-pyside6-applications/"&gt;adding images to PySide6 applications&lt;/a&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;  The multiple layers of objects can get confusing, so it's important to choose sensible variable names which make clear the distinction between, e.g. the &lt;em&gt;pixmap&lt;/em&gt; itself and the &lt;em&gt;pixmap item&lt;/em&gt; that contains it.&lt;/p&gt;
&lt;p&gt;Finally, we set the flag &lt;code&gt;RenderHint.Antialiasing&lt;/code&gt; on the view to &lt;em&gt;smooth&lt;/em&gt; the edges of diagonal lines. You almost &lt;em&gt;always&lt;/em&gt; want to enable this on your views as otherwise any rotated
objects will look very ugly indeed. Below is our scene without antialiasing enabled, you can see the jagged lines on the polygon.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene without antialiasing showing jagged edges" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;Scene with antialiasing disabled.&lt;/em&gt;&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;  Antialiasing has a (small) performance impact however, so if you are building scenes with millions of rotated items it may in some cases make sense to turn it off.&lt;/p&gt;
&lt;h2 id="adding-a-qgraphicsview-to-a-qt-layout"&gt;Adding a QGraphicsView to a Qt Layout&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QGraphicsView&lt;/code&gt; is subclassed from &lt;code&gt;QWidget&lt;/code&gt;, meaning it can be placed in layouts just like any other widget. In the following
example we add the view to a simple interface, with buttons which perform a basic effect on the view -- raising and lowering selected item's ZValue. This has the effect of allowing us to move items in front and behind other objects.&lt;/p&gt;
&lt;p&gt;The full code 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 sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QBrush, QPainter, QPen
from PySide6.QtWidgets import (
    QApplication,
    QGraphicsEllipseItem,
    QGraphicsItem,
    QGraphicsRectItem,
    QGraphicsScene,
    QGraphicsView,
    QHBoxLayout,
    QPushButton,
    QSlider,
    QVBoxLayout,
    QWidget,
)


class Window(QWidget):
    def __init__(self):
        super().__init__()

        # Defining a scene rect of 400x200, with it's origin at 0,0.
        # If we don't set this on creation, we can set it later with .setSceneRect
        self.scene = QGraphicsScene(0, 0, 400, 200)

        # Draw a rectangle item, setting the dimensions.
        rect = QGraphicsRectItem(0, 0, 200, 50)
        rect.setPos(50, 20)
        brush = QBrush(Qt.red)
        rect.setBrush(brush)

        # Define the pen (line)
        pen = QPen(Qt.cyan)
        pen.setWidth(10)
        rect.setPen(pen)

        ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
        ellipse.setPos(75, 30)

        brush = QBrush(Qt.blue)
        ellipse.setBrush(brush)

        pen = QPen(Qt.green)
        pen.setWidth(5)
        ellipse.setPen(pen)

        # Add the items to the scene. Items are stacked in the order they are added.
        self.scene.addItem(ellipse)
        self.scene.addItem(rect)

        # Set all items as moveable and selectable.
        for item in self.scene.items():
            item.setFlag(QGraphicsItem.ItemIsMovable)
            item.setFlag(QGraphicsItem.ItemIsSelectable)

        # Define our layout.
        vbox = QVBoxLayout()

        up = QPushButton("Up")
        up.clicked.connect(self.up)
        vbox.addWidget(up)

        down = QPushButton("Down")
        down.clicked.connect(self.down)
        vbox.addWidget(down)

        rotate = QSlider()
        rotate.setRange(0, 360)
        rotate.valueChanged.connect(self.rotate)
        vbox.addWidget(rotate)

        view = QGraphicsView(self.scene)
        view.setRenderHint(QPainter.Antialiasing)

        hbox = QHBoxLayout(self)
        hbox.addLayout(vbox)
        hbox.addWidget(view)

        self.setLayout(hbox)

    def up(self):
        """ Iterate all selected items in the view, moving them forward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z + 1)

    def down(self):
        """ Iterate all selected items in the view, moving them backward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z - 1)

    def rotate(self, value):
        """ Rotate the object by the received number of degrees """
        items = self.scene.selectedItems()
        for item in items:
            item.setRotation(value)


app = QApplication(sys.argv)

w = Window()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this, you will get a window like that shown below. By selecting an item in the graphics view and then clicking either the "Up" or "Down" button you can move items up and down within the scene -- behind and in front of one another. The items are all moveable, so you can drag them around too. Clicking on the slider will rotate the currently selected items by the set number of degrees.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A QGraphicsView embedded in a PySide6 layout with custom controls" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-600 600w" loading="lazy" width="530" height="267"/&gt;
&lt;em&gt;A graphics scene embedded in a Qt layout with custom controls for stacking and rotation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The raising and lowering is handled by our custom methods &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;, which work by iterating over the &lt;em&gt;currently selected&lt;/em&gt; items in the scene -- retrieved using &lt;code&gt;scene.selectedItems()&lt;/code&gt; and then getting the item's z value and increasing or decreasing it respectively.&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 up(self):
        """ Iterate all selected items in the view, moving them forward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z + 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;While rotation is handled using the &lt;code&gt;item.setRotation&lt;/code&gt; method. This receives the current angle from the &lt;code&gt;QSlider&lt;/code&gt; and again, applies it to any currently selected items in the scene. The button's &lt;code&gt;clicked&lt;/code&gt; &lt;a href="https://www.pythonguis.com/tutorials/pyside6-signals-slots-events/"&gt;signal is connected to our slot methods&lt;/a&gt; to handle the user interaction.&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 rotate(self, value):
        """ Rotate the object by the received number of degrees. """
        items = self.scene.selectedItems()
        for item in items:
            item.setRotation(value)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at the &lt;a href="https://doc.qt.io/qt-5/qgraphicsitem.html"&gt;QGraphicsItem documentation&lt;/a&gt; for some other properties you can control with widgets and try extending the interface to allow you to change them dynamically. If you want to go further with custom graphics items, see our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyside6-creating-your-own-custom-widgets/"&gt;creating your own custom widgets in PySide6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hopefully this introduction to the Qt Graphics View framework has given you some ideas of what you can do with it. In the next tutorials we'll look at how events and user interaction can be handled on items and how to create custom &amp;amp; compound items for your own scenes.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="pyside6"/><category term="qgraphics"/><category term="qgraphicsscene"/><category term="qgraphicsview"/><category term="qgraphicsitem"/><category term="vector-graphics"/><category term="2d-graphics"/><category term="qt-graphics-view"/><category term="qt"/><category term="qt6"/><category term="pyside6-vector-graphics"/></entry><entry><title>PyQt6 &amp; PySide6 Books Updated for 2024 — Extended and updated with new examples and demos, including Model-View-Controller architecture</title><link href="https://www.pythonguis.com/blog/pyqt6-pyside6-books-updated-2024/" rel="alternate"/><published>2024-06-30T06:00:00+00:00</published><updated>2024-06-30T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2024-06-30:/blog/pyqt6-pyside6-books-updated-2024/</id><summary type="html">Hello! Today I have released new digital updates to my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt;.</summary><content type="html">
            &lt;p&gt;Hello! Today I have released new digital updates to my book &lt;em&gt;Create GUI Applications with Python &amp;amp; Qt&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="whats-new-in-the-2024-edition-5th-edition"&gt;What's New in the 2024 Edition (5th Edition)?&lt;/h2&gt;
&lt;p&gt;This update brings all versions up to date with the latest developments in PyQt6 &amp;amp; PySide6. As well as corrections and additions to existing chapters, there are new sections dealing with form layouts, built-in dialogs, and developing Qt applications using a &lt;strong&gt;Model-View-Controller (MVC) architecture&lt;/strong&gt;. The same corrections and additions have been made to the PyQt5 &amp;amp; PySide2 editions.&lt;/p&gt;
&lt;p&gt;Whether you're building desktop applications with PyQt6, PySide6, or the earlier PyQt5 and PySide2 libraries, this edition provides updated guidance and practical examples to help you create professional Python GUI applications.&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;  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;the downloads page&lt;/a&gt; and enter the email you used for the purchase.&lt;/p&gt;
&lt;h2 id="get-your-copy-of-the-python-qt-book"&gt;Get Your Copy of the Python &amp;amp; Qt Book&lt;/h2&gt;
&lt;p&gt;You can buy the latest editions below --&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 Book, 5th Edition, Create GUI Applications with Python &amp;amp; Qt6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyqt5-book/"&gt;PyQt5 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonguis.com/pyside2-book/"&gt;PySide2 Book 5th Edition, Create GUI Applications with Python &amp;amp; Qt5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you bought the book elsewhere (in paperback or digital), you can register to get these updates too. &lt;strong&gt;Email your receipt to &lt;a href="mailto:register@pythonguis.com"&gt;register@pythonguis.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide2 see my book, &lt;a href="https://www.mfitzp.com/pyside2-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qt6"/><category term="python"/><category term="pyqt5"/><category term="pyside2"/><category term="pyside6"/><category term="qt5"/><category term="python-gui"/><category term="mvc"/><category term="qt"/></entry><entry><title>Introduction to the QGraphics Framework in PyQt6 — Creating vector interfaces using the QGraphics View framework</title><link href="https://www.pythonguis.com/tutorials/pyqt6-qgraphics-vector-graphics/" rel="alternate"/><published>2024-04-27T07:00:00+00:00</published><updated>2024-04-27T07:00:00+00:00</updated><author><name>Salem Al Bream</name></author><id>tag:www.pythonguis.com,2024-04-27:/tutorials/pyqt6-qgraphics-vector-graphics/</id><summary type="html">The Qt Graphics View Framework allows you to develop &lt;em&gt;fast&lt;/em&gt; and &lt;em&gt;efficient&lt;/em&gt; 2D vector graphic scenes. Scenes can contain &lt;em&gt;millions&lt;/em&gt; of items, each with their own features and behaviors. By using the Graphics View via PyQt6 you get access to this highly performant graphics layer in Python. Whether you're integrating vector graphics views into an existing PyQt6 application, or simply want a powerful vector graphics interface for Python, Qt's Graphics View is what you're looking for.</summary><content type="html">
            &lt;p&gt;The Qt Graphics View Framework allows you to develop &lt;em&gt;fast&lt;/em&gt; and &lt;em&gt;efficient&lt;/em&gt; 2D vector graphic scenes. Scenes can contain &lt;em&gt;millions&lt;/em&gt; of items, each with their own features and behaviors. By using the Graphics View via PyQt6 you get access to this highly performant graphics layer in Python. Whether you're integrating vector graphics views into an existing PyQt6 application, or simply want a powerful vector graphics interface for Python, Qt's Graphics View is what you're looking for.&lt;/p&gt;
&lt;p&gt;Some common uses of the Graphics View include data visualization, mapping applications, 2D design tools, modern data dashboards and even 2D games.&lt;/p&gt;
&lt;p&gt;In this tutorial we'll take our first steps looking at the Qt Graphics View framework, building a scene with some simple vector items. This will allow us to familiarize ourselves with the API and coordinate system, which we'll use later to build more complex examples.&lt;/p&gt;
&lt;h2 id="what-is-the-qgraphics-view-framework"&gt;What is the QGraphics View Framework?&lt;/h2&gt;
&lt;p&gt;The Graphics View framework consists of 3 main parts &lt;code&gt;QGraphicsView&lt;/code&gt;, &lt;code&gt;QGraphicsScene&lt;/code&gt;, and &lt;code&gt;QGraphicsItem&lt;/code&gt;, each with different responsibilities.&lt;/p&gt;
&lt;p&gt;The framework can be interpreted using the Model-View paradigm, with the &lt;code&gt;QGraphicsScene&lt;/code&gt; as the &lt;em&gt;Model&lt;/em&gt; and the &lt;code&gt;QGraphicsView&lt;/code&gt; as the &lt;em&gt;View&lt;/em&gt;. Each scene can have multiple views.
The &lt;em&gt;QGraphicsItems&lt;/em&gt; within the scene can be considered as items within the model, holding the visual data that the scene combines to define the complete image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QGraphicsScene&lt;/code&gt; is the central component that glues everything together. It acts as a &lt;em&gt;whiteboard&lt;/em&gt; on which all items are drawn (circles, rectangles, lines, pixmaps,
etc). The &lt;code&gt;QGraphicsView&lt;/code&gt; has the responsibility of rendering a given scene -- or part of it, with some transformation (scaling, rotating, shearing) -- to display it to the user. The view is a standard Qt widget and can be placed inside any Qt layout.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;QGraphicsScene&lt;/code&gt; provides some important functionalities out of the box, so we can use them to develop advanced applications without struggling with low-level details. For example --&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Collision Detection&lt;/strong&gt;, detect when a graphics item has collided with another item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Item Selection&lt;/strong&gt;, gives us the ability to deal with multiple items at the same time, for example, the user can select multiple items, and when pressing delete, a function asks the scene to give the list for all selected items, and then delete them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Items discovery&lt;/strong&gt;, the scene can tell us what items are present (or part of them) at a specific point or inside some defined region, for example, if the user adds an item that intersects with a forbidden area, the program will detect them and give them another (mostly red) color.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events Propagation&lt;/strong&gt;, the scene receives the events and then propagates them to items.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To define a &lt;code&gt;QGraphicsScene&lt;/code&gt; you define its boundaries or &lt;em&gt;sceneRect&lt;/em&gt; which defines the x &amp;amp; y origins and dimensions of the scene. If you don't provide a &lt;em&gt;sceneRect&lt;/em&gt; it will default to the minimum bounding rectangle for all child items -- updating as items are added, moved or removed. This is flexible but less efficient.&lt;/p&gt;
&lt;p&gt;Items in the scene are represented by &lt;code&gt;QGraphicsItem&lt;/code&gt; objects. These are the basic building block of any 2D scene, representing a shape, pixmap or SVG image to be displayed in the scene. Each item has a relative position inside the &lt;code&gt;sceneRect&lt;/code&gt; and can have different transformation effects (scale, translate, rotate, shear).&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;QGraphicsView&lt;/code&gt; is the renderer of the scene, taking the scene and displaying it -- either wholly or in part -- to the user. The view itself can have transformations (scale, translate, rotate and shear) applied to modify the display without affecting the underlying scene. By default the view will forward mouse and keyboard events to the scene allowing for user interaction. This can be disabled by calling &lt;code&gt;view.setInteractive(False)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="creating-a-simple-qgraphicsscene-in-pyqt6"&gt;Creating a Simple QGraphicsScene in PyQt6&lt;/h2&gt;
&lt;p&gt;Let's start by creating a simple scene. The following code creates a &lt;code&gt;QGraphicsScene&lt;/code&gt;, defining a 400 x 200 scene, and then displays it in a &lt;code&gt;QGraphicsView&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 QGraphicsScene, QGraphicsView, QApplication

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this example you'll see an empty window.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Empty QGraphicsScene displayed in a QGraphicsView window" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-empty.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;The empty graphics scene, shown in a QGraphicsView window.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not very exciting yet -- but this is our &lt;code&gt;QGraphicsView&lt;/code&gt; displaying our &lt;em&gt;empty&lt;/em&gt; scene.&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;  As mentioned earlier, &lt;code&gt;QGraphicsView&lt;/code&gt; is a &lt;em&gt;widget&lt;/em&gt;. In Qt any widgets without a parent display as windows. This is why our &lt;code&gt;QGraphicsView&lt;/code&gt; appears as a window on the desktop.&lt;/p&gt;
&lt;h3&gt;Adding QGraphicsItems to the Scene&lt;/h3&gt;
&lt;p&gt;Let's start adding some items to the scene. There are a number of built-in &lt;em&gt;graphics items&lt;/em&gt; which you can customize and add to your scene.
In the example below we use &lt;code&gt;QGraphicsRectItem&lt;/code&gt; which draws a rectangle. We create the item passing in its dimensions, and then set its position,
pen and brush before adding it to the scene.&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 QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PyQt6.QtGui import QBrush, QPen
from PyQt6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.GlobalColor.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.GlobalColor.cyan)
pen.setWidth(10)
rect.setPen(pen)

scene.addItem(rect)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running the above you'll see a single, rather ugly colored, rectangle in the scene.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A single QGraphicsRectItem rectangle in the scene" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-single-item.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;A single rectangle in the scene&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Adding more items is simply a case of creating the objects, customizing them and then adding them to the scene. In the example
below we add a circle, using &lt;code&gt;QGraphicsEllipseItem&lt;/code&gt; -- a circle is just an ellipse with equal height and width.&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 QGraphicsScene, QGraphicsView, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PyQt6.QtGui import QBrush, QPen
from PyQt6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.GlobalColor.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.GlobalColor.cyan)
pen.setWidth(10)
rect.setPen(pen)

ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)

brush = QBrush(Qt.GlobalColor.blue)
ellipse.setBrush(brush)

pen = QPen(Qt.GlobalColor.green)
pen.setWidth(5)
ellipse.setPen(pen)

# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)


view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The above code will give the following result.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene with two items &amp;mdash; a rectangle and an ellipse" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;A scene with two items&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Controlling Item Stacking Order with ZValue&lt;/h3&gt;
&lt;p&gt;The order you add items affects the stacking order in the scene -- items added later will always appear &lt;em&gt;on top&lt;/em&gt; of items
added first. However, if you need more control you can &lt;em&gt;set&lt;/em&gt; the stacking order using &lt;code&gt;.setZValue&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;ellipse.setZValue(500)
rect.setZValue(200)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now the circle (ellipse) appears above the rectangle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Using setZValue to control item stacking order in the scene" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-zvalue.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;Using ZValue to order items in the scene&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Try experimenting with setting the Z value of the two items -- you can set it &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;after&lt;/em&gt; the items are in the scene, and can change it 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;  Z in this context refers to the Z coordinate. The X &amp;amp; Y coordinates are the horizontal and vertical position in the scene respectively. The Z coordinate determines the relative position of items toward the front and back of the scene -- coming "out" of the screen towards the viewer.&lt;/p&gt;
&lt;p&gt;There are also the convenience methods &lt;code&gt;.stackBefore()&lt;/code&gt; and &lt;code&gt;.stackAfter()&lt;/code&gt; which allow you to stack your &lt;code&gt;QGraphicsItem&lt;/code&gt; behind, or in front of another item in the scene.&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;ellipse.stackAfter(rect)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Making Items Moveable and Selectable&lt;/h3&gt;
&lt;p&gt;Our two &lt;code&gt;QGraphicsItem&lt;/code&gt; objects are currently fixed in position where we place them, but they don't have to be! As already mentioned Qt's Graphics View framework allows items to respond to user input, for example allowing them to be dragged and dropped around the scene at will. Simple functionality like this is actually already built in, you just need to enable it on each &lt;code&gt;QGraphicsItem&lt;/code&gt;. To do that we need to set the flag &lt;code&gt;QGraphicsItem.GraphicsItemFlags.ItemIsMoveable&lt;/code&gt; on the item.&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;  The full list of &lt;a href="https://doc.qt.io/qt-5/qgraphicsitem.html#GraphicsItemFlag-enum"&gt;graphics item flags is available here&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.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem, QGraphicsRectItem, QGraphicsEllipseItem, QApplication
from PyQt6.QtGui import QBrush, QPen
from PyQt6.QtCore import Qt

app = QApplication(sys.argv)

# Defining a scene rect of 400x200, with it's origin at 0,0.
# If we don't set this on creation, we can set it later with .setSceneRect
scene = QGraphicsScene(0, 0, 400, 200)

# Draw a rectangle item, setting the dimensions.
rect = QGraphicsRectItem(0, 0, 200, 50)

# Set the origin (position) of the rectangle in the scene.
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.GlobalColor.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.GlobalColor.cyan)
pen.setWidth(10)
rect.setPen(pen)

ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
ellipse.setPos(75, 30)

brush = QBrush(Qt.GlobalColor.blue)
ellipse.setBrush(brush)

pen = QPen(Qt.GlobalColor.green)
pen.setWidth(5)
ellipse.setPen(pen)

# Add the items to the scene. Items are stacked in the order they are added.
scene.addItem(ellipse)
scene.addItem(rect)

ellipse.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the above example we've set &lt;code&gt;ItemIsMovable&lt;/code&gt; on the &lt;em&gt;ellipse&lt;/em&gt; only. You can drag the ellipse around the scene -- including behind the rectangle -- but the rectangle itself will remain locked in place. Experiment with adding more items and configuring the moveable status.&lt;/p&gt;
&lt;p&gt;If you want an item to be &lt;em&gt;selectable&lt;/em&gt; you can enable this by setting the &lt;code&gt;ItemIsSelectable&lt;/code&gt; flag, for example here using &lt;code&gt;.setFlags()&lt;/code&gt; to set multiple flags at the same 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;ellipse.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you click on the ellipse you'll now see it surrounded by a dashed line to indicate that it is selected. We'll look at how to use item selection in more detail in a later tutorial.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A selected QGraphicsItem in the scene, highlighted with dashed lines" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-two-items-selectable.png?tr=w-600 600w" loading="lazy" width="404" height="241"/&gt;
&lt;em&gt;A selected item in the scene, highlighted with dashed lines&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="alternative-way-to-create-qgraphicsitems"&gt;Alternative Way to Create QGraphicsItems&lt;/h2&gt;
&lt;p&gt;So far we've been creating items by creating the objects and &lt;em&gt;then&lt;/em&gt; adding them to the scene. But you can also create an object &lt;em&gt;in&lt;/em&gt; the scene directly by calling one of the helper methods on the scene itself, e.g. &lt;code&gt;scene.addEllipse()&lt;/code&gt;. This &lt;em&gt;creates&lt;/em&gt; the object and &lt;em&gt;returns&lt;/em&gt; it so you can modify it as before.&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 QGraphicsScene, QGraphicsView, QGraphicsRectItem, QApplication
from PyQt6.QtGui import QBrush, QPen
from PyQt6.QtCore import Qt
app = QApplication(sys.argv)

scene = QGraphicsScene(0, 0, 400, 200)

rect = scene.addRect(0, 0, 200, 50)
rect.setPos(50, 20)

# Define the brush (fill).
brush = QBrush(Qt.GlobalColor.red)
rect.setBrush(brush)

# Define the pen (line)
pen = QPen(Qt.GlobalColor.cyan)
pen.setWidth(10)
rect.setPen(pen)

view = QGraphicsView(scene)
view.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Feel free to use whichever form you find most comfortable in your code.&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 only use this approach for the built-in &lt;code&gt;QGraphicsItem&lt;/code&gt; object types.&lt;/p&gt;
&lt;h2 id="building-a-complex-scene-with-shapes-text-and-images"&gt;Building a Complex Scene with Shapes, Text, and Images&lt;/h2&gt;
&lt;p&gt;So far we've built a simple scene using the basic &lt;code&gt;QGraphicsRectItem&lt;/code&gt; and &lt;code&gt;QGraphicsEllipseItem&lt;/code&gt; shapes. Now let's use some other &lt;code&gt;QGraphicsItem&lt;/code&gt; objects to build a more complex scene, including lines, text and &lt;code&gt;QPixmap&lt;/code&gt; (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;from PyQt6.QtCore import QPointF, Qt
from PyQt6.QtWidgets import QGraphicsRectItem, QGraphicsScene, QGraphicsView, QApplication
from PyQt6.QtGui import QBrush, QPainter, QPen, QPixmap, QPolygonF
import sys

app = QApplication(sys.argv)

scene = QGraphicsScene(0, 0, 400, 200)

rectitem = QGraphicsRectItem(0, 0, 360, 20)
rectitem.setPos(20, 20)
rectitem.setBrush(QBrush(Qt.GlobalColor.red))
rectitem.setPen(QPen(Qt.GlobalColor.cyan))
scene.addItem(rectitem)

textitem = scene.addText("QGraphics is fun!")
textitem.setPos(100, 100)

scene.addPolygon(
    QPolygonF(
        [
            QPointF(30, 60),
            QPointF(270, 40),
            QPointF(400, 200),
            QPointF(20, 150),
        ]),
    QPen(Qt.GlobalColor.darkGreen),
)

pixmap = QPixmap("cat.jpg")
pixmapitem = scene.addPixmap(pixmap)
pixmapitem.setPos(250, 70)

view = QGraphicsView(scene)
view.setRenderHint(QPainter.RenderHint.Antialiasing)
view.show()

app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the example above you'll see the following scene.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene with multiple items including rectangle, polygon, text and pixmap" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;Scene with multiple items including a rectangle, polygon, text and a pixmap.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's step through the code looking at the interesting bits.&lt;/p&gt;
&lt;p&gt;Polygons are defined using a series of &lt;code&gt;QPointF&lt;/code&gt; objects which give the coordinates relative to the &lt;em&gt;item's&lt;/em&gt; position. So, for example if you create a polygon object with a point at 30, 20 and then move this polygon object X &amp;amp; Y coordinates 50, 40 then the point will be displayed at 80, 60 in the scene.&lt;/p&gt;
&lt;p&gt;Points inside an item are always relative to the item itself, and item coordinates are always relative to the scene -- or the item's parent, if it has one. We'll take a closer look at the Graphics View coordinate system in the next tutorial.&lt;/p&gt;
&lt;p&gt;To add an image to the scene we can open it from a file using &lt;code&gt;QPixmap()&lt;/code&gt;. This creates a &lt;code&gt;QPixmap&lt;/code&gt; object, which can then in turn be added to the scene using &lt;code&gt;scene.addPixmap(pixmap)&lt;/code&gt;. This returns a &lt;code&gt;QGraphicsPixmapItem&lt;/code&gt; which is the &lt;code&gt;QGraphicsItem&lt;/code&gt; type for the pixmap -- a wrapper that handles displaying the pixmap in the scene. You can use this object to perform any changes to the item in the scene.&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 multiple layers of objects can get confusing, so it's important to choose sensible variable names which make clear the distinction between, e.g. the &lt;em&gt;pixmap&lt;/em&gt; itself and the &lt;em&gt;pixmap item&lt;/em&gt; that contains it.&lt;/p&gt;
&lt;p&gt;Finally, we set the flag &lt;code&gt;RenderHint.Antialiasing&lt;/code&gt; on the view to &lt;em&gt;smooth&lt;/em&gt; the edges of diagonal lines. You almost &lt;em&gt;always&lt;/em&gt; want to enable this on your views as otherwise any rotated
objects will look very ugly indeed. Below is our scene without antialiasing enabled, you can see the jagged lines on the polygon.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QGraphicsScene without antialiasing &amp;mdash; jagged lines visible" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-multiple-items-noa.png?tr=w-600 600w" loading="lazy" width="505" height="292"/&gt;
&lt;em&gt;Scene with antialiasing disabled.&lt;/em&gt;&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;  Antialiasing has a (small) performance impact however, so if you are building scenes with millions of rotated items it may in some cases make sense to turn it off.&lt;/p&gt;
&lt;h2 id="adding-a-qgraphicsview-to-a-pyqt6-layout"&gt;Adding a QGraphicsView to a PyQt6 Layout&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QGraphicsView&lt;/code&gt; is subclassed from &lt;code&gt;QWidget&lt;/code&gt;, meaning it can be placed in layouts just like any other widget. In the following
example we add the view to a simple interface, with buttons which perform a basic effect on the view -- raising and lowering selected item's ZValue. This has the effect of allowing us to move items in front and behind other objects.&lt;/p&gt;
&lt;p&gt;The full code 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 sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QBrush, QPainter, QPen
from PyQt6.QtWidgets import (
    QApplication,
    QGraphicsEllipseItem,
    QGraphicsItem,
    QGraphicsRectItem,
    QGraphicsScene,
    QGraphicsView,
    QHBoxLayout,
    QPushButton,
    QSlider,
    QVBoxLayout,
    QWidget,
)


class Window(QWidget):
    def __init__(self):
        super().__init__()

        # Defining a scene rect of 400x200, with it's origin at 0,0.
        # If we don't set this on creation, we can set it later with .setSceneRect
        self.scene = QGraphicsScene(0, 0, 400, 200)

        # Draw a rectangle item, setting the dimensions.
        rect = QGraphicsRectItem(0, 0, 200, 50)
        rect.setPos(50, 20)
        brush = QBrush(Qt.GlobalColor.red)
        rect.setBrush(brush)

        # Define the pen (line)
        pen = QPen(Qt.GlobalColor.cyan)
        pen.setWidth(10)
        rect.setPen(pen)

        ellipse = QGraphicsEllipseItem(0, 0, 100, 100)
        ellipse.setPos(75, 30)

        brush = QBrush(Qt.GlobalColor.blue)
        ellipse.setBrush(brush)

        pen = QPen(Qt.GlobalColor.green)
        pen.setWidth(5)
        ellipse.setPen(pen)

        # Add the items to the scene. Items are stacked in the order they are added.
        self.scene.addItem(ellipse)
        self.scene.addItem(rect)

        # Set all items as moveable and selectable.
        for item in self.scene.items():
            item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
            item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)

        # Define our layout.
        vbox = QVBoxLayout()

        up = QPushButton("Up")
        up.clicked.connect(self.up)
        vbox.addWidget(up)

        down = QPushButton("Down")
        down.clicked.connect(self.down)
        vbox.addWidget(down)

        rotate = QSlider()
        rotate.setRange(0, 360)
        rotate.valueChanged.connect(self.rotate)
        vbox.addWidget(rotate)

        view = QGraphicsView(self.scene)
        view.setRenderHint(QPainter.RenderHint.Antialiasing)

        hbox = QHBoxLayout(self)
        hbox.addLayout(vbox)
        hbox.addWidget(view)

        self.setLayout(hbox)

    def up(self):
        """ Iterate all selected items in the view, moving them forward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z + 1)

    def down(self):
        """ Iterate all selected items in the view, moving them backward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z - 1)

    def rotate(self, value):
        """ Rotate the object by the received number of degrees. """
        items = self.scene.selectedItems()
        for item in items:
            item.setRotation(value)


app = QApplication(sys.argv)

w = Window()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this, you will get a window like that shown below. By selecting an item in the graphics view and then clicking either the "Up" or "Down" button you can move items up and down within the scene -- behind and in front of one another. The items are all moveable, so you can drag them around too. Clicking on the slider will rotate the currently selected items by the set number of degrees.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A QGraphicsView embedded in a PyQt6 layout with custom controls for z-ordering and rotation" src="https://www.pythonguis.com/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/introduction-to-qgraphics/scene-controls.png?tr=w-600 600w" loading="lazy" width="530" height="267"/&gt;
&lt;em&gt;A graphics scene with some custom controls&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The raising and lowering is handled by our custom methods &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt;, which work by iterating over the &lt;em&gt;currently selected&lt;/em&gt; items in the scene -- retrieved using &lt;code&gt;scene.selectedItems()&lt;/code&gt; and then getting the item's z value and increasing or decreasing it respectively.&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 up(self):
        """ Iterate all selected items in the view, moving them forward. """
        items = self.scene.selectedItems()
        for item in items:
            z = item.zValue()
            item.setZValue(z + 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;While rotation is handled using the &lt;code&gt;item.setRotation&lt;/code&gt; method. This receives the current angle from the &lt;code&gt;QSlider&lt;/code&gt; and again, applies it to any currently selected items in the scene.&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 rotate(self, value):
        """ Rotate the object by the received number of degrees. """
        items = self.scene.selectedItems()
        for item in items:
            item.setRotation(value)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Take a look at the &lt;a href="https://doc.qt.io/qt-5/qgraphicsitem.html"&gt;QGraphicsItem documentation&lt;/a&gt; for some other properties you can control with widgets and try extending the interface to allow you to change them dynamically.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this tutorial you've learned the fundamentals of PyQt6's QGraphics View framework, including how to create a &lt;code&gt;QGraphicsScene&lt;/code&gt;, add &lt;code&gt;QGraphicsItem&lt;/code&gt; objects like rectangles, ellipses, polygons, text and images, control stacking order with Z values, make items moveable and selectable, and embed a &lt;code&gt;QGraphicsView&lt;/code&gt; into a standard PyQt6 layout with interactive controls.&lt;/p&gt;
&lt;p&gt;Hopefully this introduction to the Qt Graphics View framework has given you some ideas of what you can do with it. In the next tutorials we'll look at how events and user interaction can be handled on items and how to create custom &amp;amp; compound items for your own scenes.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="pyqt"/><category term="pyqt6"/><category term="desktop-application"/><category term="qgraphics"/><category term="qgraphicsitem"/><category term="qgraphicsview"/><category term="qgraphicsscene"/><category term="vector-graphics"/><category term="2d-graphics"/><category term="qt"/><category term="qt6"/><category term="pyqt6-vector-graphics"/></entry><entry><title>Drag &amp; Drop Widgets with PySide6 — Sort widgets visually with drag and drop in a container</title><link href="https://www.pythonguis.com/faq/pyside6-drag-drop-widgets/" rel="alternate"/><published>2024-03-06T13:00:00+00:00</published><updated>2024-03-06T13:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2024-03-06:/faq/pyside6-drag-drop-widgets/</id><summary type="html">I had an interesting question from a reader of my &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 book&lt;/a&gt;, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.</summary><content type="html">
            &lt;p&gt;I had an interesting question from a reader of my &lt;a href="https://www.pythonguis.com/pyside6-book/"&gt;PySide6 book&lt;/a&gt;, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag &amp;amp; drop, exchanging the position of buttons, but I want to show the motion of &lt;code&gt;QPushButton&lt;/code&gt;, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question. By the end of this tutorial we'll have a generic, reusable drag and drop implementation which looks like the following.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stls0g003s0ak0b277ehqx/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;h2 id="drag-drop-widgets-in-pyside6"&gt;Drag &amp;amp; Drop Widgets in PySide6&lt;/h2&gt;
&lt;p&gt;We'll start with a simple application which creates a window using &lt;code&gt;QWidget&lt;/code&gt; and places a series of &lt;code&gt;QPushButton&lt;/code&gt; widgets into it.&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 substitute &lt;code&gt;QPushButton&lt;/code&gt; for any other widget you like, e.g. &lt;code&gt;QLabel&lt;/code&gt;. Any widget can have drag behavior implemented on it, although some input widgets will not work well as we capture the mouse events for the drag.&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.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = QPushButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)


app = QApplication([])
w = Window()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this you should see something like this.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 QPushButton widgets in a horizontal layout" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/windows-in-layout.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;The series of &lt;code&gt;QPushButton widgets&lt;/code&gt; in a horizontal layout.&lt;/em&gt;&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;  Here we're creating a window, but the &lt;code&gt;Window&lt;/code&gt; widget is subclassed from &lt;code&gt;QWidget&lt;/code&gt;, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget.&lt;/p&gt;
&lt;h3&gt;Making QPushButton Draggable with QDrag&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;QPushButton&lt;/code&gt; objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file.&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 QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We implement a &lt;code&gt;mouseMoveEvent&lt;/code&gt; which accepts the single &lt;code&gt;e&lt;/code&gt; parameter of the event. We check to see if the &lt;em&gt;left&lt;/em&gt; mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a &lt;code&gt;QDrag&lt;/code&gt; object, passing in &lt;code&gt;self&lt;/code&gt; to give us access later to the widget that was dragged. We also &lt;em&gt;must&lt;/em&gt; pass in &lt;code&gt;QMimeData&lt;/code&gt;. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty.&lt;/p&gt;
&lt;p&gt;Finally, we initiate a drag by calling &lt;code&gt;drag.exec(Qt.DropAction.MoveAction)&lt;/code&gt;. As with dialogs &lt;code&gt;exec()&lt;/code&gt; starts a new event loop, blocking the main loop until the drag is complete. The parameter &lt;code&gt;Qt.DropAction.MoveAction&lt;/code&gt; tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user.&lt;/p&gt;
&lt;p&gt;You can update the main window code to use our new &lt;code&gt;DragButton&lt;/code&gt; class 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;class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the code now, you &lt;em&gt;can&lt;/em&gt; drag the buttons, but you'll notice the drag is forbidden.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 drag forbidden &amp;mdash; drop not accepted" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-forbidden.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget starts but is forbidden.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What's happening? The mouse movement is being detected by our &lt;code&gt;DragButton&lt;/code&gt; object and the drag started, but the main window does not accept drag &amp;amp; drop.&lt;/p&gt;
&lt;h3&gt;Accepting Drops with dragEnterEvent&lt;/h3&gt;
&lt;p&gt;To fix this we need to enable drops on the window and implement &lt;code&gt;dragEnterEvent&lt;/code&gt; to actually accept 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;class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling &lt;code&gt;drag.exec()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 drag accepted showing move icon" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-accepted.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget starts and is accepted, showing a move icon.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Handling the Drop with dropEvent&lt;/h3&gt;
&lt;p&gt;Releasing the mouse button during a drag drop operation triggers a &lt;code&gt;dropEvent&lt;/code&gt; on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our &lt;code&gt;dropEvent&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The drop event contains the position the mouse was at when the button was released &amp;amp; the drop triggered. We can use this to determine where to move the widget to.&lt;/p&gt;
&lt;p&gt;To determine where to place the widget, we iterate over all the widgets in the layout, &lt;em&gt;until&lt;/em&gt; we find one who's &lt;code&gt;x&lt;/code&gt; position is &lt;em&gt;greater&lt;/em&gt; than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop.&lt;/p&gt;
&lt;p&gt;If we get to the end of the loop without finding a match, we must be dropping past the end of the existing items, so we increment &lt;code&gt;n&lt;/code&gt; one further (in the &lt;code&gt;else:&lt;/code&gt; block 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;    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x():
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The effect of this is that if you drag 1 pixel past the start of another widget the drop will happen to the right of it, which is a bit confusing. To fix this we can adjust the cut off to use the middle of the widget using &lt;code&gt;if pos.x() &amp;lt; w.x() + w.size().width() // 2:&lt;/code&gt; -- that is x + half of the width.&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, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Complete Drag &amp;amp; Drop Example&lt;/h3&gt;
&lt;p&gt;The complete working drag-drop code 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;from PySide6.QtCore import QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()


app = QApplication([])
w = Window()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="visual-drag-drop-with-qpixmap-preview"&gt;Visual Drag &amp;amp; Drop with QPixmap Preview&lt;/h2&gt;
&lt;p&gt;We now have a working drag &amp;amp; drop implementation. Next we'll move onto improving the UX by showing the drag visually. First we'll add support for showing the button being dragged next to the mouse pointer as it is dragged. That way the user knows exactly what it is they are dragging.&lt;/p&gt;
&lt;p&gt;Qt's &lt;code&gt;QDrag&lt;/code&gt; handler natively provides a mechanism for showing dragged objects which we can use. We can update our &lt;code&gt;DragButton&lt;/code&gt; class to pass a &lt;em&gt;pixmap&lt;/em&gt; image to &lt;code&gt;QDrag&lt;/code&gt; and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a &lt;code&gt;QPixmap&lt;/code&gt; of the widget we're dragging.&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 QMimeData, Qt
from PySide6.QtGui import QDrag
from PySide6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To create the pixmap we create a &lt;code&gt;QPixmap&lt;/code&gt; object passing in the size of the widget this event is fired on with &lt;code&gt;self.size()&lt;/code&gt;. This creates an empty &lt;em&gt;pixmap&lt;/em&gt; which we can then pass into &lt;code&gt;self.render&lt;/code&gt; to &lt;em&gt;render&lt;/em&gt; -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the &lt;code&gt;drag&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;If you run the code with this modification you'll see something like the following --&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 visual drag showing widget preview while dragging" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-visual.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget showing the dragged widget.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="building-a-generic-drag-drop-container-widget"&gt;Building a Generic Drag &amp;amp; Drop Container Widget&lt;/h2&gt;
&lt;p&gt;We now have a working drag and drop behavior implemented on our window.
We can take this a step further and implement a &lt;em&gt;generic&lt;/em&gt; drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget &lt;code&gt;DragWidget&lt;/code&gt; which can be added to any window.&lt;/p&gt;
&lt;p&gt;You can add &lt;em&gt;items&lt;/em&gt; -- instances of &lt;code&gt;DragItem&lt;/code&gt; -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal &lt;code&gt;orderChanged&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 QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = pos.y() &amp;lt; w.y() + w.size().height() // 2
            else:
                # Drag drop horizontally.
                drop_here = pos.x() &amp;lt; w.x() + w.size().width() // 2

            if drop_here:
                break

        else:
            # We aren't on the left hand/upper side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)
        self.orderChanged.emit(self.get_item_data())

        e.accept()

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Generic drag drop horizontal sorting in PySide6" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-generic-horizontal.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-600 600w" loading="lazy" width="306" height="139"/&gt;
&lt;em&gt;Generic drag-drop sorting in horizontal orientation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal &lt;code&gt;QLabel&lt;/code&gt; which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call &lt;code&gt;get_item_data&lt;/code&gt; yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort &lt;em&gt;anything&lt;/em&gt; not just strings.&lt;/p&gt;
&lt;p&gt;In the example above we're passing in the enumerated index as the data, so dragging will output (via the &lt;code&gt;print&lt;/code&gt; connected to &lt;code&gt;orderChanged&lt;/code&gt;) 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;[1, 0, 2, 3]
[1, 2, 0, 3]
[1, 0, 2, 3]
[1, 2, 0, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you remove the &lt;code&gt;item.set_data(n)&lt;/code&gt; you'll see the labels emitted on changes.&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;['B', 'A', 'C', 'D']
['B', 'C', 'A', 'D']
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've also implemented &lt;em&gt;orientation&lt;/em&gt; onto the &lt;code&gt;DragWidget&lt;/code&gt; using the Qt built in flags &lt;code&gt;Qt.Orientation.Vertical&lt;/code&gt; or &lt;code&gt;Qt.Orientation.Horizontal&lt;/code&gt;. Setting this allows you to sort items either vertically or horizontally -- the calculations are handled for both directions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Generic drag drop vertical sorting in PySide6" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-generic-vertical.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-600 600w" loading="lazy" width="202" height="216"/&gt;
&lt;em&gt;Generic drag-drop sorting in vertical orientation.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="adding-a-visual-drop-target-indicator"&gt;Adding a Visual Drop Target Indicator&lt;/h2&gt;
&lt;p&gt;If you experiment with the drag-drop tool above you'll notice that it doesn't feel completely intuitive. When dragging you don't know where an item will be
inserted until you drop it. If it ends up in the wrong place, you'll then need to pick it up and re-drop it again, using &lt;em&gt;guesswork&lt;/em&gt; to get it right.&lt;/p&gt;
&lt;p&gt;With a bit of practice you can get the hang of it, but it would be nicer to make the behavior immediately obvious for users. Many drag-drop interfaces solve this problem by showing a preview of where the item will be dropped while dragging -- either by showing the item in the place where it will be dropped, or showing some kind of placeholder.&lt;/p&gt;
&lt;p&gt;In this final section we'll implement this type of drag and drop preview indicator.&lt;/p&gt;
&lt;h3&gt;Creating the Drop Target Indicator Widget&lt;/h3&gt;
&lt;p&gt;The first step is to define our target indicator. This is just another label,
which in our example is empty, with custom styles applied to make it have a
solid "shadow" like background. This makes it obviously different to the
items in the list, so it stands out as something distinct.&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 QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )


&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;  We've copied the contents margins from the items in the list. If you change your list items, remember to also update the indicator dimensions to match.&lt;/p&gt;
&lt;h3&gt;Showing the Drop Target During Drag&lt;/h3&gt;
&lt;p&gt;The drag item is unchanged, but we need to implement some additional behavior on our &lt;code&gt;DragWidget&lt;/code&gt; to add the target, control showing and moving it.&lt;/p&gt;
&lt;p&gt;First we'll add the drag target indicator to the layout on our &lt;code&gt;DragWidget&lt;/code&gt;. This is hidden to begin with, but will be shown during the drag.&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 DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next we modify the &lt;code&gt;DragWidget.dragMoveEvent&lt;/code&gt; to show the drag target indicator. We show it by &lt;em&gt;inserting&lt;/em&gt; it into the layout and then calling &lt;code&gt;.show&lt;/code&gt; -- inserting a widget which is already in a layout will move it.
We also hide the original item which is being dragged.&lt;/p&gt;
&lt;p&gt;In the earlier examples we determined the position on drop by removing the widget being dragged, and then iterating over what is left. Because we now need to calculate the drop location before the drop, we take a different approach.&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 we wanted to do it the same way, we'd need to remove the item on drag start, hold onto it and implement re-inserting at it's old position on drag fail. That's a lot of work.&lt;/p&gt;
&lt;p&gt;Instead, the dragged item is left in place and hidden during move.&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 dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Calculating the Drop Location&lt;/h3&gt;
&lt;p&gt;The method &lt;code&gt;self._find_drop_location&lt;/code&gt; finds the index where the drag target will be shown (or the item dropped when the mouse is released). We'll implement that next.&lt;/p&gt;
&lt;p&gt;The calculation of the drop location follows the same pattern as before. We iterate over the items in the layout and calculate whether our mouse drop location is to the left of each widget. If it isn't to the left of any widget, we drop on the far right.&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 _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() &amp;gt;= w.y() - spacing
                    and pos.y() &amp;lt;= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() &amp;gt;= w.x() - spacing
                    and pos.x() &amp;lt;= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The drop location &lt;code&gt;n&lt;/code&gt; is returned for use in the &lt;code&gt;dragMoveEvent&lt;/code&gt; to place the drop target indicator.&lt;/p&gt;
&lt;h3&gt;Updating get_item_data to Ignore the Indicator&lt;/h3&gt;
&lt;p&gt;Next we need to update the &lt;code&gt;get_item_data&lt;/code&gt; handler to ignore the drop target indicator. To do this we check &lt;code&gt;w&lt;/code&gt; against &lt;code&gt;self._drag_target_indicator&lt;/code&gt; and skip if it is the same. With this change the method will work as expected.&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 get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Handling dragLeaveEvent&lt;/h3&gt;
&lt;p&gt;If you run the code at this point the drag behavior will work as expected. But if you drag the widget outside of the window and drop you'll notice a problem: the target indicator will stay in place, but dropping the item won't drop the item in that position (the drop will be cancelled).&lt;/p&gt;
&lt;p&gt;To fix that we need to implement a &lt;code&gt;dragLeaveEvent&lt;/code&gt; which hides the indicator.&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 dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Complete Drag &amp;amp; Drop with Visual Drop Indicator&lt;/h3&gt;
&lt;p&gt;With those changes, the drag-drop behavior should be working as intended.
The complete code 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;from PySide6.QtCore import QMimeData, Qt, Signal
from PySide6.QtGui import QDrag, QPixmap
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = Signal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()

    def dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()

    def dropEvent(self, e):
        widget = e.source()
        # Use drop target location for destination, then remove it.
        self._drag_target_indicator.hide()
        index = self.blayout.indexOf(self._drag_target_indicator)
        if index is not None:
            self.blayout.insertWidget(index, widget)
            self.orderChanged.emit(self.get_item_data())
            widget.show()
            self.blayout.activate()
        e.accept()

    def _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() &amp;gt;= w.y() - spacing
                    and pos.y() &amp;lt;= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() &amp;gt;= w.x() - spacing
                    and pos.x() &amp;lt;= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Fixing Blurry QPixmap Drag Preview on macOS Retina Displays&lt;/h3&gt;
&lt;p&gt;If you run this example on macOS you may notice that the widget drag preview (the &lt;code&gt;QPixmap&lt;/code&gt; created on &lt;code&gt;DragItem&lt;/code&gt;) is a bit blurry. On high-resolution screens you need to set the &lt;em&gt;device pixel ratio&lt;/em&gt; and scale up the pixmap when
you create it. Below is a modified &lt;code&gt;DragItem&lt;/code&gt; class which does this.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;DragItem&lt;/code&gt; to support high resolution screens.&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 DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            # Render at x2 pixel ratio to avoid blur on Retina screens.
            pixmap = QPixmap(self.size().width() * 2, self.size().height() * 2)
            pixmap.setDevicePixelRatio(2)
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That's it! We've created a generic drag-drop widget which can be added to any PySide6 project where you need to be able to reorder items within a list. You should feel free to experiment with the styling of the drag items and targets as this won't affect the behavior.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stls0g003s0ak0b277ehqx/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&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.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="drag-drop"/><category term="widgets"/><category term="pyside"/><category term="pyside6"/><category term="drag"/><category term="qdrag"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Drag &amp; Drop Widgets with PyQt6 — Sort widgets visually with drag and drop in a container</title><link href="https://www.pythonguis.com/faq/pyqt6-drag-drop-widgets/" rel="alternate"/><published>2024-02-07T13:00:00+00:00</published><updated>2024-02-07T13:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2024-02-07:/faq/pyqt6-drag-drop-widgets/</id><summary type="html">I had an interesting question from a reader of my &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 book&lt;/a&gt;, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.</summary><content type="html">
            &lt;p&gt;I had an interesting question from a reader of my &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;PyQt6 book&lt;/a&gt;, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag &amp;amp; drop, exchanging the position of buttons, but I want to show the motion of &lt;code&gt;QPushButton&lt;/code&gt;, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question. By the end of this tutorial we'll have a generic drag and drop implementation which looks like the following.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stls0g003s0ak0b277ehqx/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;h2 id="setting-up-drag-drop-widgets-in-pyqt6"&gt;Setting Up Drag &amp;amp; Drop Widgets in PyQt6&lt;/h2&gt;
&lt;p&gt;We'll start with a simple application which creates a window using &lt;code&gt;QWidget&lt;/code&gt; and places a series of &lt;code&gt;QPushButton&lt;/code&gt; widgets into it.&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 substitute &lt;code&gt;QPushButton&lt;/code&gt; for any other widget you like, e.g. &lt;code&gt;QLabel&lt;/code&gt;. Any widget can have drag behavior implemented on it, although some input widgets will not work well as we capture the mouse events for the drag.&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, QHBoxLayout, QPushButton, QWidget


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = QPushButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)


app = QApplication([])
w = Window()
w.show()

app.exec()


&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this you should see something like this.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QPushButton widgets in a horizontal layout" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/windows-in-layout.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/windows-in-layout.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;The series of &lt;code&gt;QPushButton widgets&lt;/code&gt; in a horizontal layout.&lt;/em&gt;&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;  Here we're creating a window, but the &lt;code&gt;Window&lt;/code&gt; widget is subclassed from &lt;code&gt;QWidget&lt;/code&gt;, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget.&lt;/p&gt;
&lt;h2 id="making-qpushbutton-draggable-with-qdrag"&gt;Making QPushButton Draggable with QDrag&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;QPushButton&lt;/code&gt; objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file.&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 QMimeData, Qt
from PyQt6.QtGui import QDrag
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We implement a &lt;code&gt;mouseMoveEvent&lt;/code&gt; which accepts the single &lt;code&gt;e&lt;/code&gt; parameter of the event. We check to see if the &lt;em&gt;left&lt;/em&gt; mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a &lt;code&gt;QDrag&lt;/code&gt; object, passing in &lt;code&gt;self&lt;/code&gt; to give us access later to the widget that was dragged. We also &lt;em&gt;must&lt;/em&gt; pass in mime data. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty.&lt;/p&gt;
&lt;p&gt;Finally, we initiate a drag by calling &lt;code&gt;drag.exec_(Qt.MoveAction)&lt;/code&gt;. As with dialogs &lt;code&gt;exec_()&lt;/code&gt; starts a new event loop, blocking the main loop until the drag is complete. The parameter &lt;code&gt;Qt.MoveAction&lt;/code&gt; tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user.&lt;/p&gt;
&lt;p&gt;You can update the main window code to use our new &lt;code&gt;DragButton&lt;/code&gt; class 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;class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run the code now, you &lt;em&gt;can&lt;/em&gt; drag the buttons, but you'll notice the drag is forbidden.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyQt6 drag forbidden icon on widget" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-forbidden.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-forbidden.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget starts but is forbidden.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What's happening? The mouse movement is being detected by our &lt;code&gt;DragButton&lt;/code&gt; object and the drag started, but the main window does not accept drag &amp;amp; drop.&lt;/p&gt;
&lt;h2 id="accepting-drops-with-dragenterevent-and-dropevent"&gt;Accepting Drops with dragEnterEvent and dropEvent&lt;/h2&gt;
&lt;p&gt;To fix the forbidden drag icon we need to enable drops on the window and implement &lt;code&gt;dragEnterEvent&lt;/code&gt; to actually accept 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;class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling &lt;code&gt;drag.exec_()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyQt6 drag accepted with move icon" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-accepted.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-accepted.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget starts and is accepted, showing a move icon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Releasing the mouse button during a drag drop operation triggers a &lt;code&gt;dropEvent&lt;/code&gt; on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our &lt;code&gt;dropEvent&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The drop event contains the position the mouse was at when the button was released &amp;amp; the drop triggered. We can use this to determine where to move the widget to.&lt;/p&gt;
&lt;h2 id="calculating-the-drop-position-in-the-layout"&gt;Calculating the Drop Position in the Layout&lt;/h2&gt;
&lt;p&gt;To determine where to place the widget, we iterate over all the widgets in the layout, &lt;em&gt;until&lt;/em&gt; we find one who's &lt;code&gt;x&lt;/code&gt; position is &lt;em&gt;greater&lt;/em&gt; than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop.&lt;/p&gt;
&lt;p&gt;If we get to the end of the loop without finding a match, we must be dropping past the end of the existing items, so we increment &lt;code&gt;n&lt;/code&gt; one further (in the &lt;code&gt;else:&lt;/code&gt; block 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;    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x():
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1
        self.blayout.insertWidget(n, widget)

        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The effect of this is that if you drag 1 pixel past the start of another widget the drop will happen to the right of it, which is a bit confusing. To fix this we can adjust the cut off to use the middle of the widget using &lt;code&gt;if pos.x() &amp;lt; w.x() + w.size().width() // 2:&lt;/code&gt; -- that is x + half of the width.&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, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1
        self.blayout.insertWidget(n, widget)

        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The complete working drag and drop code 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;from PyQt6.QtCore import QMimeData, Qt
from PyQt6.QtGui import QDrag
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec(Qt.DropAction.MoveAction)


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        self.blayout = QHBoxLayout()
        for l in ["A", "B", "C", "D"]:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() &amp;lt; w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                break
        else:
            # We aren't on the left hand side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)

        e.accept()


app = QApplication([])
w = Window()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="visual-drag-drop-with-qpixmap-preview"&gt;Visual Drag &amp;amp; Drop with QPixmap Preview&lt;/h2&gt;
&lt;p&gt;We now have a working drag &amp;amp; drop implementation. Next we'll move onto improving the UX by showing the drag visually. First we'll add support for showing the button being dragged next to the mouse point as it is dragged. That way the user knows exactly what it is they are dragging.&lt;/p&gt;
&lt;p&gt;Qt's &lt;code&gt;QDrag&lt;/code&gt; handler natively provides a mechanism for showing dragged objects which we can use. We can update our &lt;code&gt;DragButton&lt;/code&gt; class to pass a &lt;em&gt;pixmap&lt;/em&gt; image to &lt;code&gt;QDrag&lt;/code&gt; and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a &lt;code&gt;QPixmap&lt;/code&gt; of the widget we're dragging.&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 QMimeData, Qt
from PyQt6.QtGui import QDrag, QPixmap
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget


class DragButton(QPushButton):
    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To create the pixmap we create a &lt;code&gt;QPixmap&lt;/code&gt; object passing in the size of the widget this event is fired on with &lt;code&gt;self.size()&lt;/code&gt;. This creates an empty &lt;em&gt;pixmap&lt;/em&gt; which we can then pass into &lt;code&gt;self.render&lt;/code&gt; to &lt;em&gt;render&lt;/em&gt; -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the &lt;code&gt;drag&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;If you run the code with this modification you'll see something like the following --&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyQt6 drag with visual widget preview" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-visual.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-visual.png?tr=w-600 600w" loading="lazy" width="421" height="93"/&gt;
&lt;em&gt;Dragging of the widget showing the dragged widget as a QPixmap preview.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="building-a-generic-drag-drop-sortable-container"&gt;Building a Generic Drag &amp;amp; Drop Sortable Container&lt;/h2&gt;
&lt;p&gt;We now have a working drag and drop behavior implemented on our window.
We can take this a step further and implement a &lt;em&gt;generic&lt;/em&gt; drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget &lt;code&gt;DragWidget&lt;/code&gt; which can be added to any window.&lt;/p&gt;
&lt;p&gt;You can add &lt;em&gt;items&lt;/em&gt; -- instances of &lt;code&gt;DragItem&lt;/code&gt; -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal &lt;code&gt;orderChanged&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.QtCore import QMimeData, Qt, pyqtSignal
from PyQt6.QtGui import QDrag, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = pyqtSignal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.position()
        widget = e.source()
        self.blayout.removeWidget(widget)

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = pos.y() &amp;lt; w.y() + w.size().height() // 2
            else:
                # Drag drop horizontally.
                drop_here = pos.x() &amp;lt; w.x() + w.size().width() // 2

            if drop_here:
                break

        else:
            # We aren't on the left hand/upper side of any widget,
            # so we're at the end. Increment 1 to insert after.
            n += 1

        self.blayout.insertWidget(n, widget)
        self.orderChanged.emit(self.get_item_data())

        e.accept()

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="Generic drag and drop sorting in horizontal orientation with PyQt6" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-generic-horizontal.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-horizontal.png?tr=w-600 600w" loading="lazy" width="306" height="139"/&gt;
&lt;em&gt;Generic drag-drop sorting in horizontal orientation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal &lt;code&gt;QLabel&lt;/code&gt; which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call &lt;code&gt;get_item_data&lt;/code&gt; yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort &lt;em&gt;anything&lt;/em&gt; not just strings.&lt;/p&gt;
&lt;p&gt;In the example above we're passing in the enumerated index as the data, so dragging will output (via the &lt;code&gt;print&lt;/code&gt; connected to &lt;code&gt;orderChanged&lt;/code&gt;) 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;[1, 0, 2, 3]
[1, 2, 0, 3]
[1, 0, 2, 3]
[1, 2, 0, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you remove the &lt;code&gt;item.set_data(n)&lt;/code&gt; you'll see the labels emitted on changes.&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;['B', 'A', 'C', 'D']
['B', 'C', 'A', 'D']
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've also implemented &lt;em&gt;orientation&lt;/em&gt; onto the &lt;code&gt;DragWidget&lt;/code&gt; using the Qt built in flags &lt;code&gt;Qt.Orientation.Vertical&lt;/code&gt; or &lt;code&gt;Qt.Orientation.Horizontal&lt;/code&gt;. Setting this allows you to sort items either vertically or horizontally -- the calculations are handled for both directions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Generic drag and drop sorting in vertical orientation with PyQt6" src="https://www.pythonguis.com/static/faq/drag-drop-widgets/drag-generic-vertical.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/drag-drop-widgets/drag-generic-vertical.png?tr=w-600 600w" loading="lazy" width="202" height="216"/&gt;
&lt;em&gt;Generic drag-drop sorting in vertical orientation.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="adding-a-visual-drop-target-indicator"&gt;Adding a Visual Drop Target Indicator&lt;/h2&gt;
&lt;p&gt;If you experiment with the drag-drop tool above you'll notice that it doesn't feel completely intuitive. When dragging you don't know where an item will be
inserted until you drop it. If it ends up in the wrong place, you'll then need to pick it up and re-drop it again, using &lt;em&gt;guesswork&lt;/em&gt; to get it right.&lt;/p&gt;
&lt;p&gt;With a bit of practice you can get the hang of it, but it would be nicer to make the behavior immediately obvious for users. Many drag-drop interfaces solve this problem by showing a preview of where the item will be dropped while dragging -- either by showing the item in the place where it will be dropped, or showing some kind of placeholder.&lt;/p&gt;
&lt;p&gt;In this final section we'll implement this type of drag and drop preview indicator.&lt;/p&gt;
&lt;h3&gt;Creating the DragTargetIndicator Widget&lt;/h3&gt;
&lt;p&gt;The first step is to define our target indicator. This is just another label,
which in our example is empty, with custom styles applied to make it have a
solid "shadow" like background. This makes it obviously different to the
items in the list, so it stands out as something distinct.&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 QMimeData, Qt, pyqtSignal
from PyQt6.QtGui import QDrag, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )


&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;  We've copied the contents margins from the items in the list. If you change your list items, remember to also update the indicator dimensions to match.&lt;/p&gt;
&lt;h3&gt;Showing and Moving the Drop Indicator During Drag&lt;/h3&gt;
&lt;p&gt;The drag item is unchanged, but we need to implement some additional behavior on our &lt;code&gt;DragWidget&lt;/code&gt; to add the target, control showing and moving it.&lt;/p&gt;
&lt;p&gt;First we'll add the drag target indicator to the layout on our &lt;code&gt;DragWidget&lt;/code&gt;. This is hidden to begin with, but will be shown during the drag.&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 DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = pyqtSignal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next we modify the &lt;code&gt;DragWidget.dragMoveEvent&lt;/code&gt; to show the drag target indicator. We show it by &lt;em&gt;inserting&lt;/em&gt; it into the layout and then calling &lt;code&gt;.show&lt;/code&gt; -- inserting a widget which is already in a layout will move it.
We also hide the original item which is being dragged.&lt;/p&gt;
&lt;p&gt;In the earlier examples we determined the position on drop by removing the widget being dragged, and then iterating over what is left. Because we now need to calculate the drop location before the drop, we take a different approach.&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 we wanted to do it the same way, we'd need to remove the item on drag start, hold onto it and implement re-inserting at it's old position on drag fail. That's a lot of work.&lt;/p&gt;
&lt;p&gt;Instead, the dragged item is left in place and hidden during move.&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 dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The method &lt;code&gt;self._find_drop_location&lt;/code&gt; finds the index where the drag target will be shown (or the item dropped when the mouse released). We'll implement that next.&lt;/p&gt;
&lt;h3&gt;Implementing the Drop Location Calculation&lt;/h3&gt;
&lt;p&gt;The calculation of the drop location follows the same pattern as before. We iterate over the items in the layout and calculate whether our mouse drop location is to the left of each widget. If it isn't to the left of any widget, we drop on the far right.&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 _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() &amp;gt;= w.y() - spacing
                    and pos.y() &amp;lt;= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() &amp;gt;= w.x() - spacing
                    and pos.x() &amp;lt;= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The drop location &lt;code&gt;n&lt;/code&gt; is returned for use in the &lt;code&gt;dragMoveEvent&lt;/code&gt; to place the drop target indicator.&lt;/p&gt;
&lt;p&gt;Next we need to update the &lt;code&gt;get_item_data&lt;/code&gt; handler to ignore the drop target indicator. To do this we check &lt;code&gt;w&lt;/code&gt; against &lt;code&gt;self._drag_target_indicator&lt;/code&gt; and skip if it is the same. With this change the method will work as expected.&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 get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Handling dragLeaveEvent to Clean Up the Indicator&lt;/h3&gt;
&lt;p&gt;If you run the code at this point the drag behavior will work as expected. But if you drag the widget outside of the window and drop you'll notice a problem: the target indicator will stay in place, but dropping the item won't drop the item in that position (the drop will be cancelled).&lt;/p&gt;
&lt;p&gt;To fix that we need to implement a &lt;code&gt;dragLeaveEvent&lt;/code&gt; which hides the indicator.&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 dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Complete Drag &amp;amp; Drop with Visual Indicator&lt;/h3&gt;
&lt;p&gt;With those changes, the drag-drop behavior should be working as intended.
The complete code 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;from PyQt6.QtCore import QMimeData, Qt, pyqtSignal
from PyQt6.QtGui import QDrag, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class DragTargetIndicator(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setContentsMargins(25, 5, 25, 5)
        self.setStyleSheet(
            "QLabel { background-color: #ccc; border: 1px solid black; }"
        )


class DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = pyqtSignal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        # Add the drag target indicator. This is invisible by default,
        # we show it and move it around while the drag is active.
        self._drag_target_indicator = DragTargetIndicator()
        self.blayout.addWidget(self._drag_target_indicator)
        self._drag_target_indicator.hide()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dragLeaveEvent(self, e):
        self._drag_target_indicator.hide()
        e.accept()

    def dragMoveEvent(self, e):
        # Find the correct location of the drop target, so we can move it there.
        index = self._find_drop_location(e)
        if index is not None:
            # Inserting moves the item if its alreaady in the layout.
            self.blayout.insertWidget(index, self._drag_target_indicator)
            # Hide the item being dragged.
            e.source().hide()
            # Show the target.
            self._drag_target_indicator.show()
        e.accept()

    def dropEvent(self, e):
        widget = e.source()
        # Use drop target location for destination, then remove it.
        self._drag_target_indicator.hide()
        index = self.blayout.indexOf(self._drag_target_indicator)
        if index is not None:
            self.blayout.insertWidget(index, widget)
            self.orderChanged.emit(self.get_item_data())
            widget.show()
            self.blayout.activate()
        e.accept()

    def _find_drop_location(self, e):
        pos = e.position()
        spacing = self.blayout.spacing() / 2

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()

            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = (
                    pos.y() &amp;gt;= w.y() - spacing
                    and pos.y() &amp;lt;= w.y() + w.size().height() + spacing
                )
            else:
                # Drag drop horizontally.
                drop_here = (
                    pos.x() &amp;gt;= w.x() - spacing
                    and pos.x() &amp;lt;= w.x() + w.size().width() + spacing
                )

            if drop_here:
                # Drop over this target.
                break

        return n

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if w != self._drag_target_indicator:
                # The target indicator has no data.
                data.append(w.data)
        return data


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(["A", "B", "C", "D"]):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec()

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="fixing-blurry-drag-previews-on-retinahigh-dpi-screens"&gt;Fixing Blurry Drag Previews on Retina/High-DPI Screens&lt;/h2&gt;
&lt;p&gt;If you run this example on macOS you may notice that the widget drag preview (the &lt;code&gt;QPixmap&lt;/code&gt; created on &lt;code&gt;DragItem&lt;/code&gt;) is a bit blurry. On high-resolution screens you need to set the &lt;em&gt;device pixel ratio&lt;/em&gt; and scale up the pixmap when
you create it. Below is a modified &lt;code&gt;DragItem&lt;/code&gt; class which fixes blurry drag previews on Retina and HiDPI displays.&lt;/p&gt;
&lt;p&gt;Update &lt;code&gt;DragItem&lt;/code&gt; to support high resolution screens.&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 DragItem(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):
        if e.buttons() == Qt.MouseButton.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            # Render at x2 pixel ratio to avoid blur on Retina screens.
            pixmap = QPixmap(self.size().width() * 2, self.size().height() * 2)
            pixmap.setDevicePixelRatio(2)
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec(Qt.DropAction.MoveAction)
            self.show() # Show this widget again, if it's dropped outside.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That's it! We've created a generic drag and drop handler which can be added to any projects where you need to be able to reposition items within a list. You should feel free to experiment with the styling of the drag items and targets as this won't affect the behavior.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stls0g003s0ak0b277ehqx/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="drag-drop"/><category term="widgets"/><category term="pyqt"/><category term="pyqt6"/><category term="drag"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>QLineEdit — A Complete Guide to Single-Line Text Input in PyQt/PySide</title><link href="https://www.pythonguis.com/docs/qlineedit/" rel="alternate"/><published>2024-02-05T06:00:00+00:00</published><updated>2024-02-05T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2024-02-05:/docs/qlineedit/</id><summary type="html">The &lt;code&gt;QLineEdit&lt;/code&gt; class is a versatile tool for single-line text input in PyQt and PySide applications. The widget facilitates text manipulation by supporting insertion, deletion, selection, and cut-copy-paste operations natively. You can use line edits when you need to accept text input from your users in a PyQt/PySide application.</summary><content type="html">
            &lt;p&gt;The &lt;code&gt;QLineEdit&lt;/code&gt; class is a versatile tool for single-line text input in PyQt and PySide applications. The widget facilitates text manipulation by supporting insertion, deletion, selection, and cut-copy-paste operations natively. You can use line edits when you need to accept text input from your users in a PyQt/PySide application.&lt;/p&gt;
&lt;p&gt;The widget is highly customizable. You can set it up to include placeholder text, input masks, input validators, and more. It also supports many keyboard shortcuts out of the box and is able to process custom ones. This feature opens an opportunity to enhance user input speed and efficiency.&lt;/p&gt;
&lt;p&gt;In this tutorial, you will learn the basics of using &lt;code&gt;QLineEdit&lt;/code&gt; by going through its most commonly used features and capabilities, including creating line edits, manipulating text, aligning content, connecting signals and slots, and validating user input.&lt;/p&gt;
&lt;h2 id="creating-line-edit-widgets-with-qlineedit"&gt;Creating Line Edit Widgets With &lt;code&gt;QLineEdit&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;A line edit allows the user to enter and edit a single line of plain text with a useful collection of editing functionalities, such as insertion, deletion, selection, cut-copy-paste, and drag-and-drop operations.&lt;/p&gt;
&lt;p&gt;If you've used &lt;a href="https://www.pythonguis.com/pyqt6/"&gt;PyQt&lt;/a&gt; or &lt;a href="https://www.pythonguis.com/pyside6/"&gt;PySide&lt;/a&gt; to create GUI applications in Python, then it'd be likely that you already know about the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html"&gt;&lt;code&gt;QLineEdit&lt;/code&gt;&lt;/a&gt; class. This class allows you to create line edits in your graphical interfaces (GUI) quickly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;QLineEdit&lt;/code&gt; class provides two different constructors that you can use to create line edits according to your needs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constructor&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;a href="https://doc.qt.io/qt-6/qlineedit.html#QLineEdit"&gt;&lt;code&gt;QLineEdit(parent: QWidget = None)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Constructs a line edit with a parent widget but without text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#QLineEdit-1"&gt;&lt;code&gt;QLineEdit(text: str, parent: QWidget = None)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Constructs a line edit with default text and a parent widget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The parent widget would be the window or dialog where you need to place the line edit. The text can be a default text that will appear in the line edit when you run the application.&lt;/p&gt;
&lt;p&gt;To illustrate how to use the above constructors, we can code a demo 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 PyQt6.QtWidgets import QApplication, QLineEdit, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("QLineEdit Constructors")
        self.resize(300, 100)
        # Line edit with a parent widget
        top_line_edit = QLineEdit(parent=self)
        # Line edit with a parent widget and a default text
        bottom_line_edit = QLineEdit(
            "Hello! This is a line edit.", parent=self
        )

        layout = QVBoxLayout()
        layout.addWidget(top_line_edit)
        layout.addWidget(bottom_line_edit)
        self.setLayout(layout)

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we first do the required imports and then define the &lt;code&gt;Window&lt;/code&gt; class inheriting from &lt;code&gt;QWidget&lt;/code&gt;. Inside &lt;code&gt;Window&lt;/code&gt;, we create two &lt;code&gt;QLineEdit&lt;/code&gt; widgets.&lt;/p&gt;
&lt;p&gt;To create the first line edit, we use the first constructor of &lt;code&gt;QLineEdit&lt;/code&gt;, passing only a &lt;code&gt;parent&lt;/code&gt; widget. For the second line editor, we use the second constructor, which requires the parent widget and a default text. Note that the text is a regular Python string.&lt;/p&gt;
&lt;p&gt;Save the code to a file called &lt;code&gt;constructors.py&lt;/code&gt; file and run it from your command line. You'll get a window that looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit constructors example showing two text input fields in a PyQt6 window" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-constructors.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-constructors.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-constructors.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-constructors.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-constructors.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;Standard window showing our two line edits.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first line edit has no text. In most cases, this is how you would create your line edits because they're designed for accepting input. If you'd like to just display some text, then you can use a &lt;code&gt;QLabel&lt;/code&gt; widget instead. The second line edit displays the text that you passed to the constructor.&lt;/p&gt;
&lt;p&gt;Both line edits are ready for accepting input text. Note that you can use all the following keyboard shortcuts to optimize your text input process:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align="left"&gt;Keys&lt;/th&gt;
&lt;th align="left"&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Left Arrow&lt;/td&gt;
&lt;td align="left"&gt;Moves the cursor one character to the left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Shift+Left Arrow&lt;/td&gt;
&lt;td align="left"&gt;Moves and selects text one character to the left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Right Arrow&lt;/td&gt;
&lt;td align="left"&gt;Moves the cursor one character to the right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Shift+Right Arrow&lt;/td&gt;
&lt;td align="left"&gt;Moves and selects text one character to the right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Home&lt;/td&gt;
&lt;td align="left"&gt;Moves the cursor to the beginning of the line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;End&lt;/td&gt;
&lt;td align="left"&gt;Moves the cursor to the end of the line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Backspace&lt;/td&gt;
&lt;td align="left"&gt;Deletes the character to the left of the cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+Backspace&lt;/td&gt;
&lt;td align="left"&gt;Deletes the word to the left of the cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Delete&lt;/td&gt;
&lt;td align="left"&gt;Deletes the character to the right of the cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+Delete&lt;/td&gt;
&lt;td align="left"&gt;Deletes the word to the right of the cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+A&lt;/td&gt;
&lt;td align="left"&gt;Select all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+C&lt;/td&gt;
&lt;td align="left"&gt;Copies the selected text to the clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+Insert&lt;/td&gt;
&lt;td align="left"&gt;Copies the selected text to the clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+K&lt;/td&gt;
&lt;td align="left"&gt;Deletes to the end of the line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+V&lt;/td&gt;
&lt;td align="left"&gt;Pastes the clipboard text into the line edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Shift+Insert&lt;/td&gt;
&lt;td align="left"&gt;Pastes the clipboard text into the line edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+X&lt;/td&gt;
&lt;td align="left"&gt;Deletes the selected text and copies it to the clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Shift+Delete&lt;/td&gt;
&lt;td align="left"&gt;Deletes the selected text and copies it to the clipboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+Z&lt;/td&gt;
&lt;td align="left"&gt;Undoes the last operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align="left"&gt;Ctrl+Y&lt;/td&gt;
&lt;td align="left"&gt;Redoes the last undone operation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That's a lot of shortcuts! This table is just a sample of all the features that the &lt;code&gt;QLineEdit&lt;/code&gt; class provides out of the box.&lt;/p&gt;
&lt;p&gt;In addition to these keyboard shortcuts, the &lt;code&gt;QLineEdit&lt;/code&gt; class provides a context menu that you can trigger by clicking on a line edit using the right button of your mouse:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit right-click context menu with cut, copy, paste, and undo options" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-context-menu.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-context-menu.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-context-menu.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-context-menu.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-context-menu.png?tr=w-600 600w" loading="lazy" width="662" height="592"/&gt;
&lt;em&gt;QLineEdit with a context menu visible.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The built-in context menu provides basic edition options, such as cut, copy, paste, and delete. It also has options for undoing and redoing edits, and for selecting all the content of a given line edit.&lt;/p&gt;
&lt;h3&gt;Creating Read-Only Line Edits&lt;/h3&gt;
&lt;p&gt;Sometimes, we need to make a line edit non-editable. By default, all line edits are editable because their main use case is to provide text input for the user. However, in some situations, a read-only line edit can be useful.&lt;/p&gt;
&lt;p&gt;For example, say that you're creating a GUI application that generates some recovery keys for the users. The user must be able to copy the key to a safe place so that they can use it to recover access if they forget their password. In this situation, creating a read-only line edit can provide a suitable solution.&lt;/p&gt;
&lt;p&gt;To make a line edit read-only, we can use the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#readOnly-prop"&gt;&lt;code&gt;readOnly&lt;/code&gt;&lt;/a&gt; property and its setter method &lt;code&gt;setReadOnly()&lt;/code&gt; as in the following 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 secrets

from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QLineEdit,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Non-editable QLineEdit")
        self.resize(300, 100)
        non_editable_line_edit = QLineEdit(parent=self)
        non_editable_line_edit.setReadOnly(True)
        non_editable_line_edit.setText(secrets.token_hex(16))

        layout = QVBoxLayout()
        layout.addWidget(QLabel(parent=self, text="Your secret key:"))
        layout.addWidget(non_editable_line_edit)
        self.setLayout(layout)

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create a read-only line edit by using the &lt;code&gt;setReadOnly()&lt;/code&gt; method. When we set the &lt;code&gt;readOnly&lt;/code&gt; property to &lt;code&gt;True&lt;/code&gt;, our line edit won't accept editions. It'll only allow us to select and copy its content.&lt;/p&gt;
&lt;p&gt;Go ahead and run the application from your command line to explore how this line edit works. You'll get a window like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Non-editable read-only QLineEdit displaying a secret key in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/non-editable-lineedit.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/non-editable-lineedit.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/non-editable-lineedit.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/non-editable-lineedit.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/non-editable-lineedit.png?tr=w-600 600w" loading="lazy" width="662" height="424"/&gt;
&lt;em&gt;A read-only line edit with editing disabled.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you play a bit with this line edit, you'll soon discover that you can't change its text. You'll also note that the options in the context menu are now limited to &lt;em&gt;Copy&lt;/em&gt; and &lt;em&gt;Select All&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Creating Password Input Fields With QLineEdit&lt;/h3&gt;
&lt;p&gt;Another cool feature of the &lt;code&gt;QLineEdit&lt;/code&gt; class is that it allows you to create text input for passwords. This can be pretty convenient for those applications that manage several users, and each user needs to have access credentials.&lt;/p&gt;
&lt;p&gt;You can create line edits for passwords by using the &lt;a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/api/qtwidgets/qlineedit.html#echoMode"&gt;&lt;code&gt;echoMode()&lt;/code&gt;&lt;/a&gt; method. This method takes one of the following constants as an argument:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constant&lt;/th&gt;
&lt;th&gt;Value&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;QLineEdit.EchoMode.Normal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display characters as you enter them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QLineEdit.EchoMode.NoEcho&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display no characters when you enter them.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QLineEdit.EchoMode.Password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display platform-dependent password mask characters instead of the characters you enter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QLineEdit.EchoMode.PasswordEchoOnEdit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display characters as you enter them while editing. Display characters as the &lt;code&gt;Password&lt;/code&gt; mode does when reading.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;Normal&lt;/code&gt; mode is the default. The &lt;code&gt;NoEcho&lt;/code&gt; mode may be appropriate for critical passwords where even the length of the password should be kept secret.
The &lt;code&gt;Password&lt;/code&gt; mode is appropriate for most password use cases, however &lt;code&gt;PasswordEchoOnEdit&lt;/code&gt; can be used instead if you need to give users some confirmation of what they are typing.&lt;/p&gt;
&lt;p&gt;Here's a sample app that shows a user and password 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 PyQt6.QtWidgets import (
    QApplication,
    QFormLayout,
    QLineEdit,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Password QLineEdit")
        self.resize(300, 100)
        username_line_edit = QLineEdit(parent=self)
        password_line_edit = QLineEdit(parent=self)
        password_line_edit.setEchoMode(QLineEdit.EchoMode.Password)

        layout = QFormLayout()
        layout.addRow("Username:", username_line_edit)
        layout.addRow("Password:", password_line_edit)
        self.setLayout(layout)

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, you call &lt;code&gt;setEchoMode()&lt;/code&gt; on the &lt;code&gt;password_line_edit&lt;/code&gt; widget using the &lt;code&gt;Password&lt;/code&gt; mode as an argument. When you run this code from your command line, you get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Password input field created with QLineEdit echo mode in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/password-qlineedit.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/password-qlineedit.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/password-qlineedit.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/password-qlineedit.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/password-qlineedit.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;Window with a username and password line edit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;username_line_edit&lt;/code&gt; line edit is in &lt;code&gt;Normal&lt;/code&gt; mode, so we can see the characters as we type them in. In contrast, the Password line edit is in &lt;code&gt;Password&lt;/code&gt; mode. In this case, when we enter a character, the line edit shows the platform's character for passwords.&lt;/p&gt;
&lt;h2 id="manipulating-text-in-a-qlineedit"&gt;Manipulating Text in a QLineEdit&lt;/h2&gt;
&lt;p&gt;You can change the text of a line edit using the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#text-prop"&gt;&lt;code&gt;setText()&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#insert"&gt;&lt;code&gt;insert()&lt;/code&gt;&lt;/a&gt; methods. You can retrieve the text with the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#text-prop"&gt;&lt;code&gt;text()&lt;/code&gt;&lt;/a&gt; method. However, these are not the only operations that you can perform with the text of a line edit.&lt;/p&gt;
&lt;p&gt;The following table shows a summary of some of the most commonly used methods for text manipulation in line edits:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&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;a href="https://doc.qt.io/qt-6/qlineedit.html#text-prop"&gt;&lt;code&gt;setText(text)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Sets the text of a line edit to &lt;code&gt;text&lt;/code&gt;, clears the selection, clears the undo/redo history, moves the cursor to the end of the line, and resets the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#modified-prop"&gt;modified&lt;/a&gt; property to false.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#insert"&gt;&lt;code&gt;insert(text)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Deletes any selected text, inserts &lt;code&gt;text&lt;/code&gt;, and validates the result. If it is valid, it sets it as the new contents of the line edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#clear"&gt;&lt;code&gt;clear()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Clears the contents of the line edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#copy"&gt;&lt;code&gt;copy()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Copies the selected text to the clipboard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#cut"&gt;&lt;code&gt;cut()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Copies the selected text to the clipboard and deletes it from the line edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#paste"&gt;&lt;code&gt;paste()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Inserts the clipboard's text at the cursor position, deleting any selected text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#redo"&gt;&lt;code&gt;redo()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Redoes the last operation if redo is &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#redoAvailable-prop"&gt;available&lt;/a&gt;. Redo becomes available once the user has performed one or more undo operations on text in the line edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#undo"&gt;&lt;code&gt;undo()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Undoes the last operation if undo is &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#undoAvailable-prop"&gt;available&lt;/a&gt;. Undo becomes available once the user has modified the text in the line edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#selectAll"&gt;&lt;code&gt;selectAll()&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Selects all the text and moves the cursor to the end.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You can use any of these methods to manipulate the text of a line edit from your code. Consider the following example where you have two line edits and two buttons that take advantage of some of the above methods to copy some text from one line edit to the other:&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,
    QGridLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Copy and Paste")
        self.resize(300, 100)

        self.source_line_edit = QLineEdit(parent=self)
        self.source_line_edit.setText("Hello, World!")
        self.dest_line_edit = QLineEdit(parent=self)

        copy_button = QPushButton(parent=self, text="Copy")
        paste_button = QPushButton(parent=self, text="Paste")

        copy_button.clicked.connect(self.copy)
        paste_button.clicked.connect(self.paste)

        layout = QGridLayout()
        layout.addWidget(self.source_line_edit, 0, 0)
        layout.addWidget(copy_button, 0, 1)
        layout.addWidget(self.dest_line_edit, 1, 0)
        layout.addWidget(paste_button, 1, 1)
        self.setLayout(layout)

    def copy(self):
        self.source_line_edit.selectAll()
        self.source_line_edit.copy()

    def paste(self):
        self.dest_line_edit.clear()
        self.dest_line_edit.paste()

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create two line edits. The first one will hold some sample text. The second line edit will receive the text. Then, we create two buttons and connect their &lt;code&gt;clicked&lt;/code&gt; signals to the &lt;code&gt;copy()&lt;/code&gt; and &lt;code&gt;paste()&lt;/code&gt; slots.&lt;/p&gt;
&lt;p&gt;Inside the &lt;code&gt;copy()&lt;/code&gt; method we first select all the text from the source line edit. Then we use the &lt;code&gt;copy()&lt;/code&gt; method to copy the selected text to the clipboard. In &lt;code&gt;paste()&lt;/code&gt;, we call &lt;code&gt;clear()&lt;/code&gt; on the destination line edit to remove any previous text. Then, we use the &lt;code&gt;paste()&lt;/code&gt; method to copy the clipboard's content.&lt;/p&gt;
&lt;p&gt;Go ahead and run the application. You'll get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit text manipulation example with copy and paste buttons in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-text-manipulation.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-manipulation.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-manipulation.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-manipulation.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-manipulation.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;QLineEdit with Copy &amp;amp; Paste buttons attached to handlers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you've run the app, then you can click the &lt;em&gt;Copy&lt;/em&gt; button to copy the text in the first line edit. Next, you can click the &lt;em&gt;Paste&lt;/em&gt; button to paste the copied text to the second line edit. Go ahead and give it a try!&lt;/p&gt;
&lt;h2 id="aligning-and-formatting-text-in-a-qlineedit"&gt;Aligning and Formatting Text in a QLineEdit&lt;/h2&gt;
&lt;p&gt;You can also align and format the text in a line edit. For example, for text alignment, you can use the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#alignment-prop"&gt;&lt;code&gt;setAlignment()&lt;/code&gt;&lt;/a&gt; method with one or more alignment flags. Some of the most useful flags that you can find in the &lt;a href="https://doc.qt.io/qt-6/qt.html#AlignmentFlag-enum"&gt;&lt;code&gt;Qt.AlignmentFlag&lt;/code&gt;&lt;/a&gt; namespace includes the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&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;AlignLeft&lt;/code&gt;&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;AlignRight&lt;/code&gt;&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;AlignHCenter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centers horizontally in the available space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AlignJustify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Justifies the text in the available space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AlignTop&lt;/code&gt;&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;AlignBottom&lt;/code&gt;&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;AlignVCenter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centers vertically in the available space.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AlignCenter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centers in both dimensions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you want to apply multiple alignment flags to a given line edit, you don't have to call &lt;code&gt;setAlignment()&lt;/code&gt; multiple times. You can just use the bitwise OR operator (&lt;code&gt;|&lt;/code&gt;) to combine them. Consider the following 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 PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLineEdit

class Window(QLineEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Aligning Text")
        self.resize(300, 100)
        self.setText("Hello, World!")
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we use a &lt;code&gt;QLineEdit&lt;/code&gt; as the only component of our app's GUI. Using the &lt;code&gt;setAlignment()&lt;/code&gt; method, we center the &lt;code&gt;"Hello, World!"&lt;/code&gt; message in both directions, horizontally and vertically. To do this, we use the &lt;code&gt;AlignCenter&lt;/code&gt; flag. Here's what the app looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit with centered text alignment using setAlignment in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-text-alignment.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-alignment.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-alignment.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-alignment.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-alignment.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;QLineEdit with text alignment set.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Go ahead and play with other flags to see their effect on the text alignment. Use the &lt;code&gt;|&lt;/code&gt; bitwise operator to combine several alignment flags.&lt;/p&gt;
&lt;h3&gt;Setting Text Margins on a QLineEdit&lt;/h3&gt;
&lt;p&gt;Line edits also have a &lt;code&gt;textMargins&lt;/code&gt; property that you can tweak to customize the text alignment using specific values. To set margin values for your text, you can use the &lt;code&gt;setTextMargins()&lt;/code&gt; method, which has the following signatures:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&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;a href="https://doc.qt.io/qt-6/qlineedit.html#setTextMargins"&gt;&lt;code&gt;setTextMargins(left, top, right, bottom)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Sets the margins around the text to have the sizes &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, and &lt;code&gt;bottom&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#setTextMargins-1"&gt;&lt;code&gt;setTextMargins(margins)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Sets the &lt;code&gt;margins&lt;/code&gt; around the text using a &lt;a href="https://doc.qt.io/qt-6/qmargins.html"&gt;&lt;code&gt;QMargins&lt;/code&gt;&lt;/a&gt; object.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first signature allows you to provide four integer values as the left, top, right, and bottom margins for the text. The second signature accepts a &lt;code&gt;QMargins&lt;/code&gt; object as an argument. To build this object, you can use four integer values with the same meaning as the &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, and &lt;code&gt;bottom&lt;/code&gt; arguments in the first signature.&lt;/p&gt;
&lt;p&gt;Here's an example of how to set custom margins for the text in a line 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;from PyQt6.QtWidgets import QApplication, QLineEdit

class Window(QLineEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Aligning Text")
        self.resize(300, 100)
        self.setText("Hello, World!")
        self.setTextMargins(30, 30, 0, 0)

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, you set the left and top margins to custom values. Here's how this app looks when you run it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit with custom text margins using setTextMargins in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-text-margins.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-margins.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-margins.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-margins.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-text-margins.png?tr=w-600 600w" loading="lazy" width="600" height="256"/&gt;
&lt;em&gt;QLineEdit with text margins added.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;setTextMargins()&lt;/code&gt; method, we can place the text in the desired place in a line edit, which may be a requirement in some situations.&lt;/p&gt;
&lt;h2 id="connecting-qlineedit-signals-and-slots"&gt;Connecting QLineEdit Signals and Slots&lt;/h2&gt;
&lt;p&gt;When you're creating a GUI application and you need to use line edits, you may need to perform actions when the user enters or modifies the content of the line edit. To do this, you need to connect some of the signals of the line edit to specific slots or functions.&lt;/p&gt;
&lt;p&gt;Depending on specific user events, the &lt;code&gt;QLineEdit&lt;/code&gt; class can emit several different signals. Here's a summary of these signals and their corresponding meaning:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Emitted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#textChanged"&gt;&lt;code&gt;textChanged(text)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Whenever the user changes the text either manually or programmatically. The &lt;code&gt;text&lt;/code&gt; argument is the new text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#textEdited"&gt;&lt;code&gt;textEdited(text)&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Whenever the user edits the text manually. The &lt;code&gt;text&lt;/code&gt; argument is the new text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#editingFinished"&gt;&lt;code&gt;editingFinished&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;When the user presses the &lt;em&gt;Return&lt;/em&gt; or &lt;em&gt;Enter&lt;/em&gt; key, or when the line edit loses focus, and its contents have changed since the last time this signal was emitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#inputRejected"&gt;&lt;code&gt;inputRejected&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;When the user presses a key that is an unacceptable input.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#returnPressed"&gt;&lt;code&gt;returnPressed&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;When the user presses the &lt;em&gt;Return&lt;/em&gt; or &lt;em&gt;Enter&lt;/em&gt; key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qlineedit.html#selectionChanged"&gt;&lt;code&gt;selectionChanged&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;When the selection changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We can connect either of these signals with any slot. A slot is a method or function that performs a concrete action in your application. We can connect a signal and a slot so that the slot gets called when the signal gets emitted. Here's the required syntax to do 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;line_edit.&amp;lt;signal&amp;gt;.connect(&amp;lt;method&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this construct, &lt;code&gt;line_edit&lt;/code&gt; is the &lt;code&gt;QLineEdit&lt;/code&gt; object that we need to connect with a given slot. The &lt;code&gt;&amp;lt;signal&amp;gt;&lt;/code&gt; placeholder can be any of the abovementioned signals. Finally, &lt;code&gt;&amp;lt;method&amp;gt;&lt;/code&gt; represents the target slot or method.&lt;/p&gt;
&lt;p&gt;Let's write an example that puts this syntax into action. For this example, we'll connect the &lt;code&gt;textEdited&lt;/code&gt; signal with a method that updates the text of a &lt;code&gt;QLabel&lt;/code&gt; to match the text of our line 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;from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QLineEdit,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Signal and Slot")
        self.resize(300, 100)

        self.line_edit = QLineEdit(parent=self)
        self.label = QLabel(parent=self)
        self.line_edit.textEdited.connect(self.update_label)

        layout = QVBoxLayout()
        layout.addWidget(self.line_edit)
        layout.addWidget(self.label)
        self.setLayout(layout)

    def update_label(self):
        self.label.setText(self.line_edit.text())

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we connect the line edit's &lt;code&gt;textEdited&lt;/code&gt; signal with the &lt;code&gt;update_label()&lt;/code&gt; method, which sets the label's text to match the text we enter in our line edit. Go ahead and run the app. Then, enter some text in the line edit and see what happens with the label at the bottom of the app's window.&lt;/p&gt;
&lt;h2 id="validating-user-input-in-qlineedit"&gt;Validating User Input in QLineEdit&lt;/h2&gt;
&lt;p&gt;We can provide input validators to our line edits using the &lt;a href="https://doc.qt.io/qt-6/qlineedit.html#setValidator"&gt;&lt;code&gt;setValidator()&lt;/code&gt;&lt;/a&gt; method. This method takes a &lt;code&gt;QValidator&lt;/code&gt; object as an argument. PyQt and PySide have a few built-in validators that you can use directly on a line edit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;QIntValidator&lt;/strong&gt; &amp;mdash; for integer validation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QDoubleValidator&lt;/strong&gt; &amp;mdash; for floating-point validation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QRegularExpressionValidator&lt;/strong&gt; &amp;mdash; for validation based on regular expressions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Validator objects process the input to check whether it's valid. In general, a validator object has three possible states:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constant&lt;/th&gt;
&lt;th&gt;Value&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;QValidator.State.Invalid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The input is invalid.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QValidator.State.Intermediate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The input is a valid intermediate value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QValidator.State.Acceptable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The input is acceptable as a final result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When you set a validator to a given line edit, the user may change the content to any &lt;a href="https://doc.qt.io/qt-6/qvalidator.html#State-enum"&gt;&lt;code&gt;Intermediate&lt;/code&gt;&lt;/a&gt; value during editing. However, they can't edit the text to a value that is &lt;a href="https://doc.qt.io/qt-6/qvalidator.html#State-enum"&gt;&lt;code&gt;Invalid&lt;/code&gt;&lt;/a&gt;. The line edit will emit the &lt;code&gt;returnPressed&lt;/code&gt; and &lt;code&gt;editingFinished&lt;/code&gt; signals only when the validator validates input as &lt;a href="https://doc.qt.io/qt-6/qvalidator.html#State-enum"&gt;&lt;code&gt;Acceptable&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's an example where we use a &lt;code&gt;QIntValidator&lt;/code&gt; and a &lt;code&gt;QRegularExpressionValidator&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.QtCore import QRegularExpression
from PyQt6.QtGui import QIntValidator, QRegularExpressionValidator
from PyQt6.QtWidgets import (
    QApplication,
    QFormLayout,
    QLabel,
    QLineEdit,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Input Validators")
        self.resize(300, 100)

        self.int_line_edit = QLineEdit(parent=self)
        self.int_line_edit.setValidator(QIntValidator())

        self.uppercase_line_edit = QLineEdit(parent=self)
        input_validator = QRegularExpressionValidator(
            QRegularExpression("[A-Z]+"), self.uppercase_line_edit
        )
        self.uppercase_line_edit.setValidator(input_validator)
        self.uppercase_line_edit.returnPressed.connect(self.update_label)

        self.signal_label = QLabel(parent=self)

        layout = QFormLayout()
        layout.addRow("Integers:", self.int_line_edit)
        layout.addRow("Uppercase letters:", self.uppercase_line_edit)
        layout.addRow("Signal:", self.signal_label)
        self.setLayout(layout)

    def update_label(self):
        self.signal_label.setText("Return pressed!")

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we have two line edits. In the first line edit, we've used a &lt;code&gt;QIntValidator&lt;/code&gt; object. This way, the line edit will only accept valid integer numbers. If you try to type in something different, then the line edit won't accept your input.&lt;/p&gt;
&lt;p&gt;In the second line edit, we've used a &lt;code&gt;QRegularExpressionValidator&lt;/code&gt;. In this specific case, the regular expression accepts one or more uppercase letters. Again if you try to enter something else, then the line edit won't accept the input.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;QLabel&lt;/code&gt; will show the text &lt;code&gt;Return pressed!&lt;/code&gt; if the input in the second line edit is valid and you press the Enter key. Here's what the app looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="QLineEdit with QIntValidator and QRegularExpressionValidator for input validation in PyQt6" src="https://www.pythonguis.com/static/docs/qlineedit/qlineedit-input-validators.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-input-validators.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-input-validators.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-input-validators.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/docs/qlineedit/qlineedit-input-validators.png?tr=w-600 600w" loading="lazy" width="600" height="292"/&gt;
&lt;em&gt;QLineEdit with input validators restricting user input.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Validators provide a quick way to restrict the user input and set our own validation rules. This way, we can ensure valid user input in our PyQt/PySide applications.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;QLineEdit&lt;/code&gt; widget is one of the most commonly used widgets in PyQt and PySide applications. It provides a single-line text editor with many built-in features including keyboard shortcuts, copy-paste support, and a context menu.&lt;/p&gt;
&lt;p&gt;In this tutorial, you've learned how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create&lt;/strong&gt; line edits using the &lt;code&gt;QLineEdit&lt;/code&gt; constructors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make line edits read-only&lt;/strong&gt; with &lt;code&gt;setReadOnly()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build password fields&lt;/strong&gt; using echo modes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manipulate text&lt;/strong&gt; programmatically with &lt;code&gt;setText()&lt;/code&gt;, &lt;code&gt;copy()&lt;/code&gt;, &lt;code&gt;paste()&lt;/code&gt;, and more&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Align and format&lt;/strong&gt; text using alignment flags and text margins&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect signals and slots&lt;/strong&gt; to respond to user input events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate input&lt;/strong&gt; using &lt;code&gt;QIntValidator&lt;/code&gt;, &lt;code&gt;QDoubleValidator&lt;/code&gt;, and &lt;code&gt;QRegularExpressionValidator&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these skills, you can effectively add and customize text input fields in your Python GUI applications built with PyQt or PySide.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="qt"/><category term="pyside"/><category term="pyside6"/><category term="pyqt"/><category term="pyqt6"/><category term="widgets"/><category term="qlineedit"/><category term="text-input"/><category term="python"/><category term="qt6"/></entry><entry><title>How to Create a Custom Title Bar for a PyQt6 Window — Build Modern, Stylish Custom Title Bars for Your Python Desktop Apps</title><link href="https://www.pythonguis.com/tutorials/custom-title-bar-pyqt6/" rel="alternate"/><published>2023-11-27T13:00:00+00:00</published><updated>2023-11-27T13:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2023-11-27:/tutorials/custom-title-bar-pyqt6/</id><summary type="html">PyQt provides plenty of tools for creating unique and visually appealing graphical user interfaces (GUIs). One aspect of your applications that you may not have considered customizing is the &lt;strong&gt;title bar&lt;/strong&gt;. The &lt;em&gt;title bar&lt;/em&gt; is the topmost part of the window, where your users find the app's name, window controls &amp;amp; other elements.</summary><content type="html">
            &lt;p&gt;PyQt provides plenty of tools for creating unique and visually appealing graphical user interfaces (GUIs). One aspect of your applications that you may not have considered customizing is the &lt;strong&gt;title bar&lt;/strong&gt;. The &lt;em&gt;title bar&lt;/em&gt; is the topmost part of the window, where your users find the app's name, window controls &amp;amp; other elements.&lt;/p&gt;
&lt;p&gt;This part of the window is usually drawn by the operating system or desktop environment, and its default look and feel may not gel well with the rest of your application. However, you may want to customize it to add additional functionality. For example, in web browsers the document tabs are now typically collapsed into the title bar to maximize available space for viewing pages.&lt;/p&gt;
&lt;p&gt;In this tutorial, you will learn how to create &lt;strong&gt;custom title bars in PyQt6&lt;/strong&gt; step by step. We'll cover creating frameless windows, adding window control buttons, handling window dragging with the mouse, and styling the title bar with a modern unified look. By the end, you'll have the knowledge to enhance your PyQt applications with personalized and stylish title bars.&lt;/p&gt;
&lt;h2 id="creating-frameless-windows-in-pyqt6"&gt;Creating Frameless Windows in PyQt6&lt;/h2&gt;
&lt;p&gt;The first step to providing a PyQt application with a custom &lt;strong&gt;title bar&lt;/strong&gt; is to remove the default title bar and window decoration provided by the operating system. If we don't take this step, we'll end up with multiple title bars at the top of our windows.&lt;/p&gt;
&lt;p&gt;In PyQt, we can create a &lt;strong&gt;frameless window&lt;/strong&gt; using the &lt;a href="https://doc.qt.io/qt-6/qwidget.html#windowFlags-prop"&gt;&lt;code&gt;setWindowFlags()&lt;/code&gt;&lt;/a&gt; method available on all &lt;a href="https://doc.qt.io/qt-6/qwidget.html"&gt;&lt;code&gt;QWidget&lt;/code&gt;&lt;/a&gt; subclasses, including &lt;a href="https://doc.qt.io/qt-6/qmainwindow.html"&gt;&lt;code&gt;QMainWindow&lt;/code&gt;&lt;/a&gt;. We call this method, passing in the &lt;code&gt;FramelessWindowHint&lt;/code&gt; flag, which lives in the &lt;code&gt;Qt&lt;/code&gt; namespace under the &lt;code&gt;WindowType&lt;/code&gt; enumeration.&lt;/p&gt;
&lt;p&gt;Here's the code of a minimal PyQt app whose main window is frameless:&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
from PyQt6.QtWidgets import QApplication, QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After importing the required classes, we create a window by subclassing &lt;code&gt;QMainWindow&lt;/code&gt;. In the class initializer method, we set the window's title and resize the window using the &lt;code&gt;resize()&lt;/code&gt; method. Then we use the &lt;code&gt;setWindowFlags()&lt;/code&gt; to make the window frameless. The rest is the usual boilerplate code for creating PyQt applications.&lt;/p&gt;
&lt;p&gt;If you run this app from your command line, you'll get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A frameless window in PyQt6" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-600 600w" loading="lazy" width="1000" height="600"/&gt;
&lt;em&gt;A frameless window in PyQt6&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the app's main window doesn't have a title bar or any other decoration. It's only a gray rectangle on your screen.&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;  Because the window has no buttons, you need to press &lt;em&gt;Alt-F4&lt;/em&gt; on Windows and Linux or &lt;em&gt;Cmd+Q&lt;/em&gt; on macOS to close the app.&lt;/p&gt;
&lt;p&gt;This isn't very helpful, of course, but we'll be adding back in our custom title bar shortly.&lt;/p&gt;
&lt;h2 id="setting-up-the-main-window"&gt;Setting Up the Main Window&lt;/h2&gt;
&lt;p&gt;Before creating our custom title bar, we'll finish the initialization of our app's main window, import some additional classes and create the window's central widget and layouts.&lt;/p&gt;
&lt;p&gt;Here's the code 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 PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        central_widget = QWidget()
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        work_space_layout.addWidget(QLabel("Hello, World!", self))

        centra_widget_layout = QVBoxLayout()
        centra_widget_layout.setContentsMargins(0, 0, 0, 0)
        centra_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        centra_widget_layout.addWidget(self.title_bar)
        centra_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(centra_widget_layout)
        self.setCentralWidget(central_widget)

# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;First, we import the &lt;code&gt;QLabel&lt;/code&gt;, &lt;code&gt;QVBoxLayout&lt;/code&gt;, and &lt;code&gt;QWidget&lt;/code&gt; classes. In our window's initializer, we create a central widget by instantiating &lt;code&gt;QWidget()&lt;/code&gt;. Next, we create an instance attribute called &lt;code&gt;title_bar&lt;/code&gt; by instantiating a class called &lt;code&gt;CustomTitleBar&lt;/code&gt;. We still need to implement this class&amp;mdash;we'll do this in a moment.&lt;/p&gt;
&lt;p&gt;The next step is to create a layout for our window's workspace. In this example, we're using a &lt;code&gt;QVBoxLayout&lt;/code&gt;, but you can use the layout that best fits your needs. We also set some margins for the layout content and added a label containing the phrase &lt;code&gt;"Hello, World!"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we create a global layout for our central widget. Again, we use a &lt;code&gt;QVBoxLayout&lt;/code&gt;. We set the layout's margins to &lt;code&gt;0&lt;/code&gt; and aligned it on the top of our frameless window. In this layout, we need to add the title bar at the top and the workspace at the bottom. Finally, we set the central widget's layout and the app's central widget.&lt;/p&gt;
&lt;p&gt;That's it! We have all the boilerplate code we need for our window to work correctly. Now we're ready to write our custom title bar.&lt;/p&gt;
&lt;h2 id="creating-a-custom-title-bar-widget"&gt;Creating a Custom Title Bar Widget&lt;/h2&gt;
&lt;p&gt;In this section, we will create a custom title bar for our main window. To do this, we will create a new class by inheriting from &lt;code&gt;QWidget&lt;/code&gt;. First, go ahead and update your imports like in the code 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;from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QStyle,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here, we've imported a few new classes. We will use these classes as building blocks for our title bar. Without further ado, let's get into the title bar code. We'll introduce the code in small consecutive chunks to facilitate the explanation. Here's the first piece:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.ColorRole.Highlight)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code snippet, we create a new class by inheriting from &lt;code&gt;QWidget&lt;/code&gt;. This way, our title bar will have all the standard features and functionalities of any PyQt widgets. In the class initializer, we set &lt;code&gt;autoFillBackground&lt;/code&gt; to true because we want to give a custom color to the bar. The next line of code sets the title bar's background color to &lt;code&gt;QPalette.ColorRole.Highlight&lt;/code&gt;, which is a blueish color.&lt;/p&gt;
&lt;p&gt;The next line of code creates and initializes an instance attribute called &lt;code&gt;initial_pos&lt;/code&gt;. We'll use this attribute later on when we deal with moving the window around our screen.&lt;/p&gt;
&lt;p&gt;The final three lines of code allow us to create a layout for our title bar. Because the title bar should be horizontally oriented, we use a &lt;code&gt;QHBoxLayout&lt;/code&gt; class to structure it.&lt;/p&gt;
&lt;h3&gt;Adding a Window Title Label&lt;/h3&gt;
&lt;p&gt;The piece of code below deals with our window's title:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setStyleSheet(
            """QLabel {
                   font-weight: bold;
                   border: 2px solid black;
                   border-radius: 12px;
                   margin: 2px;
                }
            """
        )
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first line of new code creates a &lt;code&gt;title&lt;/code&gt; attribute. It's a &lt;code&gt;QLabel&lt;/code&gt; object that will hold the window's title. Because we want to build a cool title bar, we'd like to add some custom styling to the title. To do this, we use the &lt;code&gt;setStyleSheet()&lt;/code&gt; method with a string representing a CSS style sheet as an argument. The style sheet tweaks the font, borders, and margins of our title label.&lt;/p&gt;
&lt;p&gt;Next, we center the title using the &lt;code&gt;setAlignment()&lt;/code&gt; method with the &lt;code&gt;Qt.AlignmentFlag.AlignCenter&lt;/code&gt; flag as an argument.&lt;/p&gt;
&lt;p&gt;In the conditional statement, we check whether our window has a title. If that's the case, we set the text of our &lt;code&gt;title&lt;/code&gt; label to the current window's title. Finally, we added the &lt;code&gt;title&lt;/code&gt; label to the title bar layout.&lt;/p&gt;
&lt;h3&gt;Adding Minimize, Maximize, Close, and Restore Buttons&lt;/h3&gt;
&lt;p&gt;The next step in our journey to build a custom title bar is to provide standard window controls. In other words, we need to add the &lt;em&gt;minimize&lt;/em&gt;, &lt;em&gt;maximize&lt;/em&gt;, &lt;em&gt;close&lt;/em&gt;, and &lt;em&gt;normal&lt;/em&gt; buttons. These buttons will allow our users to interact with our window. To create the buttons, we'll use the &lt;code&gt;QToolButton&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;Here's the required 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;# ...

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        # Min button
        self.min_button = QToolButton(self)
        min_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMinButton
        )
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMaxButton
        )
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarCloseButton
        )
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarNormalButton
        )
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code snippet, we define all the required buttons by instantiating the &lt;code&gt;QToolButton&lt;/code&gt; class. The &lt;em&gt;minimize&lt;/em&gt;, &lt;em&gt;maximize&lt;/em&gt;, and &lt;em&gt;close&lt;/em&gt; buttons follow the same pattern. We create the button, define an icon for the buttons at hand, and set the icon using the &lt;code&gt;setIcon()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Note that we use the standard icons that PyQt provides. For example, the &lt;em&gt;minimize&lt;/em&gt; button uses the &lt;code&gt;SP_TitleBarMinButton&lt;/code&gt; icon. Similarly, the &lt;em&gt;maximize&lt;/em&gt; and &lt;em&gt;close&lt;/em&gt; buttons use the &lt;code&gt;SP_TitleBarMaxButton&lt;/code&gt; and &lt;code&gt;SP_TitleBarCloseButton&lt;/code&gt; icons. We find all these icons in the &lt;code&gt;QStyle.StandardPixmap&lt;/code&gt; namespace.&lt;/p&gt;
&lt;p&gt;Finally, we connect the button's &lt;code&gt;clicked()&lt;/code&gt; signal to the appropriate slot. For the minimize button, the proper slot is &lt;code&gt;.showMinimized()&lt;/code&gt;. For the maximize and close buttons, the right slots are &lt;code&gt;.showMaximized()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt;, respectively. All these slots are part of the main window's class.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;normal&lt;/em&gt; button at the end of the above code uses the &lt;code&gt;SP_TitleBarNormalButton&lt;/code&gt; icon and &lt;code&gt;showNormal()&lt;/code&gt; slot. This button has an extra setting. We've set its visibility to &lt;code&gt;False&lt;/code&gt;, meaning that the button will be hidden by default. It'll only appear when we maximize the window to allow us to return to the normal state.&lt;/p&gt;
&lt;h3&gt;Styling and Adding Buttons to the Layout&lt;/h3&gt;
&lt;p&gt;Now that we've created and tweaked the buttons, we must add them to our title bar. To do this, we can use the following &lt;code&gt;for&lt;/code&gt; 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;# ...

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(28, 28))
            button.setStyleSheet(
                """QToolButton {
                        border: 2px solid white;
                        border-radius: 12px;
                    }
                """
            )
            title_bar_layout.addWidget(button)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This loop iterates over our four buttons in a predefined order. The first thing to do inside the loop is to define the focus policy of each button. We don't want these buttons to steal focus from widgets in the window's workspace, so we set their focus policy to &lt;code&gt;NoFocus&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we set a fixed size of 28 by 28 pixels for the three buttons using the &lt;code&gt;setFixedSize()&lt;/code&gt; method with a &lt;code&gt;QSize&lt;/code&gt; object as an argument.&lt;/p&gt;
&lt;p&gt;Our main goal in this section is to create a custom title bar. A handy way to customize the look and feel of PyQt widgets is to use CSS style sheets. In the above piece of code, we use the &lt;code&gt;setStyleSheet()&lt;/code&gt; method to apply a custom CSS style sheet to our four buttons. The sheet defines a white and round border for each button.&lt;/p&gt;
&lt;p&gt;The final line in the above code calls the &lt;code&gt;addWidget()&lt;/code&gt; method to add each custom button to our title bar's layout. That's it! We're now ready to give our title bar a try. Go ahead and run the application from your command line. You'll see a window like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A PyQt window with a custom title bar" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-600 600w" loading="lazy" width="1000" height="600"/&gt;
&lt;em&gt;A PyQt6 window with a custom title bar&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is pretty simple styling, but you get the idea. You can tweak the title bar further, depending on your needs. For example, you can change the colors and borders, customize the title's font, add other widgets to the bar, and more.&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;  We'll apply some nicer styles later, once we have the functionality in place! Keep reading.&lt;/p&gt;
&lt;p&gt;Even though the title bar looks different, it has limited functionality. For example, if you click the &lt;em&gt;maximize&lt;/em&gt; button, then the window will change to its maximized state. However, the &lt;em&gt;normal&lt;/em&gt; button won't show up to allow you to return the window to its previous state.&lt;/p&gt;
&lt;p&gt;In addition to this, if you try to move the window around your screen, you'll quickly notice a problem: it's impossible to move the window!&lt;/p&gt;
&lt;p&gt;In the following sections, we'll write the necessary code to fix these issues and make our custom title bar fully functional. To kick things off, let's start by fixing the state issues.&lt;/p&gt;
&lt;h2 id="updating-the-windows-state-on-maximize-and-restore"&gt;Updating the Window's State on Maximize and Restore&lt;/h2&gt;
&lt;p&gt;To fix the issue related to the window's state, we'll override the &lt;code&gt;changeEvent()&lt;/code&gt; method on the &lt;code&gt;MainWindow&lt;/code&gt; class. This method is called directly by Qt whenever the window state changes, e.g. if the window is maximized or hidden. By overriding this event, we can add our own custom behavior.&lt;/p&gt;
&lt;p&gt;Here's the code that overrides the &lt;code&gt;changeEvent()&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 QSize, Qt, QEvent
# ...

class MainWindow(QMainWindow):
    # ...

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method is fairly straightforward. We check the event type to see if it is a &lt;code&gt;WindowStateChange&lt;/code&gt;. If that's the case, we call the &lt;code&gt;window_state_changed()&lt;/code&gt; method of our custom title bar, passing the current window's state as an argument. In the final two lines, we call the parent class's &lt;code&gt;changeEvent()&lt;/code&gt; method and accept the event to signal that we've correctly processed it.&lt;/p&gt;
&lt;p&gt;Here's the implementation of our &lt;code&gt;window_state_changed()&lt;/code&gt; method in &lt;code&gt;CustomTitleBar&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method takes a window's &lt;code&gt;state&lt;/code&gt; as an argument. Depending on the value of the &lt;code&gt;state&lt;/code&gt; parameter, we will show or hide the maximize and restore buttons.&lt;/p&gt;
&lt;p&gt;First, if the window is currently maximized we will show the &lt;em&gt;normal&lt;/em&gt; button and hide the &lt;em&gt;maximize&lt;/em&gt; button. Alternatively, if the window is currently &lt;em&gt;not&lt;/em&gt; maximized we will hide the &lt;em&gt;normal&lt;/em&gt; button and show the &lt;em&gt;maximize&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;The effect of this, together with the order we added the buttons above, is that when you maximize the window the &lt;em&gt;maximize&lt;/em&gt; button will &lt;em&gt;appear to be&lt;/em&gt; replaced with the &lt;em&gt;normal&lt;/em&gt; button. When you restore the window to its normal size, the &lt;em&gt;normal&lt;/em&gt; button will be replaced with the &lt;em&gt;maximize&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;Go ahead and run the app again. Click the &lt;em&gt;maximize&lt;/em&gt; button. You'll note that when the window gets maximized, the middle button changes its icon. Now you have access to the &lt;em&gt;normal&lt;/em&gt; button. If you click it, then the window will recover its previous state.&lt;/p&gt;
&lt;h2 id="handling-window-dragging-with-mouse-events"&gt;Handling Window Dragging with Mouse Events&lt;/h2&gt;
&lt;p&gt;Now it's time to write the code that enables us to move the window around the screen while holding the mouse's left button on the title bar. To do this, we only need to add code to the &lt;code&gt;CustomTitleBar&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;In particular, we need to override three mouse events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mousePressEvent()&lt;/code&gt; will let us know when the user clicks on our custom title bar using the mouse's left button. This may indicate that the window movement should start.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseMoveEvent()&lt;/code&gt; will let us process window movement.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseReleaseEvent()&lt;/code&gt; will let us know when the user has released the mouse's left button so that we can stop moving the window.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's the code that overrides the &lt;code&gt;mousePressEvent()&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.position().toPoint()
        super().mousePressEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this method, we first check if the user clicks on the title bar using the mouse's left-click button. If that's the case, then we update our &lt;code&gt;initial_pos&lt;/code&gt; attribute to the clicked point. Remember that we defined &lt;code&gt;initial_pos&lt;/code&gt; and initialized it to &lt;code&gt;None&lt;/code&gt; back in the &lt;code&gt;__init__()&lt;/code&gt; method of &lt;code&gt;CustomTitleBar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we need to override the &lt;code&gt;mouseMoveEvent()&lt;/code&gt; method. Here's the required 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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.position().toPoint() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This &lt;code&gt;if&lt;/code&gt; statement in &lt;code&gt;mouseMoveEvent()&lt;/code&gt; checks if the &lt;code&gt;initial_pos&lt;/code&gt; attribute is not &lt;code&gt;None&lt;/code&gt;. If this condition is true, then the &lt;code&gt;if&lt;/code&gt; code block executes because we have a valid initial position.&lt;/p&gt;
&lt;p&gt;The first line in the &lt;code&gt;if&lt;/code&gt; code block calculates the difference, or &lt;code&gt;delta&lt;/code&gt;, between the current and initial mouse positions. To get the current position, we call the &lt;code&gt;position()&lt;/code&gt; method on the &lt;code&gt;event&lt;/code&gt; object and convert that position into a &lt;code&gt;QPoint&lt;/code&gt; object using the &lt;code&gt;toPoint()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;The following four lines update the position of our application's main window by adding the &lt;code&gt;delta&lt;/code&gt; values to the current window position. The &lt;code&gt;move()&lt;/code&gt; method does the hard work of moving the window.&lt;/p&gt;
&lt;p&gt;In summary, this code updates the window position based on the movement of our mouse. It tracks the initial position of the mouse, calculates the difference between the initial position and the current position, and applies that difference to the window's position.&lt;/p&gt;
&lt;p&gt;Finally, we can complete the &lt;code&gt;mouseReleaseEvent()&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method's implementation is pretty straightforward. Its purpose is to reset the initial position by setting it back to &lt;code&gt;None&lt;/code&gt; when the mouse is released, indicating that the drag is complete.&lt;/p&gt;
&lt;p&gt;That's it! Go ahead and run your app again. Click on your custom title bar and move the window around while holding the mouse's left-click button. Can you move the window? Great! Your custom title bar is now fully functional.&lt;/p&gt;
&lt;p&gt;The completed code for the custom title bar 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;from PyQt6.QtCore import QEvent, QSize, Qt
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QStyle,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.ColorRole.Highlight)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)

        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setStyleSheet(
            """QLabel {
                   font-weight: bold;
                   border: 2px solid black;
                   border-radius: 12px;
                   margin: 2px;
                }
            """
        )
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)

        # Min button
        self.min_button = QToolButton(self)
        min_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMinButton,
        )
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMaxButton,
        )
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarCloseButton
        )
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarNormalButton
        )
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # Add buttons
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(28, 28))
            button.setStyleSheet(
                """QToolButton {
                        border: 2px solid white;
                        border-radius: 12px;
                    }
                """
            )
            title_bar_layout.addWidget(button)

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.position().toPoint()
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.position().toPoint() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        central_widget = QWidget()
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        work_space_layout.addWidget(QLabel("Hello, World!", self))

        central_widget_layout = QVBoxLayout()
        central_widget_layout.setContentsMargins(0, 0, 0, 0)
        central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        central_widget_layout.addWidget(self.title_bar)
        central_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(central_widget_layout)
        self.setCentralWidget(central_widget)

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="styling-a-modern-unified-title-bar-with-qss"&gt;Styling a Modern Unified Title Bar with QSS&lt;/h2&gt;
&lt;p&gt;So far, we've covered the technical aspects of styling our window with a custom title bar, and added the code to make it function as expected. But it doesn't look &lt;em&gt;great&lt;/em&gt;. In this section we'll take our existing code &amp;amp; tweak the styling and buttons to produce something that's a little more professional looking.&lt;/p&gt;
&lt;p&gt;One common reason for wanting to apply custom title bars to a window is to integrate the title bar with the rest of the application. This technique is called a &lt;em&gt;unified title bar&lt;/em&gt; and can be seen in some popular applications such as web browsers, or Spotify:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Unified title bar in Spotify" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/unified-titlebar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-600 600w" loading="lazy" width="534" height="334"/&gt;&lt;/p&gt;
&lt;p&gt;In this section, we'll look at how we can reproduce the same effect in PyQt using a combination of stylesheets and icons. Below is a screenshot of the final result which we'll be building:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Styled custom title bar in PyQt6 with gradient background and rounded corners" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-600 600w" loading="lazy" width="1018" height="588"/&gt;&lt;/p&gt;
&lt;p&gt;As you can see the window and the toolbar blend nicely together and the window has rounded corners. There are a few different ways to do this, but we'll cover a simple approach using Qt stylesheets to apply styling over the entire window.&lt;/p&gt;
&lt;h3&gt;Creating a Translucent Background Window&lt;/h3&gt;
&lt;p&gt;In order to customize the shape of the window, we need to first tell the OS to stop drawing the default window outline and background for us. We do that by setting a &lt;em&gt;window attribute&lt;/em&gt; on the window. This is similar to the flags we already discussed, in that it turns on and off different window manager 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;# ...
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've added a call to &lt;code&gt;self.setAttribute()&lt;/code&gt;, which sets the attribute &lt;code&gt;Qt.WidgetAttribute.WA_TranslucentBackground&lt;/code&gt; on the window. If you run the code now, you will see the window has become transparent, with only the widget text and toolbar visible.&lt;/p&gt;
&lt;h3&gt;Applying Rounded Corners with a Container Widget&lt;/h3&gt;
&lt;p&gt;Next, we'll tell Qt to draw a new &lt;em&gt;custom&lt;/em&gt; background for us. If you've worked with QSS before, the most obvious way to apply curved edges to the window using QSS stylesheets would be to set &lt;code&gt;border-radius&lt;/code&gt; styles on the main window 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;#...
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        self.setStyleSheet("background-color: gray; border-radius: 10px;")
#...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;However, if you try this, you'll notice that it doesn't work. If you enable a translucent background, the background of the window is not drawn (including your styles). If you &lt;em&gt;don't&lt;/em&gt; set translucent background, the window is filled to the edges with a solid color ignoring the border radius:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Stylesheets can't alter window shape" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-600 600w" loading="lazy" width="1850" height="608"/&gt;.&lt;/p&gt;
&lt;p&gt;The good news is that, with a bit of lateral thinking, there is a simple solution. We already know that we can construct interfaces by nesting widgets in layouts. Since we can't style the &lt;code&gt;border-radius&lt;/code&gt; of a window, but we &lt;em&gt;can&lt;/em&gt; style any other widget, the solution is to simply add a container widget into our window and apply the curved-edge and background styles to that.&lt;/p&gt;
&lt;p&gt;On our &lt;code&gt;MainWindow&lt;/code&gt; class, we already have a &lt;em&gt;central widget&lt;/em&gt; which contains our layout, so we can apply the styles 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;# ...

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        central_widget = QWidget()
        # This container holds the window contents, so we can style it.
        central_widget.setObjectName("Container")
        central_widget.setStyleSheet(
            """#Container {
                    background: qlineargradient(x1:0 y1:0, x2:1 y2:1, stop:0 #051c2a stop:1 #44315f);
                    border-radius: 5px;
                }
            """
        )
        self.title_bar = CustomTitleBar(self)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've taken the existing &lt;code&gt;central_widget&lt;/code&gt; object and assigned an &lt;em&gt;object name&lt;/em&gt; to it. This is an ID which we can use to refer to the widget from QSS and apply our styles specifically to that widget.&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;  If you're familiar with CSS, you might expect that IDs like &lt;code&gt;#Container&lt;/code&gt; must be unique. However, they are not: you can give multiple widgets the same object name if you like. So you can re-use this technique and QSS on multiple windows in your application without problems.&lt;/p&gt;
&lt;p&gt;With this style applied on our window, we have a nice gradient background with curved corners.&lt;/p&gt;
&lt;h3&gt;Making the Title Bar Transparent&lt;/h3&gt;
&lt;p&gt;Unfortunately, the title bar we created is drawn filled, so the background and curved corners of our window are overwritten. To make things look coherent we need to make our title bar also transparent by removing the background color &amp;amp; auto-fill behavior we set earlier.&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;  We don't need to set any flags or attributes on this widget because it is not a window. A &lt;code&gt;QWidget&lt;/code&gt; object is transparent by default.&lt;/p&gt;
&lt;p&gt;We can also make some tweaks to the style of the title label, such as making the title capitalized using &lt;code&gt;text-transform: uppercase&lt;/code&gt;, adjusting the font size and color -- feel free to customize this yourself:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # self.setAutoFillBackground(True) # &amp;lt;-- remove
        # self.setBackgroundRole(QPalette.ColorRole.Highlight) # &amp;lt;-- remove
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title.setStyleSheet(
            """QLabel {
                    text-transform: uppercase;
                    font-size: 10pt;
                    margin-left: 48px;
                    color: white;
                }
            """
        )
&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;  QSS is &lt;em&gt;very&lt;/em&gt; similar to CSS, especially for text styling.&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 &lt;code&gt;margin-left: 48px&lt;/code&gt; is to compensate for the 3 &amp;times; 16 px window icons on the right-hand side so the text aligns centrally.&lt;/p&gt;
&lt;h3&gt;Using Custom SVG Icons for Window Buttons&lt;/h3&gt;
&lt;p&gt;The icons are currently using built-in Qt icons which are a little bit plain &amp;amp; ugly. Next let's update the icons, using custom SVG icons of simple colored circles, for the minimize, maximize, close &amp;amp; restore 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;from PyQt6.QtGui import QIcon
# ...
class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # ...
        # Min button
        self.min_button = QToolButton(self)
        min_icon = QIcon()
        min_icon.addFile('min.svg')
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = QIcon()
        max_icon.addFile('max.svg')
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = QIcon()
        close_icon.addFile('close.svg') # Close has only a single state.
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = QIcon()
        normal_icon.addFile('normal.svg')
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code follows the same basic structure as before, but instead of using the built-in icons here we're loading our icons from SVG images. These images are very simple, consisting of a single circle in green, red or yellow for the different states mimicking macOS.&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 &lt;code&gt;normal.svg&lt;/code&gt; file for returning a maximized window to normal size shows a semi-transparent green circle for simplicity's sake, but you can include iconography and hover behaviors on the buttons if you prefer.&lt;/p&gt;
&lt;p&gt;The final step is to iterate through the created buttons, adding them to the title bar layout. This is slightly tweaked from before to remove the border styling replacing it with simple padding and setting the icon sizes to 16px. Because we are using SVG files, the icons will automatically scale to the available space:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # ...
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(16, 16))
            button.setStyleSheet(
                """QToolButton {
                        border: none;
                        padding: 2px;
                    }
                """
            )
            title_bar_layout.addWidget(button)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And that's it! With these changes, you can now run your application and you'll see a nice sleek modern-looking UI with unified title bar and custom controls:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Modern custom title bar in PyQt6 with gradient background and SVG icons" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/pyqt6-custom-modern-title-bar.jpg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt6-custom-modern-title-bar.jpg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt6-custom-modern-title-bar.jpg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt6-custom-modern-title-bar.jpg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt6-custom-modern-title-bar.jpg?tr=w-600 600w" loading="lazy" width="1748" height="1052"/&gt;
&lt;em&gt;The final result, showing our unified title bar and window design.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Complete Code for the Styled Custom Title Bar&lt;/h3&gt;
&lt;p&gt;The complete code 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;from PyQt6.QtCore import QEvent, QSize, Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title.setStyleSheet(
            """QLabel {
                    text-transform: uppercase;
                    font-size: 10pt;
                    margin-left: 48px;
                    color: white;
                }
            """
        )

        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)

        # Min button
        self.min_button = QToolButton(self)
        min_icon = QIcon()
        min_icon.addFile("min.svg")
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = QIcon()
        max_icon.addFile("max.svg")
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = QIcon()
        close_icon.addFile("close.svg")  # Close has only a single state.
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = QIcon()
        normal_icon.addFile("normal.svg")
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # Add buttons
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(16, 16))
            button.setStyleSheet(
                """QToolButton {
                        border: none;
                        padding: 2px;
                    }
                """
            )
            title_bar_layout.addWidget(button)

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.position().toPoint()
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.position().toPoint() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        central_widget = QWidget()
        # This container holds the window contents, so we can style it.
        central_widget.setObjectName("Container")
        central_widget.setStyleSheet(
            """#Container {
                    background: qlineargradient(x1:0 y1:0, x2:1 y2:1, stop:0 #051c2a stop:1 #44315f);
                    border-radius: 5px;
                }
            """
        )
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        hello_label = QLabel("Hello, World!", self)
        hello_label.setStyleSheet("color: white;")
        work_space_layout.addWidget(hello_label)

        centra_widget_layout = QVBoxLayout()
        centra_widget_layout.setContentsMargins(0, 0, 0, 0)
        centra_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        centra_widget_layout.addWidget(self.title_bar)
        centra_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(centra_widget_layout)
        self.setCentralWidget(central_widget)

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that you've also set the color of the "Hello, World!" label to white so that it's visible on a dark background.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, we have learned the fundamentals of creating custom title bars in PyQt. To do this, we have combined PyQt's widgets, layouts, and styling capabilities to create a visually appealing title bar for a PyQt app.&lt;/p&gt;
&lt;p&gt;With this skill under your belt, you're now ready to create title bars that align perfectly with your application's unique style and branding. This will allow you to break away from the standard window decoration provided by your operating system and add a personal touch to your user interface.&lt;/p&gt;
&lt;p&gt;Now let your imagination run and transform your PyQt application's UX.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="pyqt6"/><category term="window"/><category term="title bar"/><category term="frameless window"/><category term="qss"/><category term="stylesheets"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>How to Restore the Window's Geometry in a PyQt6 App — Make Your Windows Remember Their Last Position and Size</title><link href="https://www.pythonguis.com/tutorials/restore-window-geometry-pyqt/" rel="alternate"/><published>2023-11-13T06:00:00+00:00</published><updated>2023-11-13T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2023-11-13:/tutorials/restore-window-geometry-pyqt/</id><summary type="html">In GUI applications the window's position &amp;amp; size are known as the window &lt;em&gt;geometry&lt;/em&gt;. Saving and restoring the geometry of a window between executions is a useful feature in many applications. With persistent geometry users can arrange applications on their desktop for an optimal workflow and have the applications return to those positions every time they are launched.</summary><content type="html">
            &lt;p&gt;In GUI applications the window's position &amp;amp; size are known as the window &lt;em&gt;geometry&lt;/em&gt;. Saving and restoring the geometry of a window between executions is a useful feature in many applications. With persistent geometry users can arrange applications on their desktop for an optimal workflow and have the applications return to those positions every time they are launched.&lt;/p&gt;
&lt;p&gt;In this tutorial, we will explore how to save and restore the geometry and state of a PyQt window using the &lt;a href="https://www.pythonguis.com/faq/pyqt5-qsettings-how-to-use-qsettings/"&gt;&lt;code&gt;QSettings&lt;/code&gt;&lt;/a&gt; class. With this functionality, you will be able to give your PyQt6 applications a usability boost.&lt;/p&gt;
&lt;p&gt;To follow along with this tutorial, you should have prior knowledge of creating GUI apps with Python and &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;PyQt&lt;/a&gt;. Additionally, having a basic understanding of using the &lt;code&gt;QSettings&lt;/code&gt; class to manage an application's &lt;a href="https://www.pythonguis.com/faq/pyqt5-qsettings-how-to-use-qsettings/"&gt;settings&lt;/a&gt; will be beneficial.&lt;/p&gt;
&lt;h2 id="understanding-a-windows-geometry-in-pyqt6"&gt;Understanding a Window's Geometry in PyQt6&lt;/h2&gt;
&lt;p&gt;PyQt defines the &lt;a href="https://doc.qt.io/qt-6/application-windows.html#window-geometry"&gt;geometry&lt;/a&gt; of a window using a few properties. These properties represent a window's &lt;strong&gt;position&lt;/strong&gt; on the screen and &lt;strong&gt;size&lt;/strong&gt;. Here's a summary of PyQt's geometry-related properties:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Access Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#x-prop"&gt;&lt;code&gt;x&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the &lt;code&gt;x&lt;/code&gt; coordinate of a widget relative to its parent. If the widget is a window, &lt;code&gt;x&lt;/code&gt; includes any window frame and is relative to the desktop. This property defaults to &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#y-prop"&gt;&lt;code&gt;y&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the &lt;code&gt;y&lt;/code&gt; coordinate of a widget relative to its parent. If the widget is a window, &lt;code&gt;y&lt;/code&gt; includes any window frame and is relative to the desktop. This property defaults to &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;y()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#pos-prop"&gt;&lt;code&gt;pos&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the position of the widget within its parent widget. If the widget is a window, the position is relative to the desktop and includes any frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pos()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#geometry-prop"&gt;&lt;code&gt;geometry&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the widget's geometry relative to its parent and excludes the window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;geometry()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#width-prop"&gt;&lt;code&gt;width&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the width of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;width()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#height-prop"&gt;&lt;code&gt;height&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the height of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;height()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-6/qwidget.html#size-prop"&gt;&lt;code&gt;size&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the size of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In PyQt, the &lt;a href="https://doc.qt.io/qt-6/qwidget.html"&gt;&lt;code&gt;QWidget&lt;/code&gt;&lt;/a&gt; class provides the access methods in the table above. Note that when your widget is a window or form, the first three methods operate on the window and its frame, while the last four methods operate on the &lt;strong&gt;client area&lt;/strong&gt;, which is the window's workspace without the external frame.&lt;/p&gt;
&lt;p&gt;Additionally, the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; coordinates are relative to the screen of your computer. The origin of coordinates is the upper left corner of the screen, at which point both &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's create a small demo app to inspect all these properties in real time. To do this, go ahead and fire up your code editor or IDE and create a new Python file called &lt;code&gt;geometry_properties.py&lt;/code&gt;. Then add the following code to the file and save it in your favorite working 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;from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.resize(400, 200)
        self.central_widget = QWidget()
        self.global_layout = QVBoxLayout()
        self.geometry_properties = [
            "x",
            "y",
            "pos",
            "width",
            "height",
            "size",
            "geometry",
        ]
        for prop in self.geometry_properties:
            self.__dict__[f"{prop}_label"] = QLabel(f"{prop}:")
            self.global_layout.addWidget(self.__dict__[f"{prop}_label"])
        button = QPushButton("Update Geometry Properties")
        button.clicked.connect(self.update_labels)
        self.global_layout.addWidget(button)
        self.central_widget.setLayout(self.global_layout)
        self.setCentralWidget(self.central_widget)

    def update_labels(self):
        for prop in self.geometry_properties:
            self.__dict__[f"{prop}_label"].setText(
                f"{prop}: {getattr(self, prop)()}"
            )

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Wow! There's a lot of code in this file. First, we import the required classes from &lt;code&gt;PyQt6.QtWidgets&lt;/code&gt;. Then, we create our app's main window by inheriting from &lt;a href="https://doc.qt.io/qt-6/qmainwindow.html"&gt;&lt;code&gt;QMainWindow&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the initializer method, we set the window's title and size using &lt;a href="https://doc.qt.io/qt-6/qwidget.html#windowTitle-prop"&gt;&lt;code&gt;setWindowTitle()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-6/qwidget.html#resize-1"&gt;&lt;code&gt;resize()&lt;/code&gt;&lt;/a&gt;, respectively. Next, we define a central widget and a layout for our main window.&lt;/p&gt;
&lt;p&gt;We also define a list of properties. We'll use that list to add some &lt;a href="https://www.pythonguis.com/tutorials/pyside6-widgets/#qlabel"&gt;&lt;code&gt;QLabel&lt;/code&gt;&lt;/a&gt; objects. Each label will show a geometry property and its current values. The &lt;em&gt;Update Geometry Properties&lt;/em&gt; &lt;a href="https://www.pythonguis.com/docs/qpushbutton/"&gt;button&lt;/a&gt; allows us to update the value of the window's geometry properties.&lt;/p&gt;
&lt;p&gt;Finally, we define the &lt;code&gt;update_labels()&lt;/code&gt; method to update the values of all the geometry properties using their corresponding access methods. That's it! Go ahead and run the app. You'll get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A PyQt6 Window Showing Labels for Every Geometry Property" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-600 600w" loading="lazy" width="800" height="492"/&gt;
&lt;em&gt;A Window Showing Labels for Every Geometry Property&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking good! Now go ahead and click the &lt;em&gt;Update Geometry Properties&lt;/em&gt; button. You'll see how all the properties get updated. Your app's window will look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A PyQt6 Window Showing the Current Value of Every Geometry Property" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-600 600w" loading="lazy" width="800" height="492"/&gt;
&lt;em&gt;A Window Showing the Current Value of Every Geometry Property&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are numeric values, while &lt;code&gt;pos&lt;/code&gt; is a &lt;a href="https://doc.qt.io/qt-6/qpoint.html"&gt;&lt;code&gt;QPoint&lt;/code&gt;&lt;/a&gt; object with &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; as its coordinates. These properties define the position of this window on your computer screen.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; properties are also numeric values, while the &lt;code&gt;size&lt;/code&gt; property is a &lt;a href="https://doc.qt.io/qt-6/qsize.html"&gt;&lt;code&gt;QSize&lt;/code&gt;&lt;/a&gt; object defined after the current width and height.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;geometry&lt;/code&gt; property is a &lt;a href="https://doc.qt.io/qt-6/qrect.html"&gt;&lt;code&gt;QRect&lt;/code&gt;&lt;/a&gt; object. In this case, the rectangle comprises &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, and &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Great! With this first approach to how PyQt defines a window's geometry, we're ready to continue digging into this tutorial's main topic: saving and restoring the geometry of a window in PyQt6.&lt;/p&gt;
&lt;h2 id="saving-and-loading-window-geometry-settings-with-qsettings"&gt;Saving and Loading Window Geometry Settings With &lt;code&gt;QSettings&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Users of GUI apps will generally expect the apps to remember their settings across sessions. This information is often referred to as &lt;strong&gt;settings&lt;/strong&gt; or &lt;strong&gt;preferences&lt;/strong&gt;. In PyQt applications, you'll manage settings and preferences using the &lt;a href="https://doc.qt.io/qt-6/qsettings.html"&gt;&lt;code&gt;QSettings&lt;/code&gt;&lt;/a&gt; class. This class allows you to have persistent platform-independent settings in your GUI app.&lt;/p&gt;
&lt;p&gt;A commonly expected feature is that the app remembers the geometry of its windows, particularly the main window.&lt;/p&gt;
&lt;p&gt;In this section, you'll learn how to save and restore the window's geometry in a PyQt6 application. Let's start by creating a skeleton PyQt application to kick things off. Go ahead and create a new Python file called &lt;code&gt;geometry.py&lt;/code&gt;. Once you have the file opened in your favorite code editor or IDE, then 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 PyQt6.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code creates a minimal PyQt app with an empty main window. The window will appear at 50 pixels from the upper left corner of your computer screen and have a size of 400 by 200 pixels.&lt;/p&gt;
&lt;p&gt;We'll use the above code as a starting point to make the app remember and restore the main window's geometry across sessions.&lt;/p&gt;
&lt;p&gt;First, we need to have a &lt;code&gt;QSettings&lt;/code&gt; instance in our app. Therefore, you have to import &lt;code&gt;QSettings&lt;/code&gt; from &lt;code&gt;PyQt6.QtCore&lt;/code&gt; and instantiate it as in the code 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;from PyQt6.QtCore import QSettings
from PyQt6.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When instantiating &lt;code&gt;QSettings&lt;/code&gt;, we must provide the name of our company or organization and the name of our application. We use &lt;code&gt;"PythonGUIs"&lt;/code&gt; as the organization and &lt;code&gt;"GeometryApp"&lt;/code&gt; as the application name.&lt;/p&gt;
&lt;p&gt;Now that we have a &lt;code&gt;QSettings&lt;/code&gt; instance, we should implement two methods. The first method should allow you to save the app's settings and preferences. The second method should help you read and load the settings. In this tutorial, we'll call these methods &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt;, respectively:&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 Window(QMainWindow):
    # ...

    def write_settings(self):
        # Write settings here...

    def read_settings(self):
        # Read settings here...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that our methods don't do anything yet. You'll write them in a moment. For now, they're just placeholders.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;write_settings()&lt;/code&gt; method must be called when the user closes or terminates the application. This way, you guarantee that all the modified settings get saved for the next session. So, the appropriate place to call &lt;code&gt;write_settings()&lt;/code&gt; is from the main window's close event handler.&lt;/p&gt;
&lt;p&gt;Let's override the &lt;code&gt;closeEvent()&lt;/code&gt; method as in the code 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;class Window(QMainWindow):
    # ...

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code, we override the &lt;code&gt;closeEvent()&lt;/code&gt; handler method. The first line calls &lt;code&gt;write_settings()&lt;/code&gt; to ensure that we save the current state of our app's settings. Then, we call the &lt;code&gt;closeEvent()&lt;/code&gt; of our superclass &lt;code&gt;QMainWindow&lt;/code&gt; to ensure the app's window closes correctly. Finally, we accept the current event to signal that it's been processed.&lt;/p&gt;
&lt;p&gt;Now, where should we call &lt;code&gt;read_settings()&lt;/code&gt; from? In this example, the best place for calling the &lt;code&gt;read_settings()&lt;/code&gt; method is &lt;code&gt;.__init__()&lt;/code&gt;. Go ahead and add the following line of code to the end of your &lt;code&gt;__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;class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.read_settings()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By calling the &lt;code&gt;read_settings()&lt;/code&gt; method from &lt;code&gt;__init__()&lt;/code&gt;, we ensure that our app will read and load its settings every time the main window gets created and initialized.&lt;/p&gt;
&lt;p&gt;Great! We're on the way to getting our application to remember and restore its window's geometry. First, you need to know that you have at least two ways to restore the geometry of a window in PyQt6:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; properties&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;geometry&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, you need to save the current value of the selected property and load the saved value when the application starts. To kick things off, let's start with the first approach.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-with-pos-and-size"&gt;Restoring the Window's Geometry With &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In this section, we'll first write the required code to save the current value of &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; by taking advantage of our &lt;code&gt;QSettings&lt;/code&gt; object. The code snippet below shows the changes that you need to make on your &lt;code&gt;write_settings()&lt;/code&gt; method to get this done:&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 Window(QMainWindow):
    # ...

    def write_settings(self):
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("size", self.size())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code is straightforward. We call the &lt;a href="https://doc.qt.io/qt-6/qsettings.html#setValue"&gt;&lt;code&gt;setValue()&lt;/code&gt;&lt;/a&gt; method on our setting object to set the &lt;code&gt;"pos"&lt;/code&gt; and &lt;code&gt;"size"&lt;/code&gt; configuration parameters. Note that we get the current value of each property using the corresponding access method.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;write_settings()&lt;/code&gt; method updated, we're now ready to read and load the geometry properties from our app's settings. Go ahead and update the &lt;code&gt;read_settings()&lt;/code&gt; method as in the code 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;class Window(QMainWindow):
    # ...

    def read_settings(self):
        self.move(self.settings.value("pos", defaultValue=QPoint(50, 50)))
        self.resize(self.settings.value("size", defaultValue=QSize(400, 200)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first line inside &lt;code&gt;read_settings()&lt;/code&gt; retrieves the value of the &lt;code&gt;"pos"&lt;/code&gt; setting parameter. If there's no saved value for this parameter, then we use &lt;code&gt;QPoint(50, 50)&lt;/code&gt; as the default value. Next, the &lt;a href="https://doc.qt.io/qt-6/qwidget.html#move-1"&gt;&lt;code&gt;move()&lt;/code&gt;&lt;/a&gt; method moves the app's window to the resulting position on your screen.&lt;/p&gt;
&lt;p&gt;The second line in &lt;code&gt;read_settings()&lt;/code&gt; does something similar to the first one. It retrieves the current value of the &lt;code&gt;"size"&lt;/code&gt; parameter and resizes the window accordingly.&lt;/p&gt;
&lt;p&gt;Great! It's time for a test! Go ahead and run your application. Then, move the app's window to another position on your screen and resize the window as desired. Finally, close the app's window to terminate the current session. When you run the app again, the window will appear in the same position. It will also have the same size.&lt;/p&gt;
&lt;p&gt;If you have any issues completing and running the example app, then you can grab the entire code 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;from PyQt6.QtCore import QPoint, QSettings, QSize
from PyQt6.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.read_settings()

    def write_settings(self):
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("size", self.size())

    def read_settings(self):
        self.move(self.settings.value("pos", defaultValue=QPoint(50, 50)))
        self.resize(self.settings.value("size", defaultValue=QSize(400, 200)))

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you know how to restore the geometry of a window in a PyQt6 app using the &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; properties. It's time to change gears and learn how to do this using the &lt;code&gt;geometry&lt;/code&gt; property.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-with-savegeometry-and-restoregeometry"&gt;Restoring the Window's Geometry With &lt;code&gt;saveGeometry()&lt;/code&gt; and &lt;code&gt;restoreGeometry()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We can also restore the geometry of a PyQt window using the &lt;a href="https://doc.qt.io/qt-6/qwidget.html#saveGeometry"&gt;&lt;code&gt;saveGeometry()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-6/qwidget.html#restoreGeometry"&gt;&lt;code&gt;restoreGeometry()&lt;/code&gt;&lt;/a&gt; methods. This approach is often preferred because it handles edge cases like multi-monitor setups more robustly. To do that, we first need to save the current geometry using our &lt;code&gt;QSettings&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;Go ahead and create a new Python file in your working directory. Once you have the file in place, add the following code to it:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtCore import QByteArray, QSettings
from PyQt6.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.read_settings()

    def write_settings(self):
        self.settings.setValue("geometry", self.saveGeometry())

    def read_settings(self):
        self.restoreGeometry(self.settings.value("geometry", QByteArray()))

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are only two changes in this code compared to the code from the previous section. We've modified the implementation of the &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;write_settings()&lt;/code&gt;, we use &lt;code&gt;setValue()&lt;/code&gt; to save the current &lt;code&gt;geometry&lt;/code&gt; of our app's window. The &lt;code&gt;saveGeometry()&lt;/code&gt; method serializes the window's geometry into a &lt;code&gt;QByteArray&lt;/code&gt;, which includes position, size, and additional metadata. In &lt;code&gt;read_settings()&lt;/code&gt;, we call the &lt;code&gt;value()&lt;/code&gt; method to retrieve the saved &lt;code&gt;geometry&lt;/code&gt; value. Then, we use &lt;code&gt;restoreGeometry()&lt;/code&gt; to restore the geometry of our window.&lt;/p&gt;
&lt;p&gt;Again, you can run the application consecutive times and change the position and size of its main window to ensure your code works correctly.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-and-state-including-toolbars-and-dock-widgets"&gt;Restoring the Window's Geometry and State Including Toolbars and Dock Widgets&lt;/h2&gt;
&lt;p&gt;If your app's window has &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-actions-toolbars-menus/"&gt;toolbars&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-6/qdockwidget.html"&gt;dock widgets&lt;/a&gt;, then you want to restore their &lt;em&gt;state&lt;/em&gt; on the parent window. To do that, you can use the &lt;a href="https://doc.qt.io/qt-6/qmainwindow.html#restoreState"&gt;&lt;code&gt;restoreState()&lt;/code&gt;&lt;/a&gt; method. To illustrate this, let's reuse the code from the previous section.&lt;/p&gt;
&lt;p&gt;Update the content of &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt; 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;class Window(QMainWindow):
    # ...

    def write_settings(self):
        self.settings.setValue("geometry", self.saveGeometry())
        self.settings.setValue("windowState", self.saveState())

    def read_settings(self):
        self.restoreGeometry(self.settings.value("geometry", QByteArray()))
        self.restoreState(self.settings.value("windowState", QByteArray()))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;code&gt;write_settings()&lt;/code&gt;, we add a new setting value called &lt;code&gt;"windowState"&lt;/code&gt;. To keep this setting, we use the &lt;a href="https://doc.qt.io/qt-6/qmainwindow.html#saveState"&gt;&lt;code&gt;saveState()&lt;/code&gt;&lt;/a&gt; method, which saves the current state of this window's toolbars and dock widgets. Meanwhile, in &lt;code&gt;read_settings()&lt;/code&gt;, we restore the window's state by calling the &lt;code&gt;value()&lt;/code&gt; method, as usual, to get the state value back from our &lt;code&gt;QSettings&lt;/code&gt; object. Finally, we use &lt;code&gt;restoreState()&lt;/code&gt; to restore the state of toolbars and dock widgets.&lt;/p&gt;
&lt;p&gt;Now, to make sure that this new code works as expected, let's add a sample toolbar and a dock window to our app's main window. Go ahead and add the following methods right after the &lt;code&gt;__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 QByteArray, QSettings, Qt
from PyQt6.QtWidgets import QApplication, QDockWidget, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's State")
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.create_toolbar()
        self.create_dock()
        self.read_settings()

    def create_toolbar(self):
        toolbar = self.addToolBar("Toolbar")
        toolbar.addAction("One")
        toolbar.addAction("Two")
        toolbar.addAction("Three")

    def create_dock(self):
        dock = QDockWidget("Dock", self)
        dock.setAllowedAreas(
            Qt.DockWidgetArea.LeftDockWidgetArea
            | Qt.DockWidgetArea.RightDockWidgetArea
        )
        self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock)

    # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this new update, we first import the &lt;code&gt;Qt&lt;/code&gt; namespace from &lt;code&gt;PyQt6.QtCore&lt;/code&gt; and &lt;code&gt;QDockWidget&lt;/code&gt; from &lt;code&gt;PyQt6.QtWidgets&lt;/code&gt;. Then we call the two new methods from &lt;code&gt;__init__()&lt;/code&gt; to create the toolbar and dock widget at initialization time.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;create_toolbar()&lt;/code&gt; method, we create a sample toolbar with three sample buttons. This toolbar will show at the top of our app's window by default.&lt;/p&gt;
&lt;p&gt;Next, we create a dock widget in &lt;code&gt;create_dock()&lt;/code&gt;. This widget will occupy the rest of our window's working area.&lt;/p&gt;
&lt;p&gt;That's it! You're now ready to give your app a try. You'll see a window like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A PyQt6 Window Showing a Sample Toolbar and a Dock Widget" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-600 600w" loading="lazy" width="800" height="456"/&gt;
&lt;em&gt;A Window Showing a Sample Toolbar and a Dock Widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Play with the toolbar and the dock widget. Move them around. Then close the app's window and run the app again. Your toolbar and dock widget will show in the last position you left them.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Through this tutorial, you have learned how to save and restore the &lt;strong&gt;geometry&lt;/strong&gt; and &lt;strong&gt;state&lt;/strong&gt; of a window in PyQt6 applications using the &lt;code&gt;QSettings&lt;/code&gt; class. By utilizing the &lt;code&gt;pos&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;saveGeometry()&lt;/code&gt;, &lt;code&gt;restoreGeometry()&lt;/code&gt;, &lt;code&gt;saveState()&lt;/code&gt;, and &lt;code&gt;restoreState()&lt;/code&gt; methods, you can give your users the convenience of persistent window position and size across sessions.&lt;/p&gt;
&lt;p&gt;With this knowledge, you can enhance the usability of your PyQt6 applications, making your app more intuitive and user-friendly. Remembering window geometry is a small detail that significantly improves the overall user experience of desktop Python GUI applications.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="pyqt6"/><category term="qsettings"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>How Trademarks Affect Open Source Software — How do trademarks relate to copyrights and what are the implications for open source software</title><link href="https://www.pythonguis.com/faq/trademarks-and-open-source-software/" rel="alternate"/><published>2023-08-15T09:00:00+00:00</published><updated>2023-08-15T09:00:00+00:00</updated><author><name>S.M. Oliva</name></author><id>tag:www.pythonguis.com,2023-08-15:/faq/trademarks-and-open-source-software/</id><summary type="html">As &lt;a href="https://www.pythonguis.com/faq/software-copyrights/"&gt;previously discussed&lt;/a&gt;, a central concept of open source licenses is that the author of a given program grants anyone permission to use, modify, or redistribute their code without seeking specific permission first. But these licenses generally cover the copyrights to the underlying code and not any trademarks that may be used by the original author in connection with their work.</summary><content type="html">&lt;p&gt;As &lt;a href="https://www.pythonguis.com/faq/software-copyrights/"&gt;previously discussed&lt;/a&gt;, a central concept of open source licenses is that the author of a given program grants anyone permission to use, modify, or redistribute their code without seeking specific permission first. But these licenses generally cover the copyrights to the underlying code and not any trademarks that may be used by the original author in connection with their work.&lt;/p&gt;
&lt;p&gt;A trademark is basically a word, phrase, symbol, or design--or any combination of those elements--that identifies a unique product or service in commerce. For example, "Python" is a registered trademark of the Python Software Foundation (PSF). The famous blue-and-yellow "intertwined snakes" Python logo is also a PSF trademark.&lt;/p&gt;
&lt;p&gt;So how exactly do trademarks work? And as a developer, what can you do--and not do--with someone else's trademarks in your own work?&lt;/p&gt;
&lt;h2 id="trademarks-are-not-copyrights"&gt;Trademarks Are Not Copyrights&lt;/h2&gt;
&lt;p&gt;To the layperson, it might seem like trademarks are just a method by which someone can copyright a particular word or phrase. In fact, copyright and trademark are distinct forms of intellectual property. A copyright applies to a created work, such as a book or a software program, that is original and exists in some fixed form. The purpose of copyright is to enable the author to exercise exclusive control over the right to reproduce or distribute their work for a specified period of time.&lt;/p&gt;
&lt;p&gt;A trademark, in contrast, is primarily about protecting consumers from harm. A trademark identifies the &lt;em&gt;source&lt;/em&gt; of a good or service. This helps guard against potential counterfeiting or fraud while affording the trademark holder legal protection for their brand. But the trademark holder does not "own" the trademarked word or phrase. Rather, they exercise a limited right to use the trademark in connection with the sale of their product or service in a given area.&lt;/p&gt;
&lt;p&gt;Trademarks can be used in connection with a variety of products within the same brand. The PSF uses its Python mark, for instance, not just to refer to the programming language itself but also merchandise such as T-shirts and hats. But the PSF could not enforce its Python mark in an area of commerce where it does not actually produce any products.&lt;/p&gt;
&lt;p&gt;Like copyright, a trademark holder can register their mark with the government, although it is not required. In the United States, the U.S. Patent and Trademark Office (USPTO) manages trademark registrations at the federal level. A registered trademark is indicated by an "R" with a circle around it (&amp;reg;), while an unregistered trademark is indicated with the letters "TM" (&amp;trade;). As with copyright, registering a trademark provides certain legal benefits, such as a presumption that the mark is valid and belongs to the registrant.&lt;/p&gt;
&lt;h2 id="when-do-you-need-a-software-trademark-holders-permission"&gt;When Do You Need a Software Trademark Holder's Permission?&lt;/h2&gt;
&lt;p&gt;Another key difference between copyright and trademark is enforcement. With copyright, an author can be fairly lax in their assertion of rights without losing them. Indeed, open source licensing is essentially built around the notion that most developers do not want to spend their time policing how other people use their code. Yet the license itself still retains copyright in the event the author feels compelled to take legal action against infringement.&lt;/p&gt;
&lt;p&gt;With a trademark, however, the holder needs to be more proactive in asserting their rights. The reason for this is that unlike copyright, a trademark can last indefinitely, provided it continues to be actively used and defended by the holder. The more passive the holder is in defending their rights, the more likely that the USPTO or a judge may find the trademark is no longer in active use.&lt;/p&gt;
&lt;p&gt;For this reason, many organizations that rely on trademarks will publish a written policy governing their acceptable use. The &lt;a href="https://www.python.org/psf/trademarks/"&gt;PSF has a very detailed trademark usage policy&lt;/a&gt;, which provides a good model for how enforcement works in practice. Fortunately for developers, the PSF policy makes it clear that the organization wants the "Python" mark and logo "to be used with minimal restriction to refer to the Python programming language."&lt;/p&gt;
&lt;p&gt;To be clear, even without this policy, there are many uses of the "Python" trademark allowed by U.S. law (and the laws of other nations) with respect to "fair use." There are basically two kinds of fair use: nominative and descriptive. &lt;em&gt;Nominative&lt;/em&gt; fair use simply means you are referring to the trademarked good or service. So if you write a program in Python, you are allowed to refer to the "Python" trademark without asking the PSF's permission first.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Descriptive&lt;/em&gt; fair use means you are using the trademark to describe some other product or service. This tends to come up when you are comparing one product with another. For instance, if you write an article comparing the benefits and drawbacks of Python with Rust, that would involve descriptive fair use. Again, you don't need permission to do this.&lt;/p&gt;
&lt;p&gt;So when do you need a trademark holder's approval? The PSF's own policy states approval is necessary for any "commercial use" of the "Python" trademark to describe another product or company. In other words, you don't need the PSF's permission to describe your own program as being written in Python, or even to describe yourself as a Python developer. But you would need the PSF's permission to name your company something like "Python Software, Inc."&lt;/p&gt;
&lt;p&gt;But what if you wanted to make or sell merchandise with the Python logo? Again, the PSF's policy permits anyone to use the logo without permission for non-commercial purposes. It's okay to make a Python-logo shirt for yourself and wear it at PyCon. But if you wanted to sell those shirts at PyCon, you would need a license from the PSF and potentially pay royalties on any sales.&lt;/p&gt;
&lt;h2 id="four-things-python-developers-should-know-about-software-trademarks"&gt;Four Things Python Developers Should Know About Software Trademarks&lt;/h2&gt;
&lt;p&gt;If you are developing software, you need to be aware of potential trademark issues. A qualified intellectual property attorney can answer specific questions for your situation. But speaking in general terms, here are four things to keep in mind:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Check the trademark holder's usage policy.&lt;/strong&gt; If you plan to use the name of any existing company or project in describing your own software, make sure to check and see if the trademark holders have a published policy regarding usage like the PSF.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search existing trademarks before naming your project.&lt;/strong&gt; When selecting a name for your own project, you can check the U.S. Patent and Trademark Office's &lt;a href="https://www.uspto.gov/trademarks/search"&gt;trademark database&lt;/a&gt; to see if the same or similar name is already in use with respect to software.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unregistered trademarks still have legal protection.&lt;/strong&gt; Even if a trademark is not formally registered with the USPTO, that does not mean the trademark is not still legally protected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open source licenses do not grant trademark rights.&lt;/strong&gt; An open source license like the GPL, MIT, or BSD licenses do not convey any rights with respect to trademarks. Never assume that you can use the name of another software project in your own commercial application just because the underlying code is freely licensed.&lt;/li&gt;
&lt;/ol&gt;</content><category term="licensing"/><category term="pyqt"/><category term="pyside"/><category term="trademarks"/><category term="copyright"/><category term="python"/><category term="qt"/></entry><entry><title>How to Restore the Window's Geometry in a PyQt5 App — Make Your Windows Remember Their Last Geometry</title><link href="https://www.pythonguis.com/tutorials/restore-window-geometry-pyqt5/" rel="alternate"/><published>2023-05-27T06:00:00+00:00</published><updated>2023-05-27T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2023-05-27:/tutorials/restore-window-geometry-pyqt5/</id><summary type="html">In GUI applications the window's position &amp;amp; size are known as the window &lt;em&gt;geometry&lt;/em&gt;. Saving and restoring the geometry of a window between executions is a useful feature in many applications. With persistent geometry users can arrange applications on their desktop for an optimal workflow and have the applications return to those positions every time they are launched.</summary><content type="html">
            &lt;p&gt;In GUI applications the window's position &amp;amp; size are known as the window &lt;em&gt;geometry&lt;/em&gt;. Saving and restoring the geometry of a window between executions is a useful feature in many applications. With persistent geometry users can arrange applications on their desktop for an optimal workflow and have the applications return to those positions every time they are launched.&lt;/p&gt;
&lt;p&gt;In this tutorial, we will explore how to save and restore the geometry and state of a PyQt window using the &lt;a href="https://www.pythonguis.com/faq/pyqt5-qsettings-how-to-use-qsettings/"&gt;&lt;code&gt;QSettings&lt;/code&gt;&lt;/a&gt; class. With this functionality, you will be able to give your applications a usability boost.&lt;/p&gt;
&lt;p&gt;To follow along with this tutorial, you should have prior knowledge of creating GUI apps with Python and &lt;a href="https://www.pythonguis.com/tutorials/creating-your-first-pyqt-window/"&gt;PyQt&lt;/a&gt;. Additionally, having a basic understanding of using the &lt;code&gt;QSettings&lt;/code&gt; class to manage an application's &lt;a href="https://www.pythonguis.com/faq/pyqt5-qsettings-how-to-use-qsettings/"&gt;settings&lt;/a&gt; will be beneficial.&lt;/p&gt;
&lt;h2 id="understanding-a-windows-geometry"&gt;Understanding a Window's Geometry&lt;/h2&gt;
&lt;p&gt;PyQt defines the &lt;a href="https://doc.qt.io/qt-5/application-windows.html#window-geometry"&gt;geometry&lt;/a&gt; of a window using a few properties. These properties represent a window's &lt;strong&gt;position&lt;/strong&gt; on the screen and &lt;strong&gt;size&lt;/strong&gt;. Here's a summary of PyQt's geometry-related properties:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Access Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#x-prop"&gt;&lt;code&gt;x&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the &lt;code&gt;x&lt;/code&gt; coordinate of a widget relative to its parent. If the widget is a window, &lt;code&gt;x&lt;/code&gt; includes any window frame and is relative to the desktop. This property defaults to &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#y-prop"&gt;&lt;code&gt;y&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the &lt;code&gt;y&lt;/code&gt; coordinate of a widget relative to its parent. If the widget is a window, &lt;code&gt;y&lt;/code&gt; includes any window frame and is relative to the desktop. This property defaults to &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;y()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#pos-prop"&gt;&lt;code&gt;pos&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the position of the widget within its parent widget. If the widget is a window, the position is relative to the desktop and includes any frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pos()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#geometry-prop"&gt;&lt;code&gt;geometry&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the widget's geometry relative to its parent and excludes the window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;geometry()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#width-prop"&gt;&lt;code&gt;width&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the width of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;width()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#height-prop"&gt;&lt;code&gt;height&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the height of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;height()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://doc.qt.io/qt-5/qwidget.html#size-prop"&gt;&lt;code&gt;size&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Holds the size of the widget, excluding any window frame.&lt;/td&gt;
&lt;td&gt;&lt;code&gt;size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In PyQt, the &lt;a href="https://doc.qt.io/qt-5/qwidget.html"&gt;&lt;code&gt;QWidget&lt;/code&gt;&lt;/a&gt; class provides the access methods in the table above. Note that when your widget is a window or form, the first three methods operate on the window and its frame, while the last four methods operate on the &lt;strong&gt;client area&lt;/strong&gt;, which is the window's workspace without the external frame.&lt;/p&gt;
&lt;p&gt;Additionally, the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; coordinates are relative to the screen of your computer. The origin of coordinates is the upper left corner of the screen, at which point both &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's create a small demo app to inspect all these properties in real time. To do this, go ahead and fire up your code editor or IDE and create a new Python file called &lt;code&gt;geometry_properties.py&lt;/code&gt;. Then add the following code to the file and save it in your favorite working 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;from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.resize(400, 200)
        self.central_widget = QWidget()
        self.global_layout = QVBoxLayout()
        self.geometry_properties = [
            "x",
            "y",
            "pos",
            "width",
            "height",
            "size",
            "geometry",
        ]
        for prop in self.geometry_properties:
            self.__dict__[f"{prop}_label"] = QLabel(f"{prop}:")
            self.global_layout.addWidget(self.__dict__[f"{prop}_label"])
        button = QPushButton("Update Geometry Properties")
        button.clicked.connect(self.update_labels)
        self.global_layout.addWidget(button)
        self.central_widget.setLayout(self.global_layout)
        self.setCentralWidget(self.central_widget)

    def update_labels(self):
        for prop in self.geometry_properties:
            self.__dict__[f"{prop}_label"].setText(
                f"{prop}: {getattr(self, prop)()}"
            )

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Wow! There's a lot of code in this file. First, we import the required classes from &lt;code&gt;PyQt5.QtWidgets&lt;/code&gt;. Then, we create our app's main window by inheriting from &lt;a href="https://doc.qt.io/qt-5/qmainwindow.html"&gt;&lt;code&gt;QMainWindow&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the initializer method, we set the window's title and size using &lt;a href="https://doc.qt.io/qt-5/qwidget.html#windowTitle-prop"&gt;&lt;code&gt;setWindowTitle()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-5/qwidget.html#resize-1"&gt;&lt;code&gt;resize()&lt;/code&gt;&lt;/a&gt;, respectively. Next, we define a central widget and a layout for our main window.&lt;/p&gt;
&lt;p&gt;We also define a list of properties. We'll use that list to add some &lt;a href="https://www.pythonguis.com/tutorials/pyside6-widgets/#qlabel"&gt;&lt;code&gt;QLabel&lt;/code&gt;&lt;/a&gt; objects. Each label will show a geometry property and its current values. The &lt;em&gt;Update Geometry Properties&lt;/em&gt; &lt;a href="https://www.pythonguis.com/docs/qpushbutton/"&gt;button&lt;/a&gt; allows us to update the value of the window's geometry properties.&lt;/p&gt;
&lt;p&gt;Finally, we define the &lt;code&gt;update_labels()&lt;/code&gt; method to update the values of all the geometry properties using their corresponding access methods. That's it! Go ahead and run the app. You'll get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Window Showing Labels for Every Geometry Property" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties.png?tr=w-600 600w" loading="lazy" width="800" height="492"/&gt;
&lt;em&gt;A Window Showing Labels for Every Geometry Property&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking good! Now go ahead and click the &lt;em&gt;Update Geometry Properties&lt;/em&gt; button. You'll see how all the properties get updated. Your app's window will look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Window Showing the Current Value of Every Geometry Property" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-geometry-properties-update.png?tr=w-600 600w" loading="lazy" width="800" height="492"/&gt;
&lt;em&gt;A Window Showing the Current Value of Every Geometry Property&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; are numeric values, while &lt;code&gt;pos&lt;/code&gt; is a &lt;a href="https://doc.qt.io/qt-5/qpoint.html"&gt;&lt;code&gt;QPoint&lt;/code&gt;&lt;/a&gt; object with &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; as its coordinates. These properties define the position of this window on your computer screen.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; properties are also numeric values, while the &lt;code&gt;size&lt;/code&gt; property is a &lt;a href="https://doc.qt.io/qt-5/qsize.html"&gt;&lt;code&gt;QSize&lt;/code&gt;&lt;/a&gt; object defined after the current width and height.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;geometry&lt;/code&gt; property is a &lt;a href="https://doc.qt.io/qt-5/qrect.html"&gt;&lt;code&gt;QRect&lt;/code&gt;&lt;/a&gt; object. In this case, the rectangle comprises &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, and &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Great! With this first approach to how PyQt defines a window's geometry, we're ready to continue digging into this tutorial's main topic: restoring the geometry of a window in PyQt.&lt;/p&gt;
&lt;h2 id="keeping-an-apps-geometry-settings-the-qsetting-class"&gt;Keeping an App's Geometry Settings: The &lt;code&gt;QSetting&lt;/code&gt; Class&lt;/h2&gt;
&lt;p&gt;Users of GUI apps will generally expect the apps to remember their settings across sessions. This information is often referred to as &lt;strong&gt;settings&lt;/strong&gt; or &lt;strong&gt;preferences&lt;/strong&gt;. In PyQt applications, you'll manage settings and preferences using the &lt;a href="https://doc.qt.io/qt-5/qsettings.html"&gt;&lt;code&gt;QSettings&lt;/code&gt;&lt;/a&gt; class. This class allows you to have persistent platform-independent settings in your GUI app.&lt;/p&gt;
&lt;p&gt;A commonly expected feature is that the app remembers the geometry of its windows, particularly the main window.&lt;/p&gt;
&lt;p&gt;In this section, you'll learn how to save and restore the window's geometry in a PyQt application. Let's start by creating a skeleton PyQt application to kick things off. Go ahead and create a new Python file called &lt;code&gt;geometry.py&lt;/code&gt;. Once you have the file opened in your favorite code editor or IDE, then 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 PyQt5.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code creates a minimal PyQt app with an empty main window. The window will appear at 50 pixels from the upper left corner of your computer screen and have a size of 400 by 200 pixels.&lt;/p&gt;
&lt;p&gt;We'll use the above code as a starting point to make the app remember and restore the main window's geometry across sessions.&lt;/p&gt;
&lt;p&gt;First, we need to have a &lt;code&gt;QSettings&lt;/code&gt; instance in our app. Therefore, you have to import &lt;code&gt;QSettings&lt;/code&gt; from &lt;code&gt;PyQt5.QtCore&lt;/code&gt; and instantiate it as in the code 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;from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PyhonGUIs", "GeometryApp")
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When instantiating &lt;code&gt;QSettings&lt;/code&gt;, we must provide the name of our company or organization and the name of our application. We use &lt;code&gt;"PyhonGUIs"&lt;/code&gt; as the organization and &lt;code&gt;"GeometryApp"&lt;/code&gt; as the application name.&lt;/p&gt;
&lt;p&gt;Now that we have a &lt;code&gt;QSettings&lt;/code&gt; instance, we should implement two methods. The first method should allow you to save the app's settings and preferences. The second method should help you read and load the settings. In this tutorial, we'll call these methods &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt;, respectively:&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 Window(QMainWindow):
    # ...

    def write_settings(self):
        # Write settings here...

    def read_settings(self):
        # Read settings here...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that our methods don't do anything yet. You'll write them in a moment. For now, they're just placeholders.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;write_settings()&lt;/code&gt; method must be called when the user closes or terminates the application. This way, you guarantee that all the modified settings get saved for the next session. So, the appropriate place to call &lt;code&gt;write_settings()&lt;/code&gt; is from the main window's close event handler.&lt;/p&gt;
&lt;p&gt;Let's override the &lt;code&gt;closeEvent()&lt;/code&gt; method as in the code 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;class Window(QMainWindow):
    # ...

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code, we override the &lt;code&gt;closeEvent()&lt;/code&gt; handler method. The first line calls &lt;code&gt;write_settings()&lt;/code&gt; to ensure that we save the current state of our app's settings. Then, we call the &lt;code&gt;closeEvent()&lt;/code&gt; of our superclass &lt;code&gt;QMainWindow&lt;/code&gt; to ensure the app's window closes correctly. Finally, we accept the current event to signal that it's been processed.&lt;/p&gt;
&lt;p&gt;Now, where should we call &lt;code&gt;read_settings()&lt;/code&gt; from? In this example, the best place for calling the &lt;code&gt;read_settings()&lt;/code&gt; method is &lt;code&gt;.__init__()&lt;/code&gt;. Go ahead and add the following line of code to the end of your &lt;code&gt;__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;class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.read_settings()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By calling the &lt;code&gt;read_settings()&lt;/code&gt; method from &lt;code&gt;__init__()&lt;/code&gt;, we ensure that our app will read and load its settings every time the main window gets created and initialized.&lt;/p&gt;
&lt;p&gt;Great! We're on the way to getting our application to remember and restore its window's geometry. First, you need to know that you have at least two ways to restore the geometry of a window in PyQt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; properties&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;geometry&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, you need to save the current value of the selected property and load the saved value when the application starts. To kick things off, let's start with the first approach.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-with-pos-and-size"&gt;Restoring the Window's Geometry With &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In this section, we'll first write the required code to save the current value of &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; by taking advantage of our &lt;code&gt;QSettings&lt;/code&gt; object. The code snippet below shows the changes that you need to make on your &lt;code&gt;write_settings()&lt;/code&gt; method to get this done:&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 Window(QMainWindow):
    # ...

    def write_settings(self):
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("size", self.size())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code is straightforward. We call the &lt;a href="https://doc.qt.io/qt-5/qsettings.html#setValue"&gt;&lt;code&gt;setValue()&lt;/code&gt;&lt;/a&gt; method on our setting object to set the &lt;code&gt;"pos"&lt;/code&gt; and &lt;code&gt;"size"&lt;/code&gt; configuration parameters. Note that we get the current value of each property using the corresponding access method.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;write_settings()&lt;/code&gt; method updated, we're now ready to read and load the geometry properties from our app's settings. Go ahead and update the &lt;code&gt;read_settings()&lt;/code&gt; method as in the code 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;class Window(QMainWindow):
    # ...

    def read_settings(self):
        self.move(self.settings.value("pos", defaultValue=QPoint(50, 50)))
        self.resize(self.settings.value("size", defaultValue=QSize(400, 200)))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first line inside &lt;code&gt;read_settings()&lt;/code&gt; retrieves the value of the &lt;code&gt;"pos"&lt;/code&gt; setting parameter. If there's no saved value for this parameter, then we use &lt;code&gt;QPoint(50, 50)&lt;/code&gt; as the default value. Next, the &lt;a href="https://doc.qt.io/qt-5/qwidget.html#move-1"&gt;&lt;code&gt;move()&lt;/code&gt;&lt;/a&gt; method moves the app's window to the resulting position on your screen.&lt;/p&gt;
&lt;p&gt;The second line in &lt;code&gt;read_settings()&lt;/code&gt; does something similar to the first one. It retrieves the current value of the &lt;code&gt;"size"&lt;/code&gt; parameter and resizes the window accordingly.&lt;/p&gt;
&lt;p&gt;Great! It's time for a test! Go ahead and run your application. Then, move the app's window to another position on your screen and resize the window as desired. Finally, close the app's window to terminate the current session. When you run the app again, the window will appear in the same position. It will also have the same size.&lt;/p&gt;
&lt;p&gt;If you have any issues completing and running the example app, then you can grab the entire code 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;from PyQt5.QtCore import QPoint, QSettings, QSize
from PyQt5.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PyhonGUIs", "GeometryApp")
        self.read_settings()

    def write_settings(self):
        self.settings.setValue("pos", self.pos())
        self.settings.setValue("size", self.size())

    def read_settings(self):
        self.move(self.settings.value("pos", defaultValue=QPoint(50, 50)))
        self.resize(self.settings.value("size", defaultValue=QSize(400, 200)))

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you know how to restore the geometry of a window in a PyQt app using the &lt;code&gt;pos&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt; properties. It's time to change gears and learn how to do this using the &lt;code&gt;geometry&lt;/code&gt; property.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-with-geometry"&gt;Restoring the Window's Geometry With &lt;code&gt;geometry&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;We can also restore the geometry of a PyQt window using its &lt;code&gt;geometry&lt;/code&gt; property and the &lt;a href="https://doc.qt.io/qt-5/qwidget.html#restoreGeometry"&gt;&lt;code&gt;restoreGeometry()&lt;/code&gt;&lt;/a&gt; method. To do that, we first need to save the current geometry using our &lt;code&gt;QSettings&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;Go ahead and create a new Python file in your working directory. Once you have the file in place, add the following code to it:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt5.QtCore import QByteArray, QSettings
from PyQt5.QtWidgets import QApplication, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's Geometry")
        self.move(50, 50)
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.read_settings()

    def write_settings(self):
        self.settings.setValue("geometry", self.saveGeometry())

    def read_settings(self):
        self.restoreGeometry(self.settings.value("geometry", QByteArray()))

    def closeEvent(self, event):
        self.write_settings()
        super().closeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are only two changes in this code compared to the code from the previous section. We've modified the implementation of the &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;write_settings()&lt;/code&gt;, we use the &lt;code&gt;setValue()&lt;/code&gt; to save the current &lt;code&gt;geometry&lt;/code&gt; of our app's window. The &lt;a href="https://doc.qt.io/qt-5/qwidget.html#saveGeometry"&gt;&lt;code&gt;saveGeometry()&lt;/code&gt;&lt;/a&gt; allows us to access and save the current window's geometry. In &lt;code&gt;read_settings()&lt;/code&gt;, we call the &lt;code&gt;value()&lt;/code&gt; method to retrieve the saved &lt;code&gt;geometry&lt;/code&gt; value. Then, we use &lt;code&gt;restoreGeometry()&lt;/code&gt; to restore the geometry of our window.&lt;/p&gt;
&lt;p&gt;Again, you can run the application consecutive times and change the position and size of its main window to ensure your code works correctly.&lt;/p&gt;
&lt;h2 id="restoring-the-windows-geometry-and-state"&gt;Restoring the Window's Geometry and State&lt;/h2&gt;
&lt;p&gt;If your app's window has &lt;a href="https://www.pythonguis.com/tutorials/pyqt-actions-toolbars-menus/"&gt;toolbars&lt;/a&gt; and &lt;a href="https://doc.qt.io/qt-5/qdockwidget.html"&gt;dock widgets&lt;/a&gt;, then you want to restore their &lt;em&gt;state&lt;/em&gt; on the parent window. To do that, you can use the &lt;a href="https://doc.qt.io/qt-5/qmainwindow.html#restoreState"&gt;&lt;code&gt;restoreState()&lt;/code&gt;&lt;/a&gt; method. To illustrate this, let's reuse the code from the previous section.&lt;/p&gt;
&lt;p&gt;Update the content of &lt;code&gt;write_settings()&lt;/code&gt; and &lt;code&gt;read_settings()&lt;/code&gt; 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;class Window(QMainWindow):
    # ...

    def write_settings(self):
        self.settings.setValue("geometry", self.saveGeometry())
        self.settings.setValue("windowState", self.saveState())

    def read_settings(self):
        self.restoreGeometry(self.settings.value("geometry", QByteArray()))
        self.restoreState(self.settings.value("windowState", QByteArray()))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In &lt;code&gt;write_settings()&lt;/code&gt;, we add a new setting value called &lt;code&gt;"windowState"&lt;/code&gt;. To keep this setting, we use the &lt;a href="https://doc.qt.io/qt-5/qmainwindow.html#saveState"&gt;&lt;code&gt;saveState()&lt;/code&gt;&lt;/a&gt; method, which saves the current state of this window's toolbars and dock widgets. Meanwhile, in &lt;code&gt;read_settings()&lt;/code&gt;, we restore the window's state by calling the &lt;code&gt;value()&lt;/code&gt; method, as usual, to get the state value back from our &lt;code&gt;QSettings&lt;/code&gt; object. Finally, we use &lt;code&gt;restoreState()&lt;/code&gt; to restore the state of toolbars and dock widgets.&lt;/p&gt;
&lt;p&gt;Now, to make sure that this new code works as expected, let's add a sample toolbar and a dock window to our app's main window. Go ahead and add the following methods right after the &lt;code&gt;__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 PyQt5.QtCore import QByteArray, QSettings, Qt
from PyQt5.QtWidgets import QApplication, QDockWidget, QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Window's State")
        self.resize(400, 200)
        self.settings = QSettings("PythonGUIs", "GeometryApp")
        self.create_toolbar()
        self.create_dock()
        self.read_settings()

    def create_toolbar(self):
        toolbar = self.addToolBar("Toolbar")
        toolbar.addAction("One")
        toolbar.addAction("Two")
        toolbar.addAction("Three")

    def create_dock(self):
        dock = QDockWidget("Dock", self)
        dock.setAllowedAreas(
            Qt.DockWidgetArea.LeftDockWidgetArea
            | Qt.DockWidgetArea.RightDockWidgetArea
        )
        self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock)

    # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this new update, we first import the &lt;code&gt;Qt&lt;/code&gt; namespace from &lt;code&gt;PyQt5.QtCore&lt;/code&gt; and &lt;code&gt;QDockWidget&lt;/code&gt; from &lt;code&gt;PyQt5.QtWidgets&lt;/code&gt;. Then we call the two new methods from &lt;code&gt;__init__()&lt;/code&gt; to create the toolbar and dock widget at initialization time.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;create_toolbar()&lt;/code&gt; method, we create a sample toolbar with three sample buttons. This toolbar will show at the top of our app's window by default.&lt;/p&gt;
&lt;p&gt;Next, we create a dock widget in &lt;code&gt;create_dock()&lt;/code&gt;. This widget will occupy the rest of our window's working area.&lt;/p&gt;
&lt;p&gt;That's it! You're now ready to give your app a try. You'll see a window like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Window Showing a Sample Toolbar and a Dock Widget" src="https://www.pythonguis.com/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/restore-window-geometry-pyqt/window-state.png?tr=w-600 600w" loading="lazy" width="800" height="456"/&gt;
&lt;em&gt;A Window Showing a Sample Toolbar and a Dock Widget&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Play with the toolbar and the dock widget. Move them around. Then close the app's window and run the app again. Your toolbar and dock widget will show in the last position you left them.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Through this tutorial, you have learned how to restore the &lt;strong&gt;geometry&lt;/strong&gt; and &lt;strong&gt;state&lt;/strong&gt; of a window in PyQt applications using the &lt;code&gt;QSettings&lt;/code&gt; class. By utilizing the &lt;code&gt;pos&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;geometry&lt;/code&gt;, and state properties, you can give your users the convenience of persistent position and size on your app's windows.&lt;/p&gt;
&lt;p&gt;With this knowledge, you can enhance the usability of your PyQt applications,  making your app more intuitive and user-friendly.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="pyqt5"/><category term="python"/><category term="qt"/><category term="qt5"/></entry><entry><title>PyQt6 Book Now Available in Korean: 파이썬과 Qt6로 GUI 애플리케이션 만들기 — The hands-on guide to creating Python GUI applications with PyQt6 gets a Korean translation</title><link href="https://www.pythonguis.com/blog/pyqt6-book-translated-korean/" rel="alternate"/><published>2023-05-04T09:00:00+00:00</published><updated>2023-05-04T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2023-05-04:/blog/pyqt6-book-translated-korean/</id><summary type="html">I am very happy to announce that my Python GUI programming book
&lt;strong&gt;Create GUI Applications with Python &amp;amp; Qt6 &amp;mdash; PyQt6 Edition&lt;/strong&gt; is now available
in Korean from &lt;a href="http://www.acornpub.co.kr/book/python-qt6"&gt;Acorn Publishing&lt;/a&gt;.</summary><content type="html">
            &lt;p&gt;I am very happy to announce that my Python GUI programming book
&lt;strong&gt;Create GUI Applications with Python &amp;amp; Qt6 &amp;mdash; PyQt6 Edition&lt;/strong&gt; is now available
in Korean from &lt;a href="http://www.acornpub.co.kr/book/python-qt6"&gt;Acorn Publishing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Korean edition, titled &lt;strong&gt;파이썬과 Qt6로 GUI 애플리케이션 만들기&lt;/strong&gt;, is a complete translation of the original English PyQt6 book, covering everything you need to build professional desktop applications with Python and Qt6.&lt;/p&gt;
&lt;h2 id="whats-inside-the-korean-pyqt6-book"&gt;What's Inside the Korean PyQt6 Book&lt;/h2&gt;
&lt;p&gt;This hands-on guide covers all the fundamentals of Python GUI development with PyQt6, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating windows, dialogs and widgets with PyQt6&lt;/li&gt;
&lt;li&gt;Building responsive layouts for desktop applications&lt;/li&gt;
&lt;li&gt;Working with Qt signals, slots and events&lt;/li&gt;
&lt;li&gt;Using Qt Designer for visual UI design&lt;/li&gt;
&lt;li&gt;Packaging and distributing your Python GUI applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whether you're a beginner to GUI programming or an experienced Python developer looking to build desktop applications, this book provides a practical, project-based approach to learning PyQt6.&lt;/p&gt;
&lt;h2 id="a-personal-note"&gt;A Personal Note&lt;/h2&gt;
&lt;p&gt;It's more than a little mind-blowing to see a book I've written translated into
another language &amp;mdash; not least one I cannot remotely understand! When I started
writing this book a few years ago I could never have imagined it would end up on bookshelves
in Korea, never mind in Korean. This is just fantastic.&lt;/p&gt;
&lt;p&gt;&lt;img alt="파이썬과 Qt6로 GUI 애플리케이션 만들기 &amp;mdash; PyQt6 Book Korean Edition" src="https://www.pythonguis.com/static/blog/pyqt6-book-translated-korean/pyqt6-book-korean.jpg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/blog/pyqt6-book-translated-korean/pyqt6-book-korean.jpg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/blog/pyqt6-book-translated-korean/pyqt6-book-korean.jpg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/blog/pyqt6-book-translated-korean/pyqt6-book-korean.jpg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/blog/pyqt6-book-translated-korean/pyqt6-book-korean.jpg?tr=w-600 600w" loading="lazy" width="279" height="350"/&gt;
&lt;em&gt;파이썬과 Qt6로 GUI 애플리케이션 만들기 &amp;mdash; 파이썬 애플리케이션 제작 실습 가이드&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="where-to-buy-the-korean-pyqt6-book"&gt;Where to Buy the Korean PyQt6 Book&lt;/h2&gt;
&lt;p&gt;If you're in Korea, you can pick up a copy at any of the following bookstores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://product.kyobobook.co.kr/detail/S000201323866"&gt;Kyobobook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.yes24.com/Product/Goods/118042593"&gt;YES24&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=313563300"&gt;Aladin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks again to Acorn Publishing for translating my book and making it available to readers in Korea.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qt6"/><category term="python"/><category term="books"/><category term="korean"/><category term="qt"/></entry><entry><title>PyQt vs. Tkinter — Which Should You Choose for Your Next GUI Project? — What Are the Major Differences Between these Popular Python GUI Libraries</title><link href="https://www.pythonguis.com/faq/pyqt-vs-tkinter/" rel="alternate"/><published>2023-04-12T06:00:00+00:00</published><updated>2023-04-12T06:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2023-04-12:/faq/pyqt-vs-tkinter/</id><summary type="html">Graphical User Interfaces (GUIs) allow users to interact with software through intuitive and user-friendly graphical elements such as buttons, icons, text boxes, and windows. GUIs provide a simple way to perform complex tasks and navigate a software system using the mouse and keyboard and without needing to remember complex commands. By using familiar visual concepts across different tools and platforms, GUIs help make new software feel intuitive and user-friendly, even for beginners.</summary><content type="html">
            &lt;p&gt;Graphical User Interfaces (GUIs) allow users to interact with software through intuitive and user-friendly graphical elements such as buttons, icons, text boxes, and windows. GUIs provide a simple way to perform complex tasks and navigate a software system using the mouse and keyboard and without needing to remember complex commands. By using familiar visual concepts across different tools and platforms, GUIs help make new software feel intuitive and user-friendly, even for beginners.&lt;/p&gt;
&lt;p&gt;While Python is more commonly used for command-line tools, data science, and web apps, it is also perfectly capable of building graphical desktop applications. The Python ecosystem makes it possible to build almost anything, from small user-friendly interfaces for your scripts to more complex data analysis or engineering tools. Whether you're a Python beginner or a seasoned developer, learning how to build Python GUI apps is an excellent addition to your skill set.&lt;/p&gt;
&lt;h2 id="an-introduction-to-python-gui-libraries"&gt;An Introduction to Python GUI Libraries&lt;/h2&gt;
&lt;p&gt;One of Python's core strengths is the rich ecosystem of libraries and frameworks available. GUI programming libraries are not an exception -- you'll find &lt;a href="https://www.pythonguis.com/faq/which-python-gui-library/"&gt;several GUI libraries for Python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While having multiple choices is great, it means that if you want to learn how to write GUI applications with Python, the first question you will need to answer is -- &lt;em&gt;Which Python GUI library should I use?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Even though many GUI libraries are available, none has the widespread adoption of &lt;a href="https://www.pythonguis.com/topics/tkinter-foundation/"&gt;Tkinter&lt;/a&gt; and &lt;a href="https://www.pythonguis.com/pyqt6/"&gt;PyQt&lt;/a&gt;. So, if you're starting with GUI programming in Python, it'll make sense to start with one of these. There are far more tutorials, help &amp;amp; resources available to get you up and running, and you're more likely to find help if you need it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Google Trends Plot Ranking Tkinter, PyQt, and Other Python GUI Libraries" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/google-trends-plot-tkinter-vs-pyqt.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/google-trends-plot-tkinter-vs-pyqt.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/google-trends-plot-tkinter-vs-pyqt.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/google-trends-plot-tkinter-vs-pyqt.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/google-trends-plot-tkinter-vs-pyqt.png?tr=w-600 600w" loading="lazy" width="1414" height="373"/&gt;
&lt;em&gt;Google Trends Plot Ranking Tkinter, PyQt, and Other Python GUI Libraries&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The popularity of Tkinter largely stems from it being bundled with Python and, therefore, being the &lt;em&gt;default&lt;/em&gt; Python GUI library. Most beginners will find this library first.&lt;/p&gt;
&lt;p&gt;PyQt is often seen as the next logical step in your Python GUI journey when you want to start building real applications or commercial-quality software with Python.&lt;/p&gt;
&lt;p&gt;Whether you choose Tkinter or PyQt will largely depend on your goals for writing GUI applications.&lt;/p&gt;
&lt;p&gt;In this article, we'll explore and compare &lt;strong&gt;Tkinter&lt;/strong&gt; and &lt;strong&gt;PyQt&lt;/strong&gt;. We'll assess their pros &amp;amp; cons for different use cases and help you decide which Python GUI library to use for &lt;em&gt;your&lt;/em&gt; project.&lt;/p&gt;
&lt;h2 id="what-is-tkinter"&gt;What Is Tkinter?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Best for&lt;/strong&gt; simple tool GUIs, small portable applications&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Tkinter is the default GUI library for Python. It comes bundled with Python on both Windows and macOS. On Linux, you may have to install additional packages to get it set up.&lt;/p&gt;
&lt;p&gt;The library is a wrapper around the &lt;a href="https://www.tcl.tk/"&gt;Tcl/Tk GUI toolkit&lt;/a&gt;. Its name is an amalgamation of the words Tk and Interface.&lt;/p&gt;
&lt;p&gt;Tkinter supports standard layouts and basic widgets, as well as more complex widgets, such as tabbed views &amp;amp; progress bars. Tkinter is a pure GUI library rather than a GUI framework. It only provides a set of graphical components for building GUIs.&lt;/p&gt;
&lt;p&gt;It doesn't provide support for GUIs-driven data sources, databases, or for displaying or manipulating multimedia or hardware. However, if you need to make something simple that doesn't require any additional dependencies, Tkinter may be what you are looking for.&lt;/p&gt;
&lt;p&gt;Tkinter is a cross-platform GUI library. It was first released in 1990, and it has continued to evolve until today. For example, the addition of the &lt;em&gt;Themed Tk&lt;/em&gt; extensions in 2009 improved the appearance of widgets, giving a native look &amp;amp; feel to your Tkinter GUIs.&lt;/p&gt;
&lt;p&gt;Tkinter is a limited library with a friendly &lt;a href="https://en.wikipedia.org/wiki/API"&gt;API (application programming interface)&lt;/a&gt; that makes it easy to understand and learn. Because of this, Tkinter tends to be the first choice for creating quick GUIs for relatively small Python programs.&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 learn more about how to use Tkinter, check out &lt;a href="https://www.pythonguis.com/tkinter-tutorial/"&gt;Build your own desktop apps with Python &amp;amp; Tkinter&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How Do You Install Tkinter?&lt;/h3&gt;
&lt;p&gt;Tkinter comes installed by default with Python on macOS and Windows. On some Linux systems, you may need to &lt;a href="https://www.pythonguis.com/installation/install-tkinter-linux/"&gt;install an additional package&lt;/a&gt;. Tkinter also runs fine on a Raspberry Pi, although depending on your distribution, you may need to &lt;a href="https://www.pythonguis.com/installation/install-tkinter-linux/"&gt;install it first&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How Can You Write a Tkinter App?&lt;/h3&gt;
&lt;p&gt;A minimal &lt;em&gt;Hello, World!&lt;/em&gt; GUI application in Tkinter will look something 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;import tkinter as tk

# Create the app's main window
window = tk.Tk()
window.title("Hello, World!")

def handle_button_press():
    window.destroy()

button = tk.Button(text="My simple app.", command=handle_button_press)
button.pack()

# Start the event loop
window.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we first import &lt;code&gt;tkinter&lt;/code&gt; as &lt;code&gt;tk&lt;/code&gt;, a common practice when using Tkinter. We then create our app's main window by instantiating the &lt;code&gt;Tk&lt;/code&gt; class. The &lt;code&gt;title()&lt;/code&gt; method allows us to give a descriptive title to the app's windows. Next we write a function to responding to a click on the button. In this case our method closes the application window &amp;amp; terminates it.&lt;/p&gt;
&lt;p&gt;To create the button, we use the &lt;code&gt;Button&lt;/code&gt; class with an appropriate text. In order to trigger our &lt;code&gt;handle_button_press()&lt;/code&gt;  function when the button is pressed, we pass the function to &lt;code&gt;Button&lt;/code&gt; as the &lt;em&gt;command&lt;/em&gt; argument.&lt;/p&gt;
&lt;p&gt;Next, we call &lt;code&gt;pack()&lt;/code&gt; on our button object. This organizes our widget into the window layout -- although in this case, there is only a single widget. Finally, we run the app by calling &lt;code&gt;mainloop()&lt;/code&gt; on our &lt;code&gt;window&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;If you run the example, you'll see a simple window like follows.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Tkinter App Using Tk Standard Widgets" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/tkinter-demo-app.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app.png?tr=w-600 600w" loading="lazy" width="328" height="229"/&gt;
&lt;em&gt;A Tkinter App Using Tk Standard Widgets&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, by default, Tkinter has very rudimentary-looking widgets.&lt;/p&gt;
&lt;p&gt;We can modify the example to use Themed Tk widgets, which give a native appearance to widgets. The changes are minor, adding a &lt;code&gt;from tkinter import ttk&lt;/code&gt; import and using &lt;code&gt;ttk.&amp;lt;Widget&amp;gt;&lt;/code&gt; when constructing 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 tkinter as tk
from tkinter import ttk

# Create the app's main window
window = tk.Tk()
window.title("Hello, World!")

def handle_button_press():
    window.destroy()

button = ttk.Button(text="My simple app.", command=handle_button_press)
button.pack()

# Start the event loop
window.mainloop()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run this example, the simple window will appear again but now using platform-native widgets (what you see will depend on your own system).&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Tkinter App Using Themed Tk Widgets" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/tkinter-demo-app-ttk-theme.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app-ttk-theme.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app-ttk-theme.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app-ttk-theme.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/tkinter-demo-app-ttk-theme.png?tr=w-600 600w" loading="lazy" width="343" height="251"/&gt;
&lt;em&gt;A Tkinter App Using Themed Tk Widgets&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;What Is Tkinter Commonly Used for?&lt;/h3&gt;
&lt;p&gt;Tkinter is typically used for simple Python GUIs. For example, small proof-of-concept, research, or educational apps built by Python hobbyists, and the like. The non-native appearance of Tkinter apps and lack of a real application framework make this library unsuitable for building professional applications, particularly commercial software.&lt;/p&gt;
&lt;p&gt;While it's possible to put something simple together with Tkinter, as your application grows or your requirements become more complex, you'll likely end up spending more time reinventing the wheel than you saved by using a "simple" system. So, if you expect your project to grow in the future, then you should start with PyQt instead.&lt;/p&gt;
&lt;h3&gt;Is Tkinter Outdated?&lt;/h3&gt;
&lt;p&gt;The complaints of Tkinter being outdated largely stem from how the apps built with the library look on modern systems compared to other apps. Another important complaint driver is the library's limited set of widgets.&lt;/p&gt;
&lt;p&gt;Tk, the library around which Tkinter was built, was first released in 1991. However, it's been continuously developed &amp;amp; maintained. In 2009, Tkinter added support for Tk 8.5 "Themed Tk" (Ttk), which allows Tk widgets to be more easily themed to &lt;em&gt;look like&lt;/em&gt; the native on different desktop environments. Ttk also added some additional widgets, such as combo boxes, progress bars, tree views, notebooks, separators, and size grips, which weren't available in default Tk.&lt;/p&gt;
&lt;h3&gt;Why Does Tkinter Look Old-Fashioned?&lt;/h3&gt;
&lt;p&gt;It doesn't have to! Tkinter's reputation for looking &lt;em&gt;bad&lt;/em&gt; stems from earlier versions of Tk without the native platform-theming support. These early versions used an old cross-platform "Motif" style, which was blocky and a bit ugly.&lt;/p&gt;
&lt;p&gt;Tk 8.5 added support for desktop native theming, so your applications now follow the style of the desktop they are running on. This new look is provided through Themed Tk, which was added to Tkinter in 2009.&lt;/p&gt;
&lt;p&gt;If you don't want a native appearance but still want to improve how your Tkinter applications look, there are other options too. For example, &lt;a href="https://github.com/ParthJadhav/Tkinter-Designer"&gt;Tkinter Designer&lt;/a&gt; allows you to design graphical interfaces using &lt;a href="https://www.figma.com/"&gt;Figma&lt;/a&gt;. Once you have the interface ready, then you can export it into a working Tkinter app.&lt;/p&gt;
&lt;h3&gt;How Can You Design GUIs for Tkinter?&lt;/h3&gt;
&lt;p&gt;There isn't an official design tool for Tkinter GUIs. However, you can find some tools available online. For example, &lt;a href="https://visualtk.com/"&gt;Visual Tk&lt;/a&gt; allows you to build a GUI using a drag-drop interface in your browser &amp;amp; will then generate the Python code for you to create the interface in Tkinter itself.&lt;/p&gt;
&lt;p&gt;Alternatively, &lt;a href="https://github.com/ParthJadhav/Tkinter-Designer"&gt;Tkinter Designer&lt;/a&gt; will take a design drawn in the Figma software and use it to build a Tkinter-based GUI.&lt;/p&gt;
&lt;h3&gt;What Are Tkinter's Pros &amp;amp; Cons?&lt;/h3&gt;
&lt;p&gt;You can find plenty of reasons to consider Tkinter for your next project and reasons you may want to try something else. Let's take a look at some of the pros &amp;amp; cons of using Tkinter to build GUI applications with Python so that you can be in a better position to decide.&lt;/p&gt;
&lt;p&gt;The pros of using Tkinter include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is part of the Python standard library and comes installed by default (on Windows and macOS). Once you install Python, you're ready to start building GUIs with Tkinter.&lt;/li&gt;
&lt;li&gt;It doesn't have additional dependencies for your applications unless you need third-party libraries for additional functionality.&lt;/li&gt;
&lt;li&gt;It is relatively simple, meaning there isn't much to take in while learning to use it.&lt;/li&gt;
&lt;li&gt;It is a cross-platform library.&lt;/li&gt;
&lt;li&gt;It has a lot of documentation and tutorials available.&lt;/li&gt;
&lt;li&gt;It can be used freely for commercial software because its license is the same as Python's.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the other hand, the cons of using Tkinter include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It doesn't come with advanced widgets, such as data-driven views, database interfaces, vector graphics canvas, or multimedia GUI elements. To implement any of these in your applications, you need to write them yourself. This is achievable but will add to your development &amp;amp; maintenance burden.&lt;/li&gt;
&lt;li&gt;It has no official GUI designer application. You'll find a few third-party options available with varying degrees of completeness and flexibility.&lt;/li&gt;
&lt;li&gt;It lacks bundled features, which means that you're more likely to need third-party libraries to complete your applications. Luckily, there is no shortage of those in the Python ecosystem!&lt;/li&gt;
&lt;li&gt;It doesn't have a native look &amp;amp; feel by default. The default appearance of Tkinter applications is old-fashioned. However, this is largely fixed by the Themed Tk extensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you have a better idea of what Tkinter is and what are its main features and limitations, it's time for you to know its closest competitor, PyQt.&lt;/p&gt;
&lt;h2 id="what-is-pyqt"&gt;What Is PyQt?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Best for&lt;/strong&gt; desktop applications, multimedia, scientific, and engineering software.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;PyQt is a Python GUI framework built around the C++ Qt framework, which is developed and maintained by the Qt Company. It allows us to create modern interfaces that look right at home on any platform, including Windows, macOS, Linux, and even Android. It also has solid tooling, with the most notable being &lt;a href="https://www.pythonguis.com/faq/why-is-the-qt-creator-used-in-the-tutorial/"&gt;Qt Creator&lt;/a&gt;, which includes a WYSIWYG editor for designing GUI interfaces quickly and easily.&lt;/p&gt;
&lt;p&gt;PyQt is developed and maintained by Riverbank Computing and was first released in 1998, four years after Tkinter.&lt;/p&gt;
&lt;p&gt;The free-to-use version of PyQt is licensed under &lt;a href="https://www.gnu.org/licenses/gpl-3.0.en.html"&gt;GNU General Public License (GPL) v3&lt;/a&gt;. This means that PyQt is limited to GPL-licensed applications unless you purchase its &lt;a href="https://www.riverbankcomputing.com/commercial/buy"&gt;commercial license&lt;/a&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;  The Qt Company has its own Python binding for Qt, which is called PySide. This library was released in 2009. The main difference between PyQt and PySide is in licensing. PySide is licensed under GNU Lesser General Public License (LGPL), which means that you use PySide in non-GPL applications without any additional fee. For a detailed breakdown of the differences, see our guide on &lt;a href="https://www.pythonguis.com/faq/pyqt6-vs-pyside6/"&gt;PyQt6 vs PySide6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Qt, and by extension PyQt, is not just a GUI library. It's a complete GUI application development framework. In addition to standard GUI components, such as widgets and layouts, Qt provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller"&gt;MVC (model-view-controller)&lt;/a&gt; data-driven views (spreadsheets, tables)&lt;/li&gt;
&lt;li&gt;Database interfaces and models&lt;/li&gt;
&lt;li&gt;Graph plotting&lt;/li&gt;
&lt;li&gt;Vector graphics visualization&lt;/li&gt;
&lt;li&gt;Multimedia playback&lt;/li&gt;
&lt;li&gt;Sound effects and playlists&lt;/li&gt;
&lt;li&gt;Interfaces for hardware, such as printing devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Qt's &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt; mechanism for event programming allows us to properly architect complex applications from reusable and isolated components.&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 learn more about how to use PyQt, check out &lt;a href="https://www.pythonguis.com/pyqt6-tutorial/"&gt;Build your own desktop apps with Python &amp;amp; PyQt6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While other toolkits can work great when building small Python tools, PyQt really comes into its own for building commercial-quality applications where we benefit from the pre-built components. These benefits come at the expense of a steep learning curve but don't feel overwhelmed yet, you can just focus on the parts your project needs.&lt;/p&gt;
&lt;p&gt;PyQt-based applications use platform-native widgets to ensure that they look and feel native on Windows, macOS, and Qt-based Linux desktop environments.&lt;/p&gt;
&lt;h3&gt;How Do You Install PyQt?&lt;/h3&gt;
&lt;p&gt;PyQt v6.4.2 is the latest version of the library. To run it on your computer, make sure you have Python v3.6.1 or later installed. To install it, just run the following 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;$ python -m pip install pyqt6
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command will install the PyQt6 library for your platform and your version of Python. The library will be automatically downloaded from &lt;a href="https://pypi.org/project/PyQt6/"&gt;PyPI&lt;/a&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;  If you want to use Qt's own &lt;em&gt;official&lt;/em&gt; Python library, you can install PySide with &lt;code&gt;python -m pip install pyside6&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As of writing, only &lt;a href="https://www.pythonguis.com/pyqt5/"&gt;PyQt5&lt;/a&gt; is currently supported on Raspberry Pi. But you can use both the Qt Widgets (standard) and QML/Qt Quick (declarative) APIs. You can use QML to build modern touchscreen interfaces with animations, transitions, and effects.&lt;/p&gt;
&lt;p&gt;To install PyQt5 on a Raspberry Pi, run the following 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;$ sudo apt-get install python3-pyqt5
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command will install PyQt5 from your Raspberry Pi current repository. Note that the Qt framework will also be installed as a dependency.&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;  For other installation options, see &lt;a href="https://www.pythonguis.com/installation/"&gt;our complete installation guides&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How Can You Write a PyQt App?&lt;/h3&gt;
&lt;p&gt;A minimal &lt;em&gt;Hello, World!&lt;/em&gt; GUI application in PyQt6, using the Qt Widgets 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;from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

# Create the app's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Hello, World!")

        button = QPushButton("My simple app.")
        button.pressed.connect(self.close)

        self.setCentralWidget(button)
        self.show()

# Create the app, the main window, and run the app
app = QApplication([])
window = MainWindow()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we first import the required classes from &lt;code&gt;PyQt6.QtWidgets&lt;/code&gt;. We then define the &lt;code&gt;MainWindow&lt;/code&gt; class, which will provide the app's main window. The window will have a title and a button. This button is connected to the &lt;code&gt;close()&lt;/code&gt; method inherited from &lt;code&gt;QMainWindow&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, we instantiated &lt;code&gt;QApplication&lt;/code&gt; and &lt;code&gt;MainWindow&lt;/code&gt;. To run the application, we call the &lt;code&gt;exec()&lt;/code&gt; method on the &lt;code&gt;app&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;When run, you'll get a window with the title "Hello, World!" and containing a single push button that says "My simple app."&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Windowed App With a Push Button" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/pyqt-demo-app.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-demo-app.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-demo-app.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-demo-app.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-demo-app.png?tr=w-600 600w" loading="lazy" width="385" height="277"/&gt;
&lt;em&gt;A Windowed App With a Push Button&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That's a very simple example. To showcase the real capabilities of PyQt and the Qt framework, below is a more complex example consisting of a minimal but working 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;from PyQt6.QtCore import QUrl
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QApplication, QMainWindow

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

        self.browser = QWebEngineView()
        self.browser.setUrl(QUrl("https://www.google.com"))

        self.setCentralWidget(self.browser)

        self.show()

app = QApplication([])
window = MainWindow()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To make this code work, you need the Qt WebEngine extensions. This is a core part of Qt's library but is installed separately due to its size. Run the following to install this from the command line.&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-WebEngine
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With &lt;code&gt;PyQt6-WebEngine&lt;/code&gt; installed, you can now use the &lt;code&gt;QWebEngineView&lt;/code&gt; class in your own applications.&lt;/p&gt;
&lt;p&gt;If you run the code above, you'll get a minimal web browser that loads up Google &amp;amp; lets you browse from there.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Minimal Web Browser, Written in PyQt" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/pyqt-webbrowser.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-webbrowser.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-webbrowser.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-webbrowser.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-webbrowser.png?tr=w-600 600w" loading="lazy" width="906" height="620"/&gt;
&lt;em&gt;A Minimal Web Browser, Written in PyQt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For a final example, we'll create a quick video player. This example uses layouts to build a simple GUI with a viewer and a button. You can press the button to launch a platform native file picker dialog:&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 QSize, QUrl
from PyQt6.QtMultimedia import QMediaPlayer
from PyQt6.QtMultimediaWidgets import QVideoWidget
from PyQt6.QtWidgets import (
    QApplication,
    QFileDialog,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Player")

        self.viewer = QVideoWidget()
        self.viewer.setMinimumSize(QSize(800, 400))
        self.player = QMediaPlayer()

        self.loadVideoBtn = QPushButton("Open video file...")
        self.loadVideoBtn.pressed.connect(self.openVideFile)

        layout = QVBoxLayout()
        layout.addWidget(self.viewer)
        layout.addWidget(self.loadVideoBtn)
        self.setLayout(layout)

    def openVideFile(self):
        filename, _ = QFileDialog.getOpenFileName(
            self,
            caption="Open video file",
            filter="MP4 Video (*.mp4)",
        )

        if filename:
            video = QUrl(filename)
            self.player.setSource(video)
            self.player.setVideoOutput(self.viewer)
            self.player.play()

app = QApplication([])
window = Window()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this example you'll get a very basic video player that's capable of loading and playing videos in MP4 format.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A Minimal MP4 Video Player, Written in PyQt" src="https://www.pythonguis.com/static/faq/tkinter-vs-pyqt/pyqt-video-player.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-video-player.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-video-player.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-video-player.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/tkinter-vs-pyqt/pyqt-video-player.png?tr=w-600 600w" loading="lazy" width="1201" height="723"/&gt;
&lt;em&gt;A Minimal MP4 Video Player, Written in PyQt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The above examples should give you an idea of how powerful PyQt actually is. As you can see, there is much more to it than just a set of simple GUI widgets.&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;  For some more demo PyQt applications, &lt;a href="https://www.pythonguis.com/examples/"&gt;see our library of examples&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;What Is PyQt Commonly Used for?&lt;/h3&gt;
&lt;p&gt;PyQt is most commonly used for, and particularly well suited for, building full-featured GUI desktop applications. As you already learned, PyQt supports (MVC-like) data-driven views, vector graphics, animations &amp;amp; transitions, databases, and threading/concurrency.&lt;/p&gt;
&lt;p&gt;Qt Designer, the GUI creator provided by Qt, allows you to build professional quality software in no time. The signals and slots mechanism makes it possible to properly decouple the components of an application, allowing for robust and maintainable system architectures.&lt;/p&gt;
&lt;p&gt;You can also use PyQt for building touchscreen user interfaces for Raspberry Pi-powered hardware -- both using the Qt Widgets and QML/Qt Quick APIs. While PyQt can technically be used to build apps for mobile devices, this type of apps is rarely seen outside of the hobbyist space.&lt;/p&gt;
&lt;h3&gt;Can You Use PyQt for Open-Source and Commercial Apps?&lt;/h3&gt;
&lt;p&gt;Yes, you can! PyQt is free to use for personal development and GPL-licensed open-source software. For non-GPL software, such as commercially licensed software, a license is required from Riverbank Computing.&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 Qt for Python framework ---PySide--- by the Qt Company is licensed under the LGPL. If you use PySide, then you don't need to license your software under the GPL (or LGPL). As a result, you don't have to share your application's source code. So, if you don't want to buy a commercial license for PyQt, then you can use PySide, which &lt;a href="https://www.pythonguis.com/faq/pyqt6-vs-pyside6/"&gt;doesn't require a license for use in commercial software&lt;/a&gt;. For a deeper dive into the licensing differences, see our article on &lt;a href="https://www.pythonguis.com/faq/gpl-and-copyleft-pyqt-pyside/"&gt;GPL and copyleft in PyQt &amp;amp; PySide&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How Can You Design GUIs for PyQt Apps?&lt;/h3&gt;
&lt;p&gt;The Qt framework provides Qt Designer, which is a drag-drop UI editor. You can use Qt Designer to design modern and intuitive interfaces for your PyQt applications quickly. The interfaces generated using Qt Designer can be either loaded as-is in your applications or converted to Python code that you can then import into your apps.&lt;/p&gt;
&lt;p&gt;On Windows, you can install Qt Designer and other Qt tools by using &lt;code&gt;pip&lt;/code&gt; as follows:&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 -m pip install pyqt6-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This command installs Qt Designer from PyPI. To run the GUI editor, you can execute the &lt;code&gt;designer.exe&lt;/code&gt; app from your &lt;code&gt;Script&lt;/code&gt; directory.&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 use PySide, then Qt Designer will be installed by default with the library. You can also &lt;a href="https://www.pythonguis.com/installation/install-qt-designer-standalone/"&gt;install Qt Designer as a standalone application&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you can also build GUIs from scratch using Python code. Whether you use Qt Designer or code is entirely up to you. The best choice will largely depend on your project.&lt;/p&gt;
&lt;h3&gt;What Are PyQt's Pros and Cons?&lt;/h3&gt;
&lt;p&gt;There are a number of reasons you may want to choose PyQt for your project and reasons you may not. Let's take a look at some of the pros and cons of using PyQt to build GUI applications with Python.&lt;/p&gt;
&lt;p&gt;To kick things off, let's start with the pros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is a powerful &amp;amp; modern GUI framework that is suitable for building professional applications.&lt;/li&gt;
&lt;li&gt;It includes several advanced widgets, including data-driven views, charts, database interfaces, vector graphics canvas, video playback, and a fully-functional web browser component.&lt;/li&gt;
&lt;li&gt;It can take advantage of Qt Designer, which allows you to design GUIs using a graphical drag-and-drop editor.&lt;/li&gt;
&lt;li&gt;It is cross-platform and can run on Windows, Linux, macOS, and mobile devices.&lt;/li&gt;
&lt;li&gt;It provides modern and native-looking GUI components out of the box in all the major platforms. These components can be largely customized if required.&lt;/li&gt;
&lt;li&gt;It is a batteries-included library, which means that you can accomplish many things with PyQt directly. This characteristic means less need for third-party dependencies.&lt;/li&gt;
&lt;li&gt;It has plenty of support and online learning resources, including our own complete &lt;a href="https://www.pythonguis.com/pyqt6-tutorial/"&gt;PyQt tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It allows the creation of touchscreen interfaces with the QML/Qt Quick API.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even though PyQt has many neat features and advantages, it also has some cons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It can be intimidating to beginners. The size of the library and its complex feature set can make it overwhelming, to begin with.&lt;/li&gt;
&lt;li&gt;It has poor and incomplete Python &lt;a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/index.html"&gt;documentation&lt;/a&gt;. As an alternative, you can use the official Qt documentation. However, this documentation is for the C++ library and can be hard to translate.&lt;/li&gt;
&lt;li&gt;It will take time to fully learn the framework and how it works.&lt;/li&gt;
&lt;li&gt;It allows for a GPL or commercial license only.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Up to this point, you've learned a lot about Tkinter and PyQt. To make your life more pleasant, the following section provides a quick and summarized comparison between these two Python GUI tools.&lt;/p&gt;
&lt;h2 id="tkinter-vs-pyqt-a-side-by-side-feature-comparison"&gt;Tkinter vs PyQt: A Side-by-Side Feature Comparison&lt;/h2&gt;
&lt;p&gt;Now that we have a good understanding of Tkinter and PyQt, let's put them head-to-head on some key features:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Tkinter&lt;/th&gt;
&lt;th&gt;PyQt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Installation &amp;amp; Setup&lt;/td&gt;
&lt;td&gt;It's part of the Python installer on Windows and macOS. It may require additional packages on Linux.&lt;/td&gt;
&lt;td&gt;It needs to be installed separately using &lt;code&gt;pip&lt;/code&gt;. It can also be installed from the source.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;It uses the Python license.&lt;/td&gt;
&lt;td&gt;It uses GPL or a commercial license.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GUI Builder Tools&lt;/td&gt;
&lt;td&gt;It has no standard GUI builder app, but some third-party tools are available.&lt;/td&gt;
&lt;td&gt;It can use Qt Designer for building GUIs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Widgets Set&lt;/td&gt;
&lt;td&gt;It has a basic set of widgets for most common applications.&lt;/td&gt;
&lt;td&gt;It has basic and advanced widgets for building quite complex interfaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Framework&lt;/td&gt;
&lt;td&gt;It's not a framework, so you'll have to use third-party Python libraries to implement many features.&lt;/td&gt;
&lt;td&gt;It's a full GUI framework that includes databases, plotting, vector graphics, threading, multimedia, networking, and more.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Suitable for small, lightweight applications.&lt;/td&gt;
&lt;td&gt;Handles large-scale applications with complex UIs efficiently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Platform&lt;/td&gt;
&lt;td&gt;Windows, macOS, and Linux.&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux, and mobile devices.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As you can see, PyQt is the most feature-rich of the two libraries. It is more capable of both building the GUI and the backend of your application. That said, if you're building simple GUI tools, then Tkinter may still be more than enough. It'll all depend on your project.&lt;/p&gt;
&lt;h2 id="decision-time-how-to-choose-the-best-python-gui-library-for-your-project"&gt;Decision Time: How to Choose the Best Python GUI Library for Your Project&lt;/h2&gt;
&lt;p&gt;So far, we've looked in detail at each library, seen some example code, and explored the pros and cons of using each library. We've also addressed some common questions that come up when looking at the two libraries.&lt;/p&gt;
&lt;p&gt;By now, you have everything you need to decide which is best for your project. However, you may still be stuck on making the decision. In the following sections, we'll recap what we've learned about PyQt and Tkinter in the context of your own project and goals.&lt;/p&gt;
&lt;h3&gt;What Are Your Goals?&lt;/h3&gt;
&lt;p&gt;Which Python GUI library you pick is heavily dependent on your goals in writing GUI applications. If you are learning Python and just want to experiment, then Tkinter is a perfectly decent way to do that. On the other hand, if you are learning GUI development with a view to building professional applications, then it will make more sense to start with PyQt.&lt;/p&gt;
&lt;p&gt;Qt, which PyQt is built on, is a complete GUI application framework that provides tools and components for building modern applications. This feature offloads a lot of the manual and repetitive work, helping you build well-architected systems.&lt;/p&gt;
&lt;p&gt;Take, for example, loading a CSV file and displaying it in a table. In PyQt, you can write a small model to interface between the data source and the built-in table view. PyQt does all the work for you. It takes the data and displays it in an efficient way.&lt;/p&gt;
&lt;p&gt;In Tkinter, you will need to create the table by yourself, widget by widget, in a grid layout. Then, you would have to use some external tool for loading the data from the CSV file. Finally, you would have to find a way to display the data in your custom Tkinter table.&lt;/p&gt;
&lt;p&gt;In PyQt, you can have millions of rows in a table without problems using a &lt;em&gt;single&lt;/em&gt; widget created to render the entire table. In Tkinter, each cell will be an individual widget object ---even those outside the view---  meaning you'll quickly encounter problems if you work with large data.&lt;/p&gt;
&lt;h3&gt;Do You Need a GUI Library or a GUI Framework?&lt;/h3&gt;
&lt;p&gt;Tkinter is a &lt;em&gt;GUI library&lt;/em&gt;, while PyQt is a &lt;em&gt;GUI framework&lt;/em&gt;. While both allow you to build graphical user interfaces, they differ in the scope of what they include and what parts of your application they help you build.&lt;/p&gt;
&lt;p&gt;While a GUI library will help you add, position, and draw widgets in your application's GUI and hook those widgets up to your own code, a GUI framework provides additional functionalities, which are commonly required when building applications.&lt;/p&gt;
&lt;p&gt;For example, PyQt provides components for connecting to databases and creating semi-automatic model-views of database entries. It provides a vector graphics canvas, plotting, 3D rendering, networking, threading/concurrency, and more.&lt;/p&gt;
&lt;p&gt;Even though many of these features are already available in the Python standard library or in third-party libraries, most of them will not integrate as cleanly into a GUI program as those provided natively by the framework itself.&lt;/p&gt;
&lt;p&gt;The trade-off is between using a single, big dependency (PyQt) vs. lots of small dependencies (standard-library or third-party) to build your apps. Using a framework can speed up the development of complex projects because much of what you need is available out of the box. As mentioned, whether you get any benefit from the PyQt framework will largely depend on your specific project.&lt;/p&gt;
&lt;h3&gt;Is Tkinter Easier to Learn Than PyQt?&lt;/h3&gt;
&lt;p&gt;It &lt;em&gt;can&lt;/em&gt; be. Previously, there was a lack of beginner tutorials available for PyQt. This condition made it difficult to get started. That's no longer the case. Now you'll find lots of PyQt5 and PyQt6 tutorials available online.&lt;/p&gt;
&lt;p&gt;Our own &lt;a href="https://www.pythonguis.com/pyqt6-tutorial/"&gt;PyQt6 tutorial&lt;/a&gt; takes you from the absolute basics of GUI development concepts to building relatively complex applications. We're adding new tutorials regularly, covering both basic and advanced topics.&lt;/p&gt;
&lt;p&gt;There are more beginner-friendly tutorials available for Tkinter than PyQt. Basic Tkinter examples tend to avoid using subclasses, while PyQt examples default to using them, which can imply more reasoning if you're not familiar with &lt;a href="https://www.pythonguis.com/tutorials/python-classes/"&gt;object-oriented programming and inheritance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;PyQt introduces additional and complex concepts, such as signals and slots, which can be confusing for new developers. However, they are one of the most powerful parts of the framework, making it possible to build well-architected and maintainable software. The time taken to understand these things is well rewarded.&lt;/p&gt;
&lt;h3&gt;Which One Should You Learn First, Tkinter or PyQt?&lt;/h3&gt;
&lt;p&gt;There is little benefit in starting with Tkinter if you plan to switch to PyQt later. While PyQt is a large framework with thousands of classes and features, you don't need to learn all of it at once. While some of the basic GUI concepts you learn with Tkinter -- widgets, layouts, event-based programming -- will transfer over to PyQt, there are many other concepts that won't.&lt;/p&gt;
&lt;p&gt;As a developer, you'll be looking for tools that are easy to use, well-designed for the job, and at the same time, can grow with your projects. If you want to move on to develop professional or commercial software with Python, you don't want to start over again with an entirely new stack. If you think you are likely to want to migrate to PyQt later, then you may as well start there first.&lt;/p&gt;
&lt;h2 id="what-next"&gt;What Next?&lt;/h2&gt;
&lt;p&gt;Now you have enough knowledge to make an informed decision between using PyQt or Tkinter for your Python GUI projects. On &lt;a href="https://www.pythonguis.com/"&gt;Python GUIs&lt;/a&gt;, we have lots of tutorials to help you take the next step of &lt;em&gt;actually building something great&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;Our &lt;a href="https://www.pythonguis.com/pyqt6-tutorial/"&gt;PyQt6 tutorial&lt;/a&gt; is pretty complete and takes you from basic examples to complex applications. This way, you'll get the required skills to build multimedia, scientific, and engineering software at a professional level.&lt;/p&gt;
&lt;p&gt;On the other hand, if you're just looking to create simple GUIs for your automation and utility tools, then our &lt;a href="https://www.pythonguis.com/tkinter-tutorial/"&gt;Tkinter tutorial&lt;/a&gt; will give you the basics you need to get a window on the screen and start experimenting.&lt;/p&gt;
&lt;p&gt;In any case, you will have fun building Python 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.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="tkinter"/><category term="pyqt"/><category term="pyqt6"/><category term="qt"/><category term="upgrade"/><category term="licensing"/><category term="python-gui"/><category term="tk"/><category term="python"/><category term="qt6"/></entry><entry><title>How Do I Display Images in PySide6? — Using QLabel and QPixmap to easily add images to your applications</title><link href="https://www.pythonguis.com/faq/adding-images-to-pyside6-applications/" rel="alternate"/><published>2024-03-27T06:00:00+00:00</published><updated>2024-03-27T06:00:00+00:00</updated><author><name>John Lim</name></author><id>tag:www.pythonguis.com,2024-03-27:/faq/adding-images-to-pyside6-applications/</id><summary type="html">Adding images to your application is a common requirement, whether you're building an image/photo viewer or just want to add some decoration to your GUI. Unfortunately, because of how this is done in Qt, it can be a little bit tricky to work out at first.</summary><content type="html">
            &lt;p&gt;Adding images to your application is a common requirement, whether you're building an image/photo viewer or just want to add some decoration to your GUI. Unfortunately, because of how this is done in Qt, it can be a little bit tricky to work out at first.&lt;/p&gt;
&lt;p&gt;In this short tutorial, we will look at how you can insert and display an image in your PySide6 application layout, using both Python code and Qt Designer.&lt;/p&gt;
&lt;h2 id="which-widget-to-use-for-displaying-images-in-pyside6"&gt;Which Widget to Use for Displaying Images in PySide6?&lt;/h2&gt;
&lt;p&gt;Since you're wanting to insert an image you might be expecting to use a widget named &lt;code&gt;QImage&lt;/code&gt; or similar, but that would make a bit too much sense! &lt;code&gt;QImage&lt;/code&gt; is actually Qt's image &lt;em&gt;object&lt;/em&gt; type, which is used to store the actual image data for use within your application. The &lt;em&gt;widget&lt;/em&gt; you use to display an image is &lt;code&gt;QLabel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The primary use of &lt;code&gt;QLabel&lt;/code&gt; is of course to add labels to a UI, but it also has the ability to display an image &amp;mdash; or &lt;em&gt;pixmap&lt;/em&gt; &amp;mdash; instead, covering the entire area of the widget. Below we'll look at how to use &lt;code&gt;QLabel&lt;/code&gt; with &lt;code&gt;QPixmap&lt;/code&gt; to display an image in your PySide6 applications.&lt;/p&gt;
&lt;h2 id="displaying-images-in-pyside6-using-qt-designer"&gt;Displaying Images in PySide6 Using Qt Designer&lt;/h2&gt;
&lt;p&gt;First, create a &lt;em&gt;MainWindow&lt;/em&gt; object in Qt Designer and add a "Label" to it. You can find Label in &lt;em&gt;Display Widgets&lt;/em&gt; at the bottom of the left hand panel. Drag this onto the &lt;code&gt;QMainWindow&lt;/code&gt; to add it. If you're new to Qt Designer, see our &lt;a href="https://www.pythonguis.com/tutorials/pyside6-first-steps-qt-designer/"&gt;guide to getting started with Qt Designer in PySide6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="MainWindow with a single QLabel added in Qt Designer" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/1.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-600 600w" loading="lazy" width="1917" height="1027"/&gt;
&lt;em&gt;MainWindow with a single QLabel added&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, with the Label selected, look in the right hand &lt;code&gt;QLabel&lt;/code&gt; properties panel for the &lt;code&gt;pixmap&lt;/code&gt; property (scroll down to the blue region). From the property editor dropdown select "Choose File&amp;hellip;" and select an image file to insert.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7su366n00020al79n6i8ctr/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;As you can see, the image is inserted, but the image is kept at its original size, cropped to the boundaries of the &lt;code&gt;QLabel&lt;/code&gt; box. You need to resize the &lt;code&gt;QLabel&lt;/code&gt; to be able to see the entire image.&lt;/p&gt;
&lt;p&gt;In the same controls panel, click to enable &lt;code&gt;scaledContents&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stu0wj00050akza5491h2k/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;scaledContents&lt;/code&gt; is enabled the image is resized to fit the bounding box of the &lt;code&gt;QLabel&lt;/code&gt; widget. This shows the entire image at all times, although it does not respect the aspect ratio of the image if you resize the widget.&lt;/p&gt;
&lt;p&gt;You can now save your UI to file (e.g. as &lt;code&gt;mainwindow.ui&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To view the resulting UI, we can use the standard application template below. This loads the &lt;code&gt;.ui&lt;/code&gt; file we've created (&lt;code&gt;mainwindow.ui&lt;/code&gt;), creates the window and starts up the application.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-PySide6"&gt;PySide6&lt;/span&gt;
&lt;pre&gt;&lt;code class="PySide6"&gt;import sys
from PySide6 import QtWidgets
from PySide6.QtUiTools import QUiLoader

loader = QUiLoader()
app = QtWidgets.QApplication(sys.argv)
window = loader.load("mainwindow.ui", None)
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running the above code will create a window, with the image displayed in the middle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 application displaying a cat image loaded from Qt Designer" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/5.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-600 600w" loading="lazy" width="802" height="639"/&gt;
&lt;em&gt;PySide6 application showing an image loaded via Qt Designer&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="displaying-images-using-python-code-with-qlabel-and-qpixmap"&gt;Displaying Images Using Python Code with QLabel and QPixmap&lt;/h2&gt;
&lt;p&gt;Instead of using Qt Designer, you can also show an image in your PySide6 application through Python code. As before we use a &lt;code&gt;QLabel&lt;/code&gt; widget and add a &lt;em&gt;pixmap&lt;/em&gt; image to it. This is done by creating a &lt;code&gt;QPixmap&lt;/code&gt; object from an image file and applying it to the label using the &lt;code&gt;QLabel&lt;/code&gt; method &lt;code&gt;.setPixmap()&lt;/code&gt;. The full code is shown below.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-PySide6"&gt;PySide6&lt;/span&gt;
&lt;pre&gt;&lt;code class="PySide6"&gt;import sys
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you're new to building windows with PySide6, take a look at our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyside6-creating-your-first-window/"&gt;creating your first PySide6 window&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The block of code below shows the step-by-step process of creating the &lt;code&gt;QLabel&lt;/code&gt;, loading an image into a &lt;code&gt;QPixmap&lt;/code&gt; object from our file &lt;code&gt;cat.jpg&lt;/code&gt; (passed as a file path), setting this &lt;code&gt;QPixmap&lt;/code&gt; onto the &lt;code&gt;QLabel&lt;/code&gt; with &lt;code&gt;.setPixmap()&lt;/code&gt; and then finally resizing the window to fit the image.&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 = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Launching this code will show a window with the cat photo displayed and the window sized to the size of the image.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PySide6 QMainWindow displaying an image using QLabel and QPixmap" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/4.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-600 600w" loading="lazy" width="602" height="439"/&gt;
&lt;em&gt;QMainWindow with image displayed using QLabel and QPixmap&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="scaling-images-in-pyside6-with-setscaledcontents"&gt;Scaling Images in PySide6 with setScaledContents&lt;/h2&gt;
&lt;p&gt;Just as in Qt Designer, you can call &lt;code&gt;.setScaledContents(True)&lt;/code&gt; on your &lt;code&gt;QLabel&lt;/code&gt; image to enable scaled mode, which resizes the image to fit the available space.&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 = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
label.setScaledContents(True)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())
&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;  Notice that you set the scaled state on the &lt;code&gt;QLabel&lt;/code&gt; widget and not the image pixmap itself.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this quick tutorial we've covered how to display images in your PySide6 applications using &lt;code&gt;QLabel&lt;/code&gt; and &lt;code&gt;QPixmap&lt;/code&gt;, both from Qt Designer and directly from Python code. The &lt;code&gt;QLabel&lt;/code&gt; widget combined with &lt;code&gt;QPixmap&lt;/code&gt; provides a straightforward way to add images to any PySide6 GUI, and &lt;code&gt;setScaledContents&lt;/code&gt; gives you control over how images resize within your layout. To learn more about the full range of &lt;a href="https://www.pythonguis.com/tutorials/pyside6-widgets/"&gt;PySide6 widgets&lt;/a&gt; available, or to explore how to &lt;a href="https://www.pythonguis.com/tutorials/pyside6-creating-your-own-custom-widgets/"&gt;build custom widgets&lt;/a&gt;, check out our other PySide6 tutorials.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide6 see my book, &lt;a href="https://www.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyside6"/><category term="images"/><category term="qlabel"/><category term="qpixmap"/><category term="widgets"/><category term="qt-designer"/><category term="python-gui"/><category term="python"/><category term="qt"/><category term="qt6"/><category term="pyside6-qt-designer"/></entry><entry><title>Handle command-line arguments with PyQt6/PySide6 — Allow users to customize your application at launch</title><link href="https://www.pythonguis.com/faq/command-line-arguments-pyqt6/" rel="alternate"/><published>2023-03-15T09:03:00+00:00</published><updated>2023-03-15T09:03:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2023-03-15:/faq/command-line-arguments-pyqt6/</id><summary type="html">Sometimes you want to be able to pass command-line arguments to your Python GUI applications. For example, you
may want to be able to pass files which the application should open, or change the initial startup state.</summary><content type="html">
            &lt;p&gt;Sometimes you want to be able to pass command-line arguments to your Python GUI applications. For example, you
may want to be able to pass files which the application should open, or change the initial startup state.&lt;/p&gt;
&lt;p&gt;In that case it can be helpful to be able to parse custom command-line arguments in your application. Qt supports this using
the &lt;code&gt;QCommandLineOption&lt;/code&gt; and &lt;code&gt;QCommandLineParser&lt;/code&gt; classes &amp;mdash; the first defining optional parameters to be identified from the
command line, and the latter to actually perform the parsing.&lt;/p&gt;
&lt;p&gt;In this step-by-step tutorial we'll create a small demo application which accepts arguments on the command line using PyQt6 and PySide6.&lt;/p&gt;
&lt;h2 id="building-the-command-line-parser"&gt;Building the Command-Line Parser&lt;/h2&gt;
&lt;p&gt;We'll start by creating an outline of our application. As usual we create a &lt;code&gt;QApplication&lt;/code&gt; instance, however, we won't initially
create any windows. Instead, we construct our command-line parser function, which accepts our app instance and uses &lt;code&gt;QCommandLineParser&lt;/code&gt;
to parse the command-line arguments.&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="7c48cb5504304840b1094b8dda4b964e" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="7d7ff43a89a04aa1b64521d83de3943b" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="7c48cb5504304840b1094b8dda4b964e"&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
from PyQt6.QtCore import QCommandLineOption, QCommandLineParser

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()

    parser.addHelpOption()
    parser.addVersionOption()

    parser.process(app)


app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="7d7ff43a89a04aa1b64521d83de3943b"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QCommandLineOption, QCommandLineParser

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()

    parser.addHelpOption()
    parser.addVersionOption()

    parser.process(app)


app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;  We don't call &lt;code&gt;app.exec()&lt;/code&gt; yet, to start the event loop. That's because we don't have any windows,
so there would be no way to exit the app once it is started! We'll create the windows in a later step.&lt;/p&gt;
&lt;p&gt;It is important to note that you &lt;em&gt;must&lt;/em&gt; pass &lt;code&gt;sys.argv&lt;/code&gt; to &lt;code&gt;QApplication&lt;/code&gt; for the command-line parser
to work -- the parser processes the arguments from app. If you don't pass the arguments, there won't be anything to process.&lt;/p&gt;
&lt;p&gt;With this outline structure in place we can move on to creating our command-line options.&lt;/p&gt;
&lt;h2 id="adding-optional-parameters-with-qcommandlineoption"&gt;Adding Optional Parameters with QCommandLineOption&lt;/h2&gt;
&lt;p&gt;We'll add two &lt;em&gt;optional&lt;/em&gt; parameters -- the first to allow users to toggle the window start up as maximized, and the second
to pass a Qt style to use for drawing the window.&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 available styles are 'windows', 'windowsvista', 'fusion' and 'macos' -- although the platform
specific styles are only available on their own platform. If you use &lt;code&gt;macos&lt;/code&gt; on Windows, or vice versa, it will have no effect. However, 'fusion' can be used on all platforms.&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="1190fdc6903b4f1e9983aa68fb2786f9" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="7305e9627bbd45a5a90c9e694d8b9d94" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="1190fdc6903b4f1e9983aa68fb2786f9"&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
from PyQt6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)



app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="7305e9627bbd45a5a90c9e694d8b9d94"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)



app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;em&gt;optional&lt;/em&gt; arguments are now in place. We'll now add positional arguments, which will be used to open specific files.&lt;/p&gt;
&lt;h2 id="adding-positional-arguments"&gt;Adding Positional Arguments&lt;/h2&gt;
&lt;p&gt;Strictly speaking, positional arguments are any arguments not interpreted as optional arguments. You can define multiple
positional arguments if you like but this is only used for help text display. You will still need to handle them yourself internally, and
limit the number if necessary by throwing an error.&lt;/p&gt;
&lt;p&gt;In our example we are specifying a single positional argument &lt;code&gt;file&lt;/code&gt;, but noting in the help text that you can provide more than one.
There is no limit in our example -- if you pass more files, more windows will open.&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="c9b2b4b017f140ee9ad14fdb4e81b671" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="bf0e8abd20f146b18212cdd6e5fb6204" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="c9b2b4b017f140ee9ad14fdb4e81b671"&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
from PyQt6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    parser.addPositionalArgument("file", "Files to open.", "[file file file...]")

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)

    has_maximize_option = parser.isSet(maximize_option)
    app_style = parser.value(style_option)

    # Check for positional arguments (files to open).
    arguments = parser.positionalArguments()

    print("Has maximize option?", has_maximize_option)
    print("App style:", app_style)
    print("Arguments (files): ", arguments)

app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="bf0e8abd20f146b18212cdd6e5fb6204"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    parser.addPositionalArgument("file", "Files to open.", "[file file file...]")

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)

    has_maximize_option = parser.isSet(maximize_option)
    app_style = parser.value(style_option)

    # Check for positional arguments (files to open).
    arguments = parser.positionalArguments()

    print("Has maximize option?", has_maximize_option)
    print("App style:", app_style)
    print("Arguments (files): ", arguments)

app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We've added a series of &lt;code&gt;print&lt;/code&gt; calls to display the arguments and options extracted by Qt's &lt;code&gt;QCommandLineParser&lt;/code&gt;. If you run the application
now you can experiment by passing different arguments and seeing the result on the command line.&lt;/p&gt;
&lt;p&gt;For example --- with no arguments.&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 command_line.py
Has maximize option? False
App style:
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With &lt;code&gt;-m&lt;/code&gt; maximize flag and a single file&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 command_line.py -m example.txt
Has maximize option? True
App style:
Arguments (files):  ['example.txt']
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With a single file and using &lt;em&gt;Fusion&lt;/em&gt; style -- there is no window yet, so this will have no effect yet!&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 command_line.py -s fusion example.txt
Has maximize option? False
App style: fusion
Arguments (files):  ['example.txt']
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With the argument handling in place, we can now write the remainder of the example.&lt;/p&gt;
&lt;h2 id="using-the-parsed-arguments-and-options"&gt;Using the Parsed Arguments and Options&lt;/h2&gt;
&lt;p&gt;We'll be using a standard &lt;code&gt;QPlainTextEdit&lt;/code&gt; widget as our file viewer. In Qt any widget without a parent
is a window, so these editors will be floating independently on the desktop. If the &lt;code&gt;-m&lt;/code&gt; flag is used we'll
set these windows to be displayed maximized. For more on creating and managing windows, 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;p&gt;If windows are created, we'll need to start the Qt &lt;em&gt;event loop&lt;/em&gt; to draw the windows and allow interaction with them.
If no windows are created, we'll want to show the command-line help to help the user understand why nothing is happening!
This will output the format of positional and optional arguments that our app takes.&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="73b8c59bb2a142c6850feebc4ed5f738" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="bb4232c3bd17497cadf1e09a0417a4e0" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="73b8c59bb2a142c6850feebc4ed5f738"&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, QPlainTextEdit
from PyQt6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

windows = []  # Store references to our created windows.

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    parser.addPositionalArgument("file", "Files to open.", "[file file file...]")

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)

    has_maximize_option = parser.isSet(maximize_option)
    app_style = parser.value(style_option)

    if app_style and app_style in QT_STYLES:
        app.setStyle(app_style)

    # Check for positional arguments (files to open).
    arguments = parser.positionalArguments()

    # Iterate all arguments and open the files.
    for tfile in arguments:
        try:
            with open(tfile, 'r') as f:
                text = f.read()
        except Exception:
            # Skip this file if there is an error.
            continue

        window = QPlainTextEdit(text)

        # Open the file in a maximized window, if set.
        if has_maximize_option:
            window.showMaximized()
        # Keep a reference to the window in our global list, to stop them
        # being deleted. We can test this list to see whether to show the
        # help (below) or start the event loop (at the bottom).
        windows.append(window)

    if not windows:
        # If we haven't created any windows, show the help.
        parser.showHelp()


app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)

if windows:
    # We've created windows, start the event loop.
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="bb4232c3bd17497cadf1e09a0417a4e0"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PySide6.QtWidgets import QApplication, QPlainTextEdit
from PySide6.QtCore import QCommandLineOption, QCommandLineParser

QT_STYLES = ["windows", "windowsvista", "fusion", "macos"]

windows = []  # Store references to our created windows.

def parse(app):
    """Parse the arguments and options of the given app object."""

    parser = QCommandLineParser()
    parser.addHelpOption()
    parser.addVersionOption()

    parser.addPositionalArgument("file", "Files to open.", "[file file file...]")

    maximize_option = QCommandLineOption(
        ["m", "maximize"],
        "Maximize the window on startup."
    )
    parser.addOption(maximize_option)

    style_option = QCommandLineOption(
        "s",
        "Use the specified Qt style, one of: " + ', '.join(QT_STYLES),
        "style"
    )
    parser.addOption(style_option)

    parser.process(app)

    has_maximize_option = parser.isSet(maximize_option)
    app_style = parser.value(style_option)

    if app_style and app_style in QT_STYLES:
        app.setStyle(app_style)

    # Check for positional arguments (files to open).
    arguments = parser.positionalArguments()

    # Iterate all arguments and open the files.
    for tfile in arguments:
        try:
            with open(tfile, 'r') as f:
                text = f.read()
        except Exception:
            # Skip this file if there is an error.
            continue

        window = QPlainTextEdit(text)

        # Open the file in a maximized window, if set.
        if has_maximize_option:
            window.showMaximized()
        # Keep a reference to the window in our global list, to stop them
        # being deleted. We can test this list to see whether to show the
        # help (below) or start the event loop (at the bottom).
        windows.append(window)

    if not windows:
        # If we haven't created any windows, show the help.
        parser.showHelp()


app = QApplication(sys.argv)
app.setApplicationName("My Application")
app.setApplicationVersion("1.0")

parse(app)

if windows:
    # We've created windows, start the event loop.
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The arguments are handled and processed as before however, now they actually have an effect!&lt;/p&gt;
&lt;p&gt;Firstly, if the user passes the &lt;code&gt;-s &amp;lt;style&amp;gt;&lt;/code&gt; option we will apply the specified style to our app instance -- first checking to see if it is one of the known valid styles.&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 app_style and app_style in QT_STYLES:
    app.setStyle(app_style)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Next we take the list of positional arguments and iterate, creating a &lt;code&gt;QPlainTextEdit&lt;/code&gt; window and displaying the text in it.
If &lt;code&gt;has_maximize_option&lt;/code&gt; has been set, these windows are all set to be maximized with &lt;code&gt;window.showMaximized()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;References to the windows are stored in a global list &lt;code&gt;windows&lt;/code&gt;, so they are not cleaned up (deleted) on exiting the function.
After creating windows we test to see if this is empty, and if not show the help:&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 command_line.py
Usage: command_line.py [options] [file file file...]

Options:
  -?, -h, --help  Displays help on commandline options.
  --help-all      Displays help including Qt specific options.
  -v, --version   Displays version information.
  -m, --maximize  Maximize the window on startup.
  -s &amp;lt;style&amp;gt;      Use the specified Qt style, one of: windows, windowsvista,
                  fusion, macos

Arguments:
  file            Files to open.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If there &lt;em&gt;are&lt;/em&gt; windows, we finally start up the event loop to display them and allow the user to interact with 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;if windows:
    # We've created windows, start the event loop.
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial we've learned how to parse command-line arguments in PyQt6 and PySide6 applications using &lt;code&gt;QCommandLineParser&lt;/code&gt; and &lt;code&gt;QCommandLineOption&lt;/code&gt;. We covered how to add optional flags, value-based options, and positional arguments &amp;mdash; then used them to modify the UI behavior at launch. You can use this same approach in your own Python GUI applications to provide flexible command-line control over application startup and configuration.&lt;/p&gt;
&lt;p&gt;If you're new to building PyQt6 or PySide6 applications, you may want to start with our guide on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window&lt;/a&gt;. To learn more about Qt's event-driven architecture and how &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt; work, check out our dedicated tutorial. Once your app is ready for distribution, see how to &lt;a href="https://www.pythonguis.com/tutorials/packaging-pyqt6-applications-windows-pyinstaller/"&gt;package PyQt6 applications with PyInstaller on Windows&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.mfitzp.com/pyside6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyside6"/><category term="qt6"/><category term="app"/><category term="command-line"/><category term="python"/><category term="qt"/></entry><entry><title>Working With Classes in Python and PyQt — Understanding the Intricacies of Python Classes for GUI Development</title><link href="https://www.pythonguis.com/tutorials/python-classes/" rel="alternate"/><published>2023-03-06T06:00:00+00:00</published><updated>2023-03-06T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2023-03-06:/tutorials/python-classes/</id><summary type="html">Python supports &lt;a href="https://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object-oriented programming (OOP)&lt;/a&gt; through classes, which allow you to bundle data and behavior in a single entity. Python classes allow you to quickly model concepts by creating representations of real objects that you can then use to organize your code.</summary><content type="html">
            &lt;p&gt;Python supports &lt;a href="https://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object-oriented programming (OOP)&lt;/a&gt; through classes, which allow you to bundle data and behavior in a single entity. Python classes allow you to quickly model concepts by creating representations of real objects that you can then use to organize your code.&lt;/p&gt;
&lt;p&gt;Most of the currently available GUI frameworks for Python developers, such as PyQt, PySide, and Tkinter, rely on classes to provide apps, windows, widgets, and more. This means that you'll be actively using classes for designing and developing your GUI apps.&lt;/p&gt;
&lt;p&gt;In this tutorial, you'll learn how OOP and classes work in Python. This knowledge will allow you to quickly grasp how GUI frameworks are internally organized, how they work, and how you can use their classes and APIs to create robust GUI applications.&lt;/p&gt;
&lt;h2 id="defining-classes-in-python"&gt;Defining Classes in Python&lt;/h2&gt;
&lt;p&gt;Python classes are templates or blueprints that allow us to create objects through &lt;strong&gt;instantiation&lt;/strong&gt;. These objects will contain data representing the object's state, and methods that will act on the data providing the object's behavior.&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;  Instantiation is the process of creating instances of a class by calling the &lt;strong&gt;class constructor&lt;/strong&gt; with appropriate arguments.&lt;/p&gt;
&lt;p&gt;Attributes and methods make up what is known as the class &lt;strong&gt;interface&lt;/strong&gt; or &lt;a href="https://en.wikipedia.org/wiki/API"&gt;API&lt;/a&gt;. This interface allows us to operate on the objects without needing to understand their internal implementation and structure.&lt;/p&gt;
&lt;p&gt;Alright, it is time to start creating our own classes. We'll start by defining a &lt;code&gt;Color&lt;/code&gt; class with minimal functionality. To do that in Python, you'll use the &lt;code&gt;class&lt;/code&gt; keyword followed by the class name. Then you provide the class body in the next &lt;a href="https://en.wikipedia.org/wiki/Indentation_(typesetting)"&gt;indentation&lt;/a&gt; level:&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;&amp;gt;&amp;gt;&amp;gt; class Color:
...     pass
...

&amp;gt;&amp;gt;&amp;gt; red = Color()

&amp;gt;&amp;gt;&amp;gt; type(red)
&amp;lt;class '__main__.Color'&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we defined our &lt;code&gt;Color&lt;/code&gt; class using the &lt;code&gt;class&lt;/code&gt; keyword. This class is empty. It doesn't have attributes or methods. Its body only contains a &lt;code&gt;pass&lt;/code&gt; statement, which is Python's way to do nothing.&lt;/p&gt;
&lt;p&gt;Even though the class is minimal, it allows us to create instances by calling its constructor, &lt;code&gt;Color()&lt;/code&gt;. So, &lt;code&gt;red&lt;/code&gt; is an instance of &lt;code&gt;Color&lt;/code&gt;. Now let's make our &lt;code&gt;Color&lt;/code&gt; class more fun by adding some attributes.&lt;/p&gt;
&lt;h3&gt;Adding Class and Instance Attributes&lt;/h3&gt;
&lt;p&gt;Python classes allow you to add two types of attributes. You can have &lt;strong&gt;class&lt;/strong&gt; and &lt;strong&gt;instance attributes&lt;/strong&gt;. A class attribute belongs to its containing class. Its data is common to the class and all its instances. To access a class attribute, we can use either the class or any of its instances.&lt;/p&gt;
&lt;p&gt;Let's now add a class attribute to our &lt;code&gt;Color&lt;/code&gt; class. For example, let's say we need to keep note of how many instances of &lt;code&gt;Color&lt;/code&gt; your code creates. Then you can have a &lt;code&gt;color_count&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;&amp;gt;&amp;gt;&amp;gt; class Color:
...     color_count = 0
...     def __init__(self):
...         Color.color_count += 1
...

&amp;gt;&amp;gt;&amp;gt; red = Color()
&amp;gt;&amp;gt;&amp;gt; green = Color()

&amp;gt;&amp;gt;&amp;gt; Color.color_count
2
&amp;gt;&amp;gt;&amp;gt; red.color_count
2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now &lt;code&gt;Color&lt;/code&gt; has a class attribute called &lt;code&gt;color_count&lt;/code&gt; that gets incremented every time we create a new instance. We can quickly access that attribute using either the class directly or one of its instances, like &lt;code&gt;red&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To follow up with this example, say that we want to represent our &lt;code&gt;Color&lt;/code&gt; objects using red, green, and blue attributes as part of the &lt;a href="https://en.wikipedia.org/wiki/RGB_color_model"&gt;RGB color model&lt;/a&gt;. These attributes should have specific values for specific instances of the class. So, they should be instance attributes.&lt;/p&gt;
&lt;p&gt;To add an instance attribute to a Python class, you must use the &lt;code&gt;.__init__()&lt;/code&gt; special method, which we introduced in the previous code but didn't explain. This method works as the instance initializer because it allows you to provide initial values for instance attributes:&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;&amp;gt;&amp;gt;&amp;gt; class Color:
...     color_count = 0
...     def __init__(self, red, green, blue):
...         Color.color_count += 1
...         self.red = red
...         self.green = green
...         self.blue = blue
...

&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)

&amp;gt;&amp;gt;&amp;gt; red.red
255
&amp;gt;&amp;gt;&amp;gt; red.green
0
&amp;gt;&amp;gt;&amp;gt; red.blue
0

&amp;gt;&amp;gt;&amp;gt; Color.red
Traceback (most recent call last):
    ...
AttributeError: type object 'Color' has no attribute 'red'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Cool! Now our &lt;code&gt;Color&lt;/code&gt; class looks more useful. It has the usual class attributes and also three new instance attributes. Note that, unlike class attributes, instance attributes can't be accessed through the class itself. They're specific to a concrete instance.&lt;/p&gt;
&lt;p&gt;There's something that jumps into sight in this new version of &lt;code&gt;Color&lt;/code&gt;. What is the &lt;code&gt;self&lt;/code&gt; argument in the definition of &lt;code&gt;.__init__()&lt;/code&gt;? This argument holds a reference to the current instance. Using the name &lt;code&gt;self&lt;/code&gt; to identify the current instance is a strong convention in Python.&lt;/p&gt;
&lt;p&gt;We'll use &lt;code&gt;self&lt;/code&gt; as the first or even the only argument to instance methods like &lt;code&gt;.__init__()&lt;/code&gt;. Inside an instance method, we'll use &lt;code&gt;self&lt;/code&gt; to access other methods and attributes defined in the class. To do that, we must prepend &lt;code&gt;self&lt;/code&gt; to the name of the target attribute or method of the class.&lt;/p&gt;
&lt;p&gt;For example, our class has an attribute &lt;code&gt;.red&lt;/code&gt; that we can access using the syntax &lt;code&gt;self.red&lt;/code&gt; inside the class. This will return the number stored under that name. From outside the class, you need to use a concrete instance instead of &lt;code&gt;self&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Providing Behavior With Methods&lt;/h3&gt;
&lt;p&gt;A class bundles data (attributes) and behavior (methods) together in an object. You'll use the data to set the object's state and the methods to operate on that data or state.&lt;/p&gt;
&lt;p&gt;Methods are just functions that we define inside a class. Like functions, methods can take arguments, return values, and perform different computations on an object's attributes. They allow us to make our objects usable.&lt;/p&gt;
&lt;p&gt;In Python, we can define three types of methods in our classes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instance methods&lt;/strong&gt;, which need the instance (&lt;code&gt;self&lt;/code&gt;) as their first argument&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class methods&lt;/strong&gt;, which take the class (&lt;code&gt;cls&lt;/code&gt;) as their first argument&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static methods&lt;/strong&gt;, which take neither the class nor the instance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's now talk about instance methods. Say that we need to get the attributes of our &lt;code&gt;Color&lt;/code&gt; class as a tuple of numbers. In this case, we can add an &lt;code&gt;.as_tuple()&lt;/code&gt; method 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;class Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This new method is pretty straightforward. Since it's an instance method, it takes &lt;code&gt;self&lt;/code&gt; as its first argument. Then it returns a tuple containing the attributes &lt;code&gt;.red&lt;/code&gt;, &lt;code&gt;.green&lt;/code&gt;, and &lt;code&gt;.blue&lt;/code&gt;. Note how you need to use &lt;code&gt;self&lt;/code&gt; to access the attributes of the current instance inside the class.&lt;/p&gt;
&lt;p&gt;This method may be useful if you need to iterate over the RGB components of your color 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;&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)
&amp;gt;&amp;gt;&amp;gt; red.as_tuple()
(255, 0, 0)

&amp;gt;&amp;gt;&amp;gt; for level in red.as_tuple():
...     print(level)
...
255
0
0
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Our &lt;code&gt;as_tuple()&lt;/code&gt; method works great! It returns a tuple containing the RGB components of our color objects.&lt;/p&gt;
&lt;p&gt;We can also add class methods to our Python classes. To do this, we need to use the &lt;a href="https://docs.python.org/3/library/functions.html#classmethod"&gt;&lt;code&gt;@classmethod&lt;/code&gt;&lt;/a&gt; decorator 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;class Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue

    @classmethod
    def from_tuple(cls, rbg):
        return cls(*rbg)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;from_tuple()&lt;/code&gt; class method takes a tuple object containing the RGB components of a desired color as an argument, creates a valid color object from it, and returns the object back to the caller:&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;&amp;gt;&amp;gt;&amp;gt; blue = Color.from_tuple((0, 0, 255))
&amp;gt;&amp;gt;&amp;gt; blue.as_tuple()
(0, 0, 255)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we use the &lt;code&gt;Color&lt;/code&gt; class to access the class method &lt;code&gt;from_tuple()&lt;/code&gt;. We can also access the method using a concrete instance of this class. However, in both cases, we'll get a completely new object.&lt;/p&gt;
&lt;p&gt;Finally, Python classes can also have static methods that we can define with the &lt;a href="https://docs.python.org/3/library/functions.html#staticmethod"&gt;&lt;code&gt;@staticmethod&lt;/code&gt;&lt;/a&gt; decorator:&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 Color:
    representation = "RGB"

    def __init__(self, red, green, blue):
        self.red = red
        self.green = green
        self.blue = blue

    def as_tuple(self):
        return self.red, self.green, self.blue

    @classmethod
    def from_tuple(cls, rbg):
        return cls(*rbg)

    @staticmethod
    def color_says(message):
        print(message)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Static methods don't operate either on the current instance &lt;code&gt;self&lt;/code&gt; or the current class &lt;code&gt;cls&lt;/code&gt;. These methods can work as independent functions. However, we typically put them inside a class when they are related to the class, and we need to have them accessible from the class and its instances.&lt;/p&gt;
&lt;p&gt;Here's how the method works:&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;&amp;gt;&amp;gt;&amp;gt; Color.color_says("Hello from the Color class!")
Hello from the Color class!

&amp;gt;&amp;gt;&amp;gt; red = Color(255, 0, 0)
&amp;gt;&amp;gt;&amp;gt; red.color_says("Hello from the red instance!")
Hello from the red instance!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method accepts a message and prints it on your screen. It works independently from the class or instance attributes. Note that you can call the method using the class or any of its instances.&lt;/p&gt;
&lt;h3&gt;Writing Getter and Setter Methods&lt;/h3&gt;
&lt;p&gt;Programming languages like Java and C++ rely heavily on setter and getter methods to retrieve and update the attributes of a class and its instances. These methods encapsulate an attribute allowing us to get and change its value without directly accessing the attribute itself.&lt;/p&gt;
&lt;p&gt;For example, say that we have a &lt;code&gt;Label&lt;/code&gt; class with a &lt;code&gt;text&lt;/code&gt; attribute. We can make &lt;code&gt;text&lt;/code&gt; a non-public attribute and provide getter and setter methods to manipulate the attributes according to our needs:&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 Label:
    def __init__(self, text):
        self.set_text(text)

    def text(self):
        return self._text

    def set_text(self, value):
        self._text = str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this class, the &lt;code&gt;text()&lt;/code&gt; method is the getter associated with the &lt;code&gt;._text&lt;/code&gt; attribute, while the &lt;code&gt;set_text()&lt;/code&gt; method is the setter for &lt;code&gt;._text&lt;/code&gt;. Note how &lt;code&gt;._text&lt;/code&gt; is a non-public attribute. We know this because it has a leading underscore on its name.&lt;/p&gt;
&lt;p&gt;The setter method calls &lt;code&gt;str()&lt;/code&gt; to convert any input value into a string. Therefore, we can call this method with any type of object. It will convert any input argument into a string, as you will see in a moment.&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;  If you come from programming languages like Java or C++, you need to know Python doesn't have the notion of &lt;strong&gt;private&lt;/strong&gt;, &lt;strong&gt;protected&lt;/strong&gt;, and &lt;strong&gt;public&lt;/strong&gt; attributes. In Python, you'll use a naming convention to signal that an attribute is non-public. This convention consists of adding a leading underscore to the attribute's name. Note that this naming pattern only indicates that the attribute isn't intended to be used directly. It doesn't prevent direct access, though.&lt;/p&gt;
&lt;p&gt;This class works 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;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; label.text()
'Python!'

&amp;gt;&amp;gt;&amp;gt; label.set_text("PyQt!")
&amp;gt;&amp;gt;&amp;gt; label.text()
'PyQt!'

&amp;gt;&amp;gt;&amp;gt; label.set_text(123)
&amp;gt;&amp;gt;&amp;gt; label.text()
'123'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we create an instance of &lt;code&gt;Label&lt;/code&gt;. The original text is passed to the class constructor, &lt;code&gt;Label()&lt;/code&gt;, which automatically calls &lt;code&gt;__init__()&lt;/code&gt; to set the value of &lt;code&gt;._text&lt;/code&gt; by calling the setter method &lt;code&gt;set_text()&lt;/code&gt;. You can use &lt;code&gt;text()&lt;/code&gt; to access the label's text and &lt;code&gt;set_text()&lt;/code&gt; to update it. Remember that any input will be converted into a string, as we can see in the final example above.&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;  Note that the &lt;code&gt;Label&lt;/code&gt; class above is just a toy example, don't confuse this class with similarly named classes from GUI frameworks like PyQt, PySide, and Tkinter.&lt;/p&gt;
&lt;p&gt;The getter and setter pattern is pretty common in languages like Java and C++. Because PyQt and PySide are Python bindings to the Qt library, which is written in C++, you'll be using this pattern a lot in your Qt-based GUI apps. However, this pattern is less popular among Python developers. Instead, they use the &lt;code&gt;@property&lt;/code&gt; decorator to hide attributes behind properties.&lt;/p&gt;
&lt;h3&gt;Using Python Properties Instead of Getters and Setters&lt;/h3&gt;
&lt;p&gt;Here's how most Python developers will write their &lt;code&gt;Label&lt;/code&gt; class using the &lt;code&gt;@property&lt;/code&gt; decorator:&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 Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This class defines &lt;code&gt;.text&lt;/code&gt; as a property. This property has getter and setter methods. Python calls them automatically when we access the attribute or update its value in an assignment:&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;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; label.text
'Python!'

&amp;gt;&amp;gt;&amp;gt; label.text = "PyQt"
&amp;gt;&amp;gt;&amp;gt; label.text
'PyQt'

&amp;gt;&amp;gt;&amp;gt; label.text = 123
&amp;gt;&amp;gt;&amp;gt; label.text
'123'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Python properties allow you to add function behavior to your attributes while permitting you to use them as normal attributes instead of as methods.&lt;/p&gt;
&lt;h3&gt;Writing Special Methods (&lt;code&gt;__str__&lt;/code&gt;, &lt;code&gt;__repr__&lt;/code&gt;, and More)&lt;/h3&gt;
&lt;p&gt;Python supports many &lt;a href="https://docs.python.org/3/glossary.html#term-special-method"&gt;special methods&lt;/a&gt;, also known as &lt;strong&gt;dunder&lt;/strong&gt; or &lt;strong&gt;magic&lt;/strong&gt; methods, that are part of its class mechanism. We can identify these methods because their names start and end with a double underscore, which is the origin of their other name: dunder methods.&lt;/p&gt;
&lt;p&gt;These methods accomplish different tasks in Python's class mechanism. They all have a common feature: &lt;em&gt;Python calls them automatically depending on the operation we run.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For example, all Python objects are printable. We can print them to the screen using the &lt;code&gt;print()&lt;/code&gt; function. Calling &lt;code&gt;print()&lt;/code&gt; internally falls back to calling the target object's &lt;code&gt;__str__()&lt;/code&gt; special 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;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; print(label)
&amp;lt;__main__.Label object at 0x10354efd0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, we've printed our &lt;code&gt;label&lt;/code&gt; object. This action provides some information about the object and the memory address where it lives. However, the actual output is not very useful from the user's perspective.&lt;/p&gt;
&lt;p&gt;Fortunately, we can improve this by providing our &lt;code&gt;Label&lt;/code&gt; class with an appropriate &lt;code&gt;__str__()&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;class Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)

    def __str__(self):
        return self.text
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;__str__()&lt;/code&gt; method must return a user-friendly string representation for our objects. In this case, when we print an instance of &lt;code&gt;Label&lt;/code&gt; to the screen, the label's text will be displayed:&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;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")

&amp;gt;&amp;gt;&amp;gt; print(label)
Python!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can see, Python takes care of calling &lt;code&gt;__str__()&lt;/code&gt; automatically when we use the &lt;code&gt;print()&lt;/code&gt; function to display our instances of &lt;code&gt;Label&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another special method that belongs to Python's class mechanism is &lt;code&gt;__repr__()&lt;/code&gt;. This method returns a developer-friendly string representation of a given object. Here, developer-friendly implies that the representation should allow a developer to recreate the object itself.&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 Label:
    def __init__(self, text):
        self.text = text

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = str(value)

    def __str__(self):
        return self.text

    def __repr__(self):
        return f"{type(self).__name__}(text='{self.text}')"
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;__repr__()&lt;/code&gt; method returns a string representation of the current object. This string differs from what &lt;code&gt;__str__()&lt;/code&gt; returns:&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;&amp;gt;&amp;gt;&amp;gt; label = Label("Python!")
&amp;gt;&amp;gt;&amp;gt; label
Label(text='Python!')
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now when you access the instance in your REPL session, you get a string representation of the current object. You can copy and paste this representation to recreate the object in an appropriate environment.&lt;/p&gt;
&lt;h2 id="reusing-code-with-class-inheritance"&gt;Reusing Code With Class Inheritance&lt;/h2&gt;
&lt;p&gt;Inheritance is an advanced topic in object-oriented programming. It allows you to create hierarchies of classes where each subclass inherits all the attributes and behaviors from its parent class or classes. Arguably, &lt;strong&gt;code reuse&lt;/strong&gt; is the primary use case of inheritance.&lt;/p&gt;
&lt;p&gt;You code a base class with a given functionality and make that functionality available to its subclasses through inheritance. This way, you implement the functionality only once and reuse it in every subclass.&lt;/p&gt;
&lt;p&gt;Python classes support &lt;strong&gt;single&lt;/strong&gt; and &lt;strong&gt;multiple&lt;/strong&gt; inheritance. For example, let's say we need to create a button class. This class needs &lt;code&gt;.width&lt;/code&gt; and &lt;code&gt;.height&lt;/code&gt; attributes that define its rectangular shape. The class also needs a label for displaying some informative text.&lt;/p&gt;
&lt;p&gt;We can code this class from scratch, or we can use inheritance and reuse the code of our current &lt;code&gt;Label&lt;/code&gt; class. Here's how to do 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;class Button(Label):
    def __init__(self, text, width, height):
        super().__init__(text)
        self.width = width
        self.height = height

    def __repr__(self):
        return (
            f"{type(self).__name__}"
            f"(text='{self.text}', "
            f"width={self.width}, "
            f"height={self.height})"
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To inherit from a parent class in Python, we need to list the parent class or classes in the subclass definition. To do this, we use a pair of parentheses and a comma-separated list of parent classes. If we use several parent classes, then we're using multiple inheritance, which can be challenging to reason about.&lt;/p&gt;
&lt;p&gt;The first line in &lt;code&gt;__init__()&lt;/code&gt; calls the &lt;code&gt;__init__()&lt;/code&gt; method on the parent class to properly initialize its &lt;code&gt;.text&lt;/code&gt; attribute. To do this, we use the built-in &lt;code&gt;super()&lt;/code&gt; function. Then we define the &lt;code&gt;.width&lt;/code&gt; and &lt;code&gt;.height&lt;/code&gt; attributes, which are specific to our &lt;code&gt;Button&lt;/code&gt; class. Finally, we provide a custom implementation of &lt;code&gt;__repr__()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's how our &lt;code&gt;Button&lt;/code&gt; class works:&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;&amp;gt;&amp;gt;&amp;gt; button = Button("Ok", 10, 5)

&amp;gt;&amp;gt;&amp;gt; button.text
'Ok'
&amp;gt;&amp;gt;&amp;gt; button.text = "Click Me!"
&amp;gt;&amp;gt;&amp;gt; button.text
'Click Me!'

&amp;gt;&amp;gt;&amp;gt; button.width
10
&amp;gt;&amp;gt;&amp;gt; button.height
5

&amp;gt;&amp;gt;&amp;gt; button
Button(text='Ok', width=10, height=5)
&amp;gt;&amp;gt;&amp;gt; print(button)
Click Me!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As you can conclude from this code, &lt;code&gt;Button&lt;/code&gt; has inherited the &lt;code&gt;.text&lt;/code&gt; attribute from &lt;code&gt;Label&lt;/code&gt;. This attribute is completely functional. Our class has also inherited the &lt;code&gt;__str__()&lt;/code&gt; method from &lt;code&gt;Label&lt;/code&gt;. That's why we get the button's text when we print the instance.&lt;/p&gt;
&lt;h2 id="using-python-classes-in-pyqt-and-pyside-gui-apps"&gt;Using Python Classes in PyQt and PySide GUI Apps&lt;/h2&gt;
&lt;p&gt;Everything we've learned so far about Python classes is the basis of our future work in GUI development. When it comes to working with PyQt, PySide, Tkinter, or any other GUI framework, we'll heavily rely on our knowledge of classes and OOP because most of them are based on classes and class hierarchies.&lt;/p&gt;
&lt;p&gt;We'll now look at how to use inheritance to create some GUI-related classes. For example, when we create an application with PyQt or PySide, we usually have a main window. To create this window, we typically inherit from &lt;code&gt;QMainWindow&lt;/code&gt;:&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="5a027972084e42e7bc9726f55bfa93a2" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="4cc589a389824b6f9551bedfb0de4eb3" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="5a027972084e42e7bc9726f55bfa93a2"&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

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="4cc589a389824b6f9551bedfb0de4eb3"&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.QtWidgets import QMainWindow

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the definition of our &lt;code&gt;Window&lt;/code&gt; class, we use the &lt;code&gt;QMainWindow&lt;/code&gt; class as the parent class. This tells Python that we want to define a class that inherits all the functionalities that &lt;code&gt;QMainWindow&lt;/code&gt; provides.&lt;/p&gt;
&lt;p&gt;We can continue adding attributes and methods to our &lt;code&gt;Window&lt;/code&gt; class. Some of these attributes can be GUI widgets, such as labels, buttons, comboboxes, checkboxes, line edits, and many others. In PyQt, we can create all these GUI components using classes such as &lt;code&gt;QLabel&lt;/code&gt;, &lt;a href="https://www.pythonguis.com/docs/qpushbutton/"&gt;&lt;code&gt;QPushButton&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.pythonguis.com/docs/qcombobox/"&gt;&lt;code&gt;QComboBox&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.pythonguis.com/docs/qcheckbox/"&gt;&lt;code&gt;QCheckBox&lt;/code&gt;&lt;/a&gt;, and &lt;code&gt;QLineEdit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All of them have their own sets of attributes and methods that we can use according to our specific needs when designing the GUI of a given application.&lt;/p&gt;
&lt;h2 id="summary-of-python-class-concepts"&gt;Summary of Python Class Concepts&lt;/h2&gt;
&lt;p&gt;As we've seen, Python allows us to write classes that work as templates that you can use to create concrete objects that bundle together data and behavior. The building blocks of Python classes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attributes&lt;/strong&gt;, which hold the data in a class&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Methods&lt;/strong&gt;, which provide the behaviors of a class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attributes of a class define the class's data, while the methods provide the class's behaviors, which typically act on that data.&lt;/p&gt;
&lt;p&gt;To better understand OOP and classes in Python, we should first discuss some terms that are commonly used in this aspect of Python development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Classes&lt;/strong&gt; are blueprints or templates for creating objects &amp;mdash; just like a blueprint for creating a car, plane, house, or anything else. In programming, this blueprint will define the data (attributes) and behavior (methods) of the object and will allow us to create multiple objects of the same kind.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Objects&lt;/strong&gt; or &lt;strong&gt;Instances&lt;/strong&gt; are the realizations of a class. We can create objects from the blueprint provided by the class. For example, you can create John's car from a &lt;code&gt;Car&lt;/code&gt; class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Methods&lt;/strong&gt; are functions defined within a class. They provide the behavior of an object of that class. For example, our &lt;code&gt;Car&lt;/code&gt; class can have methods to start the engine, turn right and left, stop, and so on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attributes&lt;/strong&gt; are properties of an object or class. We can think of attributes as variables defined in a class or object. Therefore, we can have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class attributes&lt;/strong&gt;, which are specific to a concrete class and common to all the instances of that class. You can access them either through the class or an object of that class. For example, if we're dealing with a single car manufacturer, then our &lt;code&gt;Car&lt;/code&gt; class can have a manufacturer attribute that identifies it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance attributes&lt;/strong&gt;, which are specific to a concrete instance. You can access them through the specific instance. For example, our &lt;code&gt;Car&lt;/code&gt; class can have attributes to store properties such as the maximum speed, the number of passengers, the car's weight, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Instantiation&lt;/strong&gt; is the process of creating an individual &lt;em&gt;instance&lt;/em&gt; from a class. For example, we can create John's car, Jane's car, and Linda's car from our &lt;code&gt;Car&lt;/code&gt; class through instantiation. In Python, this process runs through two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Instance creation&lt;/strong&gt;: Creates a new object and allocates memory for storing it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance initialization&lt;/strong&gt;: Initializes all the attributes of the current object with appropriate values.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inheritance&lt;/strong&gt; is a mechanism of code reuse that allows us to inherit attributes and methods from one or multiple existing classes. In this context, we'll hear terms like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parent class&lt;/strong&gt;: The class we're inheriting from. This class is also known as the &lt;strong&gt;superclass&lt;/strong&gt; or &lt;strong&gt;base class&lt;/strong&gt;. If we have one parent class, then we're using &lt;strong&gt;single inheritance&lt;/strong&gt;. If we have more than one parent class, then we're using &lt;strong&gt;multiple inheritance&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Child class&lt;/strong&gt;: The class that inherits from a given parent. This class is also known as the &lt;strong&gt;subclass&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't feel frustrated or bad if you don't understand all these terms immediately. They'll become more familiar as you use them in your own Python code. Many of our GUI tutorials make use of some or all of these concepts.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now you know the basics of Python classes. You also learned fundamental concepts of object-oriented programming, such as inheritance. You also learned that most GUI frameworks are heavily based on classes. Therefore knowing about classes will open the door to begin building your own GUI app using PyQt, PySide, Tkinter, or any other GUI framework for Python.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="python"/><category term="classes"/><category term="pyqt6"/><category term="pyside6"/><category term="tkinter"/><category term="qt"/><category term="qt6"/><category term="tk"/></entry><entry><title>How to Create a Custom Title Bar for a PyQt5 Window — Customize Your Python App's Title Bars</title><link href="https://www.pythonguis.com/tutorials/custom-title-bar-pyqt5/" rel="alternate"/><published>2023-02-18T06:00:00+00:00</published><updated>2023-02-18T06:00:00+00:00</updated><author><name>Leo Well</name></author><id>tag:www.pythonguis.com,2023-02-18:/tutorials/custom-title-bar-pyqt5/</id><summary type="html">PyQt provides plenty of tools for creating unique and visually appealing graphical user interfaces (GUIs). One aspect of your applications that you may not have considered customizing is the &lt;strong&gt;title bar&lt;/strong&gt;. The &lt;em&gt;title bar&lt;/em&gt; is the topmost part of the window, where your users find the app's name, window controls &amp;amp; other elements.</summary><content type="html">
            &lt;p&gt;PyQt provides plenty of tools for creating unique and visually appealing graphical user interfaces (GUIs). One aspect of your applications that you may not have considered customizing is the &lt;strong&gt;title bar&lt;/strong&gt;. The &lt;em&gt;title bar&lt;/em&gt; is the topmost part of the window, where your users find the app's name, window controls &amp;amp; other elements.&lt;/p&gt;
&lt;p&gt;This part of the window is usually drawn by the operating system or desktop environment, and its default look and feel may not gel well with the rest of your application. However, you may want to customize it to add additional functionality. For example, in web browsers the document tabs are now typically collapsed into the title bar to maximize available space for viewing pages.&lt;/p&gt;
&lt;p&gt;In this tutorial, you will learn how to create custom title bars in PyQt. By the end of this tutorial, you will have the necessary knowledge to enhance your PyQt applications with personalized and (hopefully!) stylish title bars.&lt;/p&gt;
&lt;h2 id="creating-frameless-windows-in-pyqt"&gt;Creating Frameless Windows in PyQt&lt;/h2&gt;
&lt;p&gt;The first step to providing a PyQt application with a custom &lt;strong&gt;title bar&lt;/strong&gt; is to remove the default title bar and window decoration provided by the operating system. If we don't take this step, we'll end up with multiple title bars at the top of our windows.&lt;/p&gt;
&lt;p&gt;In PyQt, we can create a frameless window using the &lt;a href="https://doc.qt.io/qt-6/qwidget.html#windowFlags-prop"&gt;&lt;code&gt;setWindowFlags()&lt;/code&gt;&lt;/a&gt; method available on all &lt;a href="https://doc.qt.io/qt-6/qwidget.html"&gt;&lt;code&gt;QWidget&lt;/code&gt;&lt;/a&gt; subclasses, including &lt;a href="https://doc.qt.io/qt-6/qmainwindow.html"&gt;&lt;code&gt;QMainWindow&lt;/code&gt;&lt;/a&gt;. We call this method, passing in the &lt;code&gt;FramelessWindowHint&lt;/code&gt; flag, which lives in the &lt;code&gt;Qt&lt;/code&gt; namespace under the &lt;code&gt;WindowType&lt;/code&gt; enumeration.&lt;/p&gt;
&lt;p&gt;Here's the code of a minimal PyQt app whose main window is frameless:&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 PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After importing the required classes, we create a window by subclassing &lt;code&gt;QMainWindow&lt;/code&gt;. In the class initializer method, we set the window's title and resize the window using the &lt;code&gt;resize()&lt;/code&gt; method. Then we use the &lt;code&gt;setWindowFlags()&lt;/code&gt; to make the window frameless. The rest is the usual boilerplate code for creating PyQt applications.&lt;/p&gt;
&lt;p&gt;If you run this app from your command line, you'll get the following window on your screen:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A frameless window in PyQt" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/frameless-window-pyqt.png?tr=w-600 600w" loading="lazy" width="1000" height="600"/&gt;
&lt;em&gt;A frameless window in PyQt&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the app's main window doesn't have a title bar or any other decoration. It's only a gray rectangle on your screen.&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;  Because the window has no buttons, you need to press &lt;em&gt;Alt-F4&lt;/em&gt; on Windows and Linux or &lt;em&gt;Cmd+Q&lt;/em&gt; on macOS to close the app.&lt;/p&gt;
&lt;p&gt;This isn't very helpful, of course, but we'll be adding back in our custom title bar shortly.&lt;/p&gt;
&lt;h2 id="setting-up-the-main-window"&gt;Setting Up the Main Window&lt;/h2&gt;
&lt;p&gt;Before creating our custom title bar, we'll finish the initialization of our app's main window, import some additional classes and create the window's central widget and layouts.&lt;/p&gt;
&lt;p&gt;Here's the code 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 PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        central_widget = QWidget()
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        work_space_layout.addWidget(QLabel("Hello, World!", self))

        centra_widget_layout = QVBoxLayout()
        centra_widget_layout.setContentsMargins(0, 0, 0, 0)
        centra_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        centra_widget_layout.addWidget(self.title_bar)
        centra_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(centra_widget_layout)
        self.setCentralWidget(central_widget)

# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;First, we import the &lt;code&gt;QLabel&lt;/code&gt;, &lt;code&gt;QVBoxLayout&lt;/code&gt;, and &lt;code&gt;QWidget&lt;/code&gt; classes. In our window's initializer, we create a central widget by instantiating &lt;code&gt;QWidget()&lt;/code&gt;. Next, we create an instance attribute called &lt;code&gt;title_bar&lt;/code&gt; by instantiating a class called &lt;code&gt;CustomTitleBar&lt;/code&gt;. We still need to implement this class&amp;mdash;we'll do this in a moment.&lt;/p&gt;
&lt;p&gt;The next step is to create a layout for our window's workspace. In this example, we're using a &lt;code&gt;QVBoxLayout&lt;/code&gt;, but you can use the layout that best fits your needs. We also set some margins for the layout content and added a label containing the phrase &lt;code&gt;"Hello, World!"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we create a global layout for our central widget. Again, we use a &lt;code&gt;QVBoxLayout&lt;/code&gt;. We set the layout's margins to &lt;code&gt;0&lt;/code&gt; and aligned it on the top of our frameless window. In this layout, we need to add the title bar at the top and the workspace at the bottom. Finally, we set the central widget's layout and the app's central widget.&lt;/p&gt;
&lt;p&gt;That's it! We have all the boilerplate code we need for our window to work correctly. Now we're ready to write our custom title bar.&lt;/p&gt;
&lt;h2 id="creating-a-custom-title-bar-for-a-pyqt-window"&gt;Creating a Custom Title Bar for a PyQt Window&lt;/h2&gt;
&lt;p&gt;In this section, we will create a custom title bar for our main window. To do this, we will create a new class by inheriting from &lt;code&gt;QWidget&lt;/code&gt;. First, go ahead and update your imports like in the code 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;from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QStyle,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

# ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here, we've imported a few new classes. We will use these classes as building blocks for our title bar. Without further ado, let's get into the title bar code. We'll introduce the code in small consecutive chunks to facilitate the explanation. Here's the first piece:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.ColorRole.Highlight)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code snippet, we create a new class by inheriting from &lt;code&gt;QWidget&lt;/code&gt;. This way, our title bar will have all the standard features and functionalities of any PyQt widgets. In the class initializer, we set &lt;code&gt;autoFillBackground&lt;/code&gt; to true because we want to give a custom color to the bar. The next line of code sets the title bar's background color to &lt;code&gt;QPalette.ColorRole.Highlight&lt;/code&gt;, which is a blueish color.&lt;/p&gt;
&lt;p&gt;The next line of code creates and initializes an instance attribute called &lt;code&gt;initial_pos&lt;/code&gt;. We'll use this attribute later on when we deal with moving the window around our screen.&lt;/p&gt;
&lt;p&gt;The final three lines of code allow us to create a layout for our title bar. Because the title bar should be horizontally oriented, we use a &lt;code&gt;QHBoxLayout&lt;/code&gt; class to structure it.&lt;/p&gt;
&lt;p&gt;The piece of code below deals with our window's title:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setStyleSheet(
            """QLabel {
                   font-weight: bold;
                   border: 2px solid black;
                   border-radius: 12px;
                   margin: 2px;
                }
            """
        )
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first line of new code creates a &lt;code&gt;title&lt;/code&gt; attribute. It's a &lt;code&gt;QLabel&lt;/code&gt; object that will hold the window's title. Because we want to build a cool title bar, we'd like to add some custom styling to the title. To do this, we use the &lt;code&gt;setStyleSheet()&lt;/code&gt; method with a string representing a CSS style sheet as an argument. The style sheet tweaks the font, borders, and margins of our title label.&lt;/p&gt;
&lt;p&gt;Next, we center the title using the &lt;code&gt;setAlignment()&lt;/code&gt; method with the &lt;code&gt;Qt.AlignmentFlag.AlignCenter&lt;/code&gt; flag as an argument.&lt;/p&gt;
&lt;p&gt;In the conditional statement, we check whether our window has a title. If that's the case, we set the text of our &lt;code&gt;title&lt;/code&gt; label to the current window's title. Finally, we added the &lt;code&gt;title&lt;/code&gt; label to the title bar layout.&lt;/p&gt;
&lt;p&gt;The next step in our journey to build a custom title bar is to provide standard window controls. In other words, we need to add the &lt;em&gt;minimize&lt;/em&gt;, &lt;em&gt;maximize&lt;/em&gt;, &lt;em&gt;close&lt;/em&gt;, and &lt;em&gt;normal&lt;/em&gt; buttons. These buttons will allow our users to interact with our window. To create the buttons, we'll use the &lt;code&gt;QToolButton&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;Here's the required 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;# ...

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        # Min button
        self.min_button = QToolButton(self)
        min_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMinButton
        )
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMaxButton
        )
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarCloseButton
        )
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarNormalButton
        )
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this code snippet, we define all the required buttons by instantiating the &lt;code&gt;QToolButton&lt;/code&gt; class. The &lt;em&gt;minimize&lt;/em&gt;, &lt;em&gt;maximize&lt;/em&gt;, and &lt;em&gt;close&lt;/em&gt; buttons follow the same pattern. We create the button, define an icon for the buttons at hand, and set the icon using the &lt;code&gt;setIcon()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Note that we use the standard icons that PyQt provides. For example, the &lt;em&gt;minimize&lt;/em&gt; button uses the &lt;code&gt;SP_TitleBarMinButton&lt;/code&gt; icon. Similarly, the &lt;em&gt;maximize&lt;/em&gt; and &lt;em&gt;close&lt;/em&gt; buttons use the &lt;code&gt;SP_TitleBarMaxButton&lt;/code&gt; and &lt;code&gt;SP_TitleBarCloseButton&lt;/code&gt; icons. We find all these icons in the &lt;code&gt;QStyle.StandardPixmap&lt;/code&gt; namespace.&lt;/p&gt;
&lt;p&gt;Finally, we connect the button's &lt;code&gt;clicked()&lt;/code&gt; signal to the appropriate slot. For the minimize button, the proper slot is &lt;code&gt;.showMinimized()&lt;/code&gt;. For the maximize and close buttons, the right slots are &lt;code&gt;.showMaximized()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt;, respectively. All these slots are part of the main window's class.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;normal&lt;/em&gt; button at the end of the above code uses the &lt;code&gt;SP_TitleBarNormalButton&lt;/code&gt; icon and &lt;code&gt;showNormal()&lt;/code&gt; slot. This button has an extra setting. We've set its visibility to &lt;code&gt;False&lt;/code&gt;, meaning that the button will be hidden by default. It'll only appear when we maximize the window to allow us to return to the normal state.&lt;/p&gt;
&lt;p&gt;Now that we've created and tweaked the buttons, we must add them to our title bar. To do this, we can use the following &lt;code&gt;for&lt;/code&gt; 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;# ...

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        # ...
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(28, 28))
            button.setStyleSheet(
                """QToolButton {
                        border: 2px solid white;
                        border-radius: 12px;
                    }
                """
            )
            title_bar_layout.addWidget(button)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This loop iterates over our four buttons in a predefined order. The first thing to do inside the loop is to define the focus policy of each button. We don't want these buttons to steal focus from widgets in the window's workspace, so we set their focus policy to &lt;code&gt;NoFocus&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we set a fixed size of 28 by 28 pixels for the three buttons using the &lt;code&gt;setFixedSize()&lt;/code&gt; method with a &lt;code&gt;QSize&lt;/code&gt; object as an argument.&lt;/p&gt;
&lt;p&gt;Our main goal in this section is to create a custom title bar. A handy way to customize the look and feel of PyQt widgets is to use CSS style sheets. In the above piece of code, we use the &lt;code&gt;setStyleSheet()&lt;/code&gt; method to apply a custom CSS style sheet to our four buttons. The sheet defines a white and round border for each button.&lt;/p&gt;
&lt;p&gt;The final line in the above code calls the &lt;code&gt;addWidget()&lt;/code&gt; method to add each custom button to our title bar's layout. That's it! We're now ready to give our title bar a try. Go ahead and run the application from your command line. You'll see a window like the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A PyQt window with a custom title bar" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/window-with-custom-title-bar.png?tr=w-600 600w" loading="lazy" width="1000" height="600"/&gt;
&lt;em&gt;A PyQt window with a custom title bar&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is pretty simple styling, but you get the idea. You can tweak the title bar further, depending on your needs. For example, you can change the colors and borders, customize the title's font, add other widgets to the bar, and more.&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;  We'll apply some nicer styles later, once we have the functionality in place! Keep reading.&lt;/p&gt;
&lt;p&gt;Even though the title bar looks different, it has limited functionality. For example, if you click the &lt;em&gt;maximize&lt;/em&gt; button, then the window will change to its maximized state. However, the &lt;em&gt;normal&lt;/em&gt; button won't show up to allow you to return the window to its previous state.&lt;/p&gt;
&lt;p&gt;In addition to this, if you try to move the window around your screen, you'll quickly notice a problem: it's impossible to move the window!&lt;/p&gt;
&lt;p&gt;In the following sections, we'll write the necessary code to fix these issues and make our custom title bar fully functional. To kick things off, let's start by fixing the state issues.&lt;/p&gt;
&lt;h2 id="updating-the-windows-state"&gt;Updating the Window's State&lt;/h2&gt;
&lt;p&gt;To fix the issue related to the window's state, we'll override the &lt;code&gt;changeEvent()&lt;/code&gt; method on the &lt;code&gt;MainWindow&lt;/code&gt; class. This method is called directly by Qt whenever the window state changes, e.g. if the window is maximized or hidden. By overriding this event, we can add our own custom behavior.&lt;/p&gt;
&lt;p&gt;Here's the code that overrides the &lt;code&gt;changeEvent()&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 PyQt5.QtCore import QSize, Qt, QEvent
# ...

class MainWindow(QMainWindow):
    # ...

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method is fairly straightforward. We check the event type to see if it is a &lt;code&gt;WindowStateChange&lt;/code&gt;. If that's the case, we call the &lt;code&gt;window_state_changed()&lt;/code&gt; method of our custom title bar, passing the current window's state as an argument. In the final two lines, we call the parent class's &lt;code&gt;changeEvent()&lt;/code&gt; method and accept the event to signal that we've correctly processed it.&lt;/p&gt;
&lt;p&gt;Here's the implementation of our &lt;code&gt;window_state_changed()&lt;/code&gt; method in &lt;code&gt;CustomTitleBar&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method takes a window's &lt;code&gt;state&lt;/code&gt; as an argument. Depending on the value of the &lt;code&gt;state&lt;/code&gt; parameter, we will show or hide the maximize and restore buttons.&lt;/p&gt;
&lt;p&gt;First, if the window is currently maximized we will show the &lt;em&gt;normal&lt;/em&gt; button and hide the &lt;em&gt;maximize&lt;/em&gt; button. Alternatively, if the window is currently &lt;em&gt;not&lt;/em&gt; maximized we will hide the &lt;em&gt;normal&lt;/em&gt; button and show the &lt;em&gt;maximize&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;The effect of this, together with the order we added the buttons above, is that when you maximize the window the &lt;em&gt;maximize&lt;/em&gt; button will &lt;em&gt;appear to be&lt;/em&gt; replaced with the &lt;em&gt;normal&lt;/em&gt; button. When you restore the window to its normal size, the &lt;em&gt;normal&lt;/em&gt; button will be replaced with the &lt;em&gt;maximize&lt;/em&gt; button.&lt;/p&gt;
&lt;p&gt;Go ahead and run the app again. Click the &lt;em&gt;maximize&lt;/em&gt; button. You'll note that when the window gets maximized, the middle button changes its icon. Now you have access to the &lt;em&gt;normal&lt;/em&gt; button. If you click it, then the window will recover its previous state.&lt;/p&gt;
&lt;h2 id="handling-the-windows-movements"&gt;Handling the Window's Movements&lt;/h2&gt;
&lt;p&gt;Now it's time to write the code that enables us to move the window around the screen while holding the mouse's left button on the title bar. To do this, we only need to add code to the &lt;code&gt;CustomTitleBar&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;In particular, we need to override three mouse events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mousePressEvent()&lt;/code&gt; will let us know when the user clicks on our custom title bar using the mouse's left button. This may indicate that the window movement should start.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseMoveEvent()&lt;/code&gt; will let us process window movement.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseReleaseEvent()&lt;/code&gt; will let us know when the user has released the mouse's left button so that we can stop moving the window.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's the code that overrides the &lt;code&gt;mousePressEvent()&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.pos()
        super().mousePressEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this method, we first check if the user clicks on the title bar using the mouse's left-click button. If that's the case, then we update our &lt;code&gt;initial_pos&lt;/code&gt; attribute to the clicked point. Remember that we defined &lt;code&gt;initial_pos&lt;/code&gt; and initialized it to &lt;code&gt;None&lt;/code&gt; back in the &lt;code&gt;__init__()&lt;/code&gt; method of &lt;code&gt;CustomTitleBar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we need to override the &lt;code&gt;mouseMoveEvent()&lt;/code&gt; method. Here's the required 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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.pos() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This &lt;code&gt;if&lt;/code&gt; statement in &lt;code&gt;mouseMoveEvent()&lt;/code&gt; checks if the &lt;code&gt;initial_pos&lt;/code&gt; attribute is not &lt;code&gt;None&lt;/code&gt;. If this condition is true, then the &lt;code&gt;if&lt;/code&gt; code block executes because we have a valid initial position.&lt;/p&gt;
&lt;p&gt;The first line in the &lt;code&gt;if&lt;/code&gt; code block calculates the difference, or &lt;code&gt;delta&lt;/code&gt;, between the current and initial mouse positions. To get the current position, we call the &lt;code&gt;pos()&lt;/code&gt; method on the &lt;code&gt;event&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;The following four lines update the position of our application's main window by adding the &lt;code&gt;delta&lt;/code&gt; values to the current window position. The &lt;code&gt;move()&lt;/code&gt; method does the hard work of moving the window.&lt;/p&gt;
&lt;p&gt;In summary, this code updates the window position based on the movement of our mouse. It tracks the initial position of the mouse, calculates the difference between the initial position and the current position, and applies that difference to the window's position.&lt;/p&gt;
&lt;p&gt;Finally, we can complete the &lt;code&gt;mouseReleaseEvent()&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;# ...

class CustomTitleBar(QWidget):
    # ...

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method's implementation is pretty straightforward. Its purpose is to reset the initial position by setting it back to &lt;code&gt;None&lt;/code&gt; when the mouse is released, indicating that the drag is complete.&lt;/p&gt;
&lt;p&gt;That's it! Go ahead and run your app again. Click on your custom title bar and move the window around while holding the mouse's left-click button. Can you move the window? Great! Your custom title bar is now fully functional.&lt;/p&gt;
&lt;p&gt;The completed code for the custom title bar 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;from PyQt5.QtCore import QEvent, QSize, Qt
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QStyle,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.setAutoFillBackground(True)
        self.setBackgroundRole(QPalette.ColorRole.Highlight)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)

        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setStyleSheet(
            """QLabel {
                   font-weight: bold;
                   border: 2px solid black;
                   border-radius: 12px;
                   margin: 2px;
                }
            """
        )
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)

        # Min button
        self.min_button = QToolButton(self)
        min_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMinButton,
        )
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMaxButton,
        )
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarCloseButton
        )
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarNormalButton
        )
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # Add buttons
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(28, 28))
            button.setStyleSheet(
                """QToolButton {
                        border: 2px solid white;
                        border-radius: 12px;
                    }
                """
            )
            title_bar_layout.addWidget(button)

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.pos()
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.pos() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        central_widget = QWidget()
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        work_space_layout.addWidget(QLabel("Hello, World!", self))

        central_widget_layout = QVBoxLayout()
        central_widget_layout.setContentsMargins(0, 0, 0, 0)
        central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        central_widget_layout.addWidget(self.title_bar)
        central_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(central_widget_layout)
        self.setCentralWidget(central_widget)

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="making-it-a-little-more-beautiful"&gt;Making It a Little More Beautiful&lt;/h2&gt;
&lt;p&gt;So far, we've covered the technical aspects of styling our window with a custom title bar, and added the code to make it function as expected. But it doesn't look &lt;em&gt;great&lt;/em&gt;. In this section we'll take our existing code &amp;amp; tweak the styling and buttons to produce something that's a little more professional looking.&lt;/p&gt;
&lt;p&gt;One common reason for wanting to apply custom title bars to a window is to integrate the title bar with the rest of the application. This technique is called a &lt;em&gt;unified title bar&lt;/em&gt; and can be seen in some popular applications such as web browsers, or Spotify:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Unified title bar in Spotify" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/unified-titlebar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/unified-titlebar.png?tr=w-600 600w" loading="lazy" width="534" height="334"/&gt;&lt;/p&gt;
&lt;p&gt;In this section, we'll look at how we can reproduce the same effect in PyQt using a combination of stylesheets and icons. Below is a screenshot of the final result which we'll be building:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Style custom title bar in PyQt5" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/styled-window-custom-title-bar.png?tr=w-600 600w" loading="lazy" width="1018" height="588"/&gt;&lt;/p&gt;
&lt;p&gt;As you can see the window and the toolbar blend nicely together and the window has rounded corners. There are a few different ways to do this, but we'll cover a simple approach using Qt stylesheets to apply styling over the entire window.&lt;/p&gt;
&lt;p&gt;In order to customize the shape of the window, we need to first tell the OS to stop drawing the default window outline and background for us. We do that by setting a &lt;em&gt;window attribute&lt;/em&gt; on the window. This is similar to the flags we already discussed, in that it turns on and off different window manager 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;# ...
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've added a call to &lt;code&gt;self.setAttribute()&lt;/code&gt;, which sets the attribute &lt;code&gt;Qt.WidgetAttribute.WA_TranslucentBackground&lt;/code&gt; on the window. If you run the code now, you will see the window has become transparent, with only the widget text and toolbar visible.&lt;/p&gt;
&lt;p&gt;Next, we'll tell Qt to draw a new &lt;em&gt;custom&lt;/em&gt; background for us. If you've worked with QSS before, the most obvious way to apply curved edges to the window using QSS stylesheets would be to set &lt;code&gt;border-radius&lt;/code&gt; styles on the main window 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;#...
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        self.setStyleSheet("background-color: gray; border-radius: 10px;")
#...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;However, if you try this, you'll notice that it doesn't work. If you enable a translucent background, the background of the window is not drawn (including your styles). If you &lt;em&gt;don't&lt;/em&gt; set translucent background, the window is filled to the edges with a solid color ignoring the border radius:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Stylesheets can't alter window shape" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/stylesheet-window-styling.jpg?tr=w-600 600w" loading="lazy" width="1850" height="608"/&gt;.&lt;/p&gt;
&lt;p&gt;The good news is that, with a bit of lateral thinking, there is a simple solution. We already know that we can construct interfaces by nesting widgets in layouts. Since we can't style the &lt;code&gt;border-radius&lt;/code&gt; of a window, but we &lt;em&gt;can&lt;/em&gt; style any other widget, the solution is to simply add a container widget into our window and apply the curved-edge and background styles to that.&lt;/p&gt;
&lt;p&gt;On our &lt;code&gt;MainWindow&lt;/code&gt; class, we already have a &lt;em&gt;central widget&lt;/em&gt; which contains our layout, so we can apply the styles 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;# ...

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        central_widget = QWidget()
        # This container holds the window contents, so we can style it.
        central_widget.setObjectName("Container")
        central_widget.setStyleSheet(
            """#Container {
                    background: qlineargradient(x1:0 y1:0, x2:1 y2:1, stop:0 #051c2a stop:1 #44315f);
                    border-radius: 5px;
                }
            """
        )
        self.title_bar = CustomTitleBar(self)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We've taken the existing &lt;code&gt;central_widget&lt;/code&gt; object and assigned an &lt;em&gt;object name&lt;/em&gt; to it. This is an ID which we can use to refer to the widget from QSS and apply our styles specifically to that widget.&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;  If you're familiar with CSS, you might expect that IDs like &lt;code&gt;#Container&lt;/code&gt; must be unique. However, they are not: you can give multiple widgets the same object name if you like. So you can re-use this technique and QSS on multiple windows in your application without problems.&lt;/p&gt;
&lt;p&gt;With this style applied on our window, we have a nice gradient background with curved corners.&lt;/p&gt;
&lt;p&gt;Unfortunately, the title bar we created is drawn filled, so the background and curved corners of our window are overwritten. To make things look coherent we need to make our title bar also transparent by removing the background color &amp;amp; auto-fill behavior we set earlier.&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;  We don't need to set any flags or attributes on this widget because it is not a window. A &lt;code&gt;QWidget&lt;/code&gt; object is transparent by default.&lt;/p&gt;
&lt;p&gt;We can also make some tweaks to the style of the title label, such as making the title capitalized using &lt;code&gt;text-transform: uppercase&lt;/code&gt;, adjusting the font size and color -- feel free to customize this yourself:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # self.setAutoFillBackground(True) # &amp;lt;-- remove
        # self.setBackgroundRole(QPalette.ColorRole.Highlight) # &amp;lt;-- remove
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title.setStyleSheet(
            """QLabel {
                    text-transform: uppercase;
                    font-size: 10pt;
                    margin-left: 48px;
                    color: white;
                }
            """
        )
&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;  QSS is &lt;em&gt;very&lt;/em&gt; similar to CSS, especially for text styling.&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 &lt;code&gt;margin-left: 48px&lt;/code&gt; is to compensate for the 3 &amp;times; 16 px window icons on the right-hand side so the text aligns centrally.&lt;/p&gt;
&lt;p&gt;The icons are currently using built-in Qt icons which are a little bit plain &amp;amp; ugly. Next let's update the icons, using custom SVG icons of simple colored circles, for the minimize, maximize, close &amp;amp; restore 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;from PyQt5.QtGui import QIcon
# ...
class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # ...
        # Min button
        self.min_button = QToolButton(self)
        min_icon = QIcon()
        min_icon.addFile('min.svg')
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = QIcon()
        max_icon.addFile('max.svg')
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = QIcon()
        close_icon.addFile('close.svg') # Close has only a single state.
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = QIcon()
        normal_icon.addFile('normal.svg')
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # ...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This code follows the same basic structure as before, but instead of using the built-in icons here we're loading our icons from SVG images. These images are very simple, consisting of a single circle in green, red or yellow for the different states mimicking macOS.&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 &lt;code&gt;normal.svg&lt;/code&gt; file for returning a maximized window to normal size shows a semi-transparent green circle for simplicity's sake, but you can include iconography and hover behaviors on the buttons if you prefer.&lt;/p&gt;
&lt;p&gt;The final step is to iterate through the created buttons, adding them to the title bar layout. This is slightly tweaked from before to remove the border styling replacing it with simple padding and setting the icon sizes to 16px. Because we are using SVG files, the icons will automatically scale to the available space:&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 CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        # ...
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(16, 16))
            button.setStyleSheet(
                """QToolButton {
                        border: none;
                        padding: 2px;
                    }
                """
            )
            title_bar_layout.addWidget(button)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And that's it! With these changes, you can now run your application and you'll see a nice sleek modern-looking UI with unified title bar and custom controls:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Style custom titlebar in PyQt5" src="https://www.pythonguis.com/static/tutorials/qt/custom-title-bar/pyqt5-custom-modern-title-bar.jpg" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt5-custom-modern-title-bar.jpg?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt5-custom-modern-title-bar.jpg?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt5-custom-modern-title-bar.jpg?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/tutorials/qt/custom-title-bar/pyqt5-custom-modern-title-bar.jpg?tr=w-600 600w" loading="lazy" width="1748" height="1052"/&gt;
&lt;em&gt;The final result, showing our unified title bar and window design.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The complete code 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;from PyQt5.QtCore import QEvent, QSize, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QToolButton,
    QVBoxLayout,
    QWidget,
)

class CustomTitleBar(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.initial_pos = None
        title_bar_layout = QHBoxLayout(self)
        title_bar_layout.setContentsMargins(1, 1, 1, 1)
        title_bar_layout.setSpacing(2)
        self.title = QLabel(f"{self.__class__.__name__}", self)
        self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.title.setStyleSheet(
            """QLabel {
                    text-transform: uppercase;
                    font-size: 10pt;
                    margin-left: 48px;
                    color: white;
                }
            """
        )

        if title := parent.windowTitle():
            self.title.setText(title)
        title_bar_layout.addWidget(self.title)

        # Min button
        self.min_button = QToolButton(self)
        min_icon = QIcon()
        min_icon.addFile("min.svg")
        self.min_button.setIcon(min_icon)
        self.min_button.clicked.connect(self.window().showMinimized)

        # Max button
        self.max_button = QToolButton(self)
        max_icon = QIcon()
        max_icon.addFile("max.svg")
        self.max_button.setIcon(max_icon)
        self.max_button.clicked.connect(self.window().showMaximized)

        # Close button
        self.close_button = QToolButton(self)
        close_icon = QIcon()
        close_icon.addFile("close.svg")  # Close has only a single state.
        self.close_button.setIcon(close_icon)
        self.close_button.clicked.connect(self.window().close)

        # Normal button
        self.normal_button = QToolButton(self)
        normal_icon = QIcon()
        normal_icon.addFile("normal.svg")
        self.normal_button.setIcon(normal_icon)
        self.normal_button.clicked.connect(self.window().showNormal)
        self.normal_button.setVisible(False)
        # Add buttons
        buttons = [
            self.min_button,
            self.normal_button,
            self.max_button,
            self.close_button,
        ]
        for button in buttons:
            button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
            button.setFixedSize(QSize(16, 16))
            button.setStyleSheet(
                """QToolButton {
                        border: none;
                        padding: 2px;
                    }
                """
            )
            title_bar_layout.addWidget(button)

    def window_state_changed(self, state):
        if state == Qt.WindowState.WindowMaximized:
            self.normal_button.setVisible(True)
            self.max_button.setVisible(False)
        else:
            self.normal_button.setVisible(False)
            self.max_button.setVisible(True)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.initial_pos = event.pos()
        super().mousePressEvent(event)
        event.accept()

    def mouseMoveEvent(self, event):
        if self.initial_pos is not None:
            delta = event.pos() - self.initial_pos
            self.window().move(
                self.window().x() + delta.x(),
                self.window().y() + delta.y(),
            )
        super().mouseMoveEvent(event)
        event.accept()

    def mouseReleaseEvent(self, event):
        self.initial_pos = None
        super().mouseReleaseEvent(event)
        event.accept()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Title Bar")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
        central_widget = QWidget()
        # This container holds the window contents, so we can style it.
        central_widget.setObjectName("Container")
        central_widget.setStyleSheet(
            """#Container {
                    background: qlineargradient(x1:0 y1:0, x2:1 y2:1, stop:0 #051c2a stop:1 #44315f);
                    border-radius: 5px;
                }
            """
        )
        self.title_bar = CustomTitleBar(self)

        work_space_layout = QVBoxLayout()
        work_space_layout.setContentsMargins(11, 11, 11, 11)
        hello_label = QLabel("Hello, World!", self)
        hello_label.setStyleSheet("color: white;")
        work_space_layout.addWidget(hello_label)

        centra_widget_layout = QVBoxLayout()
        centra_widget_layout.setContentsMargins(0, 0, 0, 0)
        centra_widget_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
        centra_widget_layout.addWidget(self.title_bar)
        centra_widget_layout.addLayout(work_space_layout)

        central_widget.setLayout(centra_widget_layout)
        self.setCentralWidget(central_widget)

    def changeEvent(self, event):
        if event.type() == QEvent.Type.WindowStateChange:
            self.title_bar.window_state_changed(self.windowState())
        super().changeEvent(event)
        event.accept()

if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that you've also set the color of the "Hello, World!" label to white so that it's visible on a dark background.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this tutorial, we have learned the fundamentals of creating custom title bars in PyQt. To do this, we have combined PyQt's widgets, layouts, and styling capabilities to create a visually appealing title bar for a PyQt app.&lt;/p&gt;
&lt;p&gt;With this skill under your belt, you're now ready to create title bars that align perfectly with your application's unique style and branding. This will allow you to break away from the standard window decoration provided by your operating system and add a personal touch to your user interface.&lt;/p&gt;
&lt;p&gt;Now let your imagination run and transform your PyQt application's UX.&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.mfitzp.com/pyqt5-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt"/><category term="pyqt5"/><category term="window"/><category term="title bar"/><category term="python"/><category term="qt"/><category term="qt5"/></entry><entry><title>How Do I Display Images in PyQt6? — Using QLabel and QPixmap to easily add images to your applications</title><link href="https://www.pythonguis.com/faq/adding-images-to-pyqt6-applications/" rel="alternate"/><published>2024-02-14T06:00:00+00:00</published><updated>2024-02-14T06:00:00+00:00</updated><author><name>John Lim</name></author><id>tag:www.pythonguis.com,2024-02-14:/faq/adding-images-to-pyqt6-applications/</id><summary type="html">Adding images to your Python GUI application is a common requirement, whether you're building an image/photo viewer, or just want to add some decoration to your GUI. Unfortunately, because of how this is done in Qt, it can be a little bit tricky to work out at first.</summary><content type="html">
            &lt;p&gt;Adding images to your Python GUI application is a common requirement, whether you're building an image/photo viewer, or just want to add some decoration to your GUI. Unfortunately, because of how this is done in Qt, it can be a little bit tricky to work out at first.&lt;/p&gt;
&lt;p&gt;In this short tutorial, we will look at how you can display an image in a PyQt6 application using &lt;code&gt;QLabel&lt;/code&gt; and &lt;code&gt;QPixmap&lt;/code&gt;, with both Python code and Qt Designer examples.&lt;/p&gt;
&lt;h2 id="which-widget-to-use-for-displaying-images-in-pyqt6"&gt;Which Widget to Use for Displaying Images in PyQt6?&lt;/h2&gt;
&lt;p&gt;Since you're wanting to insert an image you might be expecting to use a widget named &lt;code&gt;QImage&lt;/code&gt; or similar, but that would make a bit too much sense! &lt;code&gt;QImage&lt;/code&gt; is actually Qt's image &lt;em&gt;object&lt;/em&gt; type, which is used to store the actual image data for use within your application. The &lt;em&gt;widget&lt;/em&gt; you use to display an image is &lt;code&gt;QLabel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The primary use of &lt;code&gt;QLabel&lt;/code&gt; is of course to add labels to a UI, but it also has the ability to display an image &amp;mdash; or &lt;em&gt;pixmap&lt;/em&gt; &amp;mdash; instead, covering the entire area of the widget. Below we'll look at how to use &lt;code&gt;QLabel&lt;/code&gt; with &lt;code&gt;QPixmap&lt;/code&gt; to display an image in your PyQt6 applications. If you're new to PyQt6, you might want to start with our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;tutorial on creating your first PyQt6 window&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="displaying-images-in-pyqt6-using-qt-designer"&gt;Displaying Images in PyQt6 Using Qt Designer&lt;/h2&gt;
&lt;p&gt;First, create a &lt;em&gt;MainWindow&lt;/em&gt; object in Qt Designer and add a "Label" to it. You can find Label in &lt;em&gt;Display Widgets&lt;/em&gt; at the bottom of the left hand panel. Drag this onto the &lt;code&gt;QMainWindow&lt;/code&gt; to add it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="MainWindow with a single QLabel added in Qt Designer" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/1.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/1.png?tr=w-600 600w" loading="lazy" width="1917" height="1027"/&gt;
&lt;em&gt;MainWindow with a single QLabel added&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, with the Label selected, look in the right hand &lt;code&gt;QLabel&lt;/code&gt; properties panel for the &lt;code&gt;pixmap&lt;/code&gt; property (scroll down to the blue region). From the property editor dropdown select "Choose File&amp;hellip;" and select an image file to insert.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7sts93f00040aky0k56e1i8/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;As you can see, the image is inserted, but the image is kept at its original size, cropped to the boundaries of the &lt;code&gt;QLabel&lt;/code&gt; box. You need to resize the &lt;code&gt;QLabel&lt;/code&gt; to be able to see the entire image.&lt;/p&gt;
&lt;p&gt;In the same controls panel, click to enable &lt;code&gt;scaledContents&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
&lt;iframe allowfullscreen="" allowtransparency="" src="https://www.tella.tv/video/cm7stu0wj00050akza5491h2k/embed?b=0&amp;amp;title=0&amp;amp;a=0&amp;amp;autoPlay=true&amp;amp;loop=1&amp;amp;t=0&amp;amp;muted=1&amp;amp;wt=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;scaledContents&lt;/code&gt; is enabled the image is resized to fit the bounding box of the &lt;code&gt;QLabel&lt;/code&gt; widget. This shows the entire image at all times, although it does not respect the aspect ratio of the image if you resize the widget.&lt;/p&gt;
&lt;p&gt;You can now save your UI to file (e.g. as &lt;code&gt;mainwindow.ui&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To view the resulting UI, we can use the standard application template below. This loads the &lt;code&gt;.ui&lt;/code&gt; file we've created (&lt;code&gt;mainwindow.ui&lt;/code&gt;), creates the window and starts up the application. For more on designing UIs with Qt Designer, see our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qt-designer-gui-layout/"&gt;guide to Qt Designer GUI layout&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-PyQt6"&gt;PyQt6&lt;/span&gt;
&lt;pre&gt;&lt;code class="PyQt6"&gt;import sys
from PyQt6 import QtWidgets, uic

app = QtWidgets.QApplication(sys.argv)

window = uic.loadUi("mainwindow.ui")
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Running the above code will create a window, with the image displayed in the middle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyQt6 application showing an image using QLabel and Qt Designer" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/5.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/5.png?tr=w-600 600w" loading="lazy" width="802" height="639"/&gt;
&lt;em&gt;QtDesigner application showing a Cat&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="displaying-images-using-python-code-with-qlabel-and-qpixmap"&gt;Displaying Images Using Python Code with QLabel and QPixmap&lt;/h2&gt;
&lt;p&gt;Instead of using Qt Designer, you might also want to show an image in your PyQt6 application through code. As before we use a &lt;code&gt;QLabel&lt;/code&gt; widget and add a &lt;em&gt;pixmap&lt;/em&gt; image to it. This is done by creating a &lt;code&gt;QPixmap&lt;/code&gt; object and passing it to the &lt;code&gt;QLabel&lt;/code&gt; method &lt;code&gt;.setPixmap()&lt;/code&gt;. The full code is shown below.&lt;/p&gt;
&lt;div class="tabbed-area multicode"&gt;&lt;ul class="tabs"&gt;&lt;li class="tab-link current" data-tab="44403d6775684e1cae25c60a3f7c87a6" v-on:click="switch_tab"&gt;PyQt6&lt;/li&gt;
&lt;li class="tab-link" data-tab="8fd108a461b943d5b56bc6058ed53410" v-on:click="switch_tab"&gt;PySide6&lt;/li&gt;
&lt;li class="tab-link" data-tab="b5250635ec21460494bc8d7090aa6b4c" v-on:click="switch_tab"&gt;PyQt5&lt;/li&gt;
&lt;li class="tab-link" data-tab="50bcaaaaf01e4446b80df34bce00bd15" v-on:click="switch_tab"&gt;PySide2&lt;/li&gt;&lt;/ul&gt;&lt;div class="tab-content current code-block-outer" id="44403d6775684e1cae25c60a3f7c87a6"&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.QtGui import QPixmap
from PyQt6.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="8fd108a461b943d5b56bc6058ed53410"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="b5250635ec21460494bc8d7090aa6b4c"&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-content code-block-outer" id="50bcaaaaf01e4446b80df34bce00bd15"&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 PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QMainWindow, QApplication, QLabel

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = "Image Viewer"
        self.setWindowTitle(self.title)

        label = QLabel(self)
        pixmap = QPixmap('cat.jpg')
        label.setPixmap(pixmap)
        self.setCentralWidget(label)
        self.resize(pixmap.width(), pixmap.height())


app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The block of code below shows the process of creating the &lt;code&gt;QLabel&lt;/code&gt;, creating a &lt;code&gt;QPixmap&lt;/code&gt; object from our file &lt;code&gt;cat.jpg&lt;/code&gt; (passed as a file path), setting this &lt;code&gt;QPixmap&lt;/code&gt; onto the &lt;code&gt;QLabel&lt;/code&gt; with &lt;code&gt;.setPixmap()&lt;/code&gt; and then finally resizing the window to fit the image.&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 = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Launching this code will show a window with the cat photo displayed and the window sized to the size of the image.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PyQt6 QMainWindow displaying an image with QLabel and QPixmap" src="https://www.pythonguis.com/static/faq/adding-images-to-applications/4.png" srcset="https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-100 100w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-200 200w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-400 400w, https://ik.imagekit.io/mfitzp/pythonguis/static/faq/adding-images-to-applications/4.png?tr=w-600 600w" loading="lazy" width="602" height="439"/&gt;
&lt;em&gt;QMainWindow with Cat image displayed&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="scaling-images-in-pyqt6-with-setscaledcontents"&gt;Scaling Images in PyQt6 with setScaledContents&lt;/h2&gt;
&lt;p&gt;Just as in Qt Designer, you can call &lt;code&gt;.setScaledContents(True)&lt;/code&gt; on your &lt;code&gt;QLabel&lt;/code&gt; image to enable scaled mode, which resizes the image to fit the available space. This is useful when you want the displayed image to automatically adapt to the size of the label 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;label = QLabel(self)
pixmap = QPixmap('cat.jpg')
label.setPixmap(pixmap)
label.setScaledContents(True)
self.setCentralWidget(label)
self.resize(pixmap.width(), pixmap.height())
&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;  Notice that you set the scaled state on the &lt;code&gt;QLabel&lt;/code&gt; widget and not the image pixmap itself.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this quick tutorial we've covered how to display images in your PyQt6 applications using &lt;code&gt;QLabel&lt;/code&gt; and &lt;code&gt;QPixmap&lt;/code&gt;. We walked through two approaches: using Qt Designer to visually add images to your UI, and writing Python code to programmatically load and display images with &lt;code&gt;QPixmap&lt;/code&gt;. Both methods rely on &lt;code&gt;QLabel&lt;/code&gt; as the display widget and &lt;code&gt;QPixmap&lt;/code&gt; to handle the image data, giving you a straightforward way to enhance your Python GUI applications with images. You also learned how to use &lt;code&gt;setScaledContents&lt;/code&gt; to automatically scale images to fit the label widget.&lt;/p&gt;
&lt;p&gt;For more advanced image and graphics work, take a look at our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-bitmap-graphics/"&gt;bitmap graphics in PyQt6&lt;/a&gt; or explore other &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-widgets/"&gt;PyQt6 widgets&lt;/a&gt; to build richer interfaces.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PySide2 see my book, &lt;a href="https://www.mfitzp.com/pyside2-book/"&gt;Create GUI Applications with Python &amp; Qt5.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="images"/><category term="qlabel"/><category term="widgets"/><category term="graphics"/><category term="qpixmap"/><category term="pixmap"/><category term="python"/><category term="qt"/><category term="qt6"/></entry><entry><title>Can You Charge for Open-Source Software? — Making Money from Open-Source Projects</title><link href="https://www.pythonguis.com/faq/charge-for-open-source-software/" rel="alternate"/><published>2023-01-30T06:00:00+00:00</published><updated>2023-01-30T06:00:00+00:00</updated><author><name>S.M. Oliva</name></author><id>tag:www.pythonguis.com,2023-01-30:/faq/charge-for-open-source-software/</id><summary type="html">The use of the term &lt;a href="https://en.wikipedia.org/wiki/Free_software"&gt;"free software"&lt;/a&gt; to describe software that is published under open-source licenses has often led to confusion over whether or not developers can actually charge money for their work.</summary><content type="html">&lt;p&gt;The use of the term &lt;a href="https://en.wikipedia.org/wiki/Free_software"&gt;"free software"&lt;/a&gt; to describe software that is published under open-source licenses has often led to confusion over whether or not developers can actually charge money for their work.&lt;/p&gt;
&lt;p&gt;Indeed, this confusion came up in one of the few litigated court cases in the United States involving open-source licenses. In 2006, a federal appeals court in Chicago dismissed a lawsuit brought by a developer -- &lt;a href="https://scholar.google.com/scholar_case?case=3689055814945636124"&gt;&lt;em&gt;Williams v. International Business Machines Corp.&lt;/em&gt;&lt;/a&gt; -- who alleged that various companies and entities responsible for developing Linux had engaged in a massive antitrust conspiracy to "eliminate competition in the operating system market by making Linux available at an unbeatable price," i.e., for nothing.&lt;/p&gt;
&lt;p&gt;Frank Easterbrook, the judge who authored the appeals court's decision, explained that the &lt;a href="https://en.wikipedia.org/wiki/GNU_General_Public_License"&gt;GNU General Public License (GPL)&lt;/a&gt; "sets a price of zero" for covered works but that did not qualify as an illegal "price fixing" agreement under U.S. antitrust law. The flaw in Easterbrook's reasoning, however, was that the GPL says nothing of the sort. To the contrary, Section 4 of version 3 of the GPL expressly states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. (&lt;a href="https://www.gnu.org/licenses/gpl-3.0.en.html"&gt;Source&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So why did Easterbrook say the GPL sets a price of "zero"? The likely explanation was that in the context of a U.S. appeals court proceeding, Easterbrook took the allegations of the person who filed the lawsuit at face value. The developer who alleged the GPL violated antitrust law claimed the GPL mandated a zero-price, and the courts held that even assuming that was true, there was no antitrust violation since there was no harm to consumers.&lt;/p&gt;
&lt;p&gt;Still, the fact that a judicial opinion authored by a prominent U.S. jurist stated the price of GPL-covered works is zero by design reflects an ongoing misunderstanding of how open-source licenses actually work. As we discussed in a &lt;a href="https://www.pythonguis.com/faq/gpl-and-copyleft-pyqt-pyside/"&gt;prior article&lt;/a&gt;, the main goal of reciprocal open-source licenses like the GPL is to ensure that source code remains free and available to all users. But that isn't the same thing as saying an individual developer must provide their code--or modifications to existing code--without charging for it.&lt;/p&gt;
&lt;h2 id="does-free-software-mean-free-of-charge"&gt;Does "Free Software" Mean Free of Charge?&lt;/h2&gt;
&lt;p&gt;The short answer is no. "Free" in free software refers to &lt;strong&gt;freedom&lt;/strong&gt;, not price. The Free Software Foundation has long used the phrase "free as in freedom, not free as in beer" to clarify this distinction. You are legally permitted to sell open-source software, including GPL-licensed code, for any price you choose.&lt;/p&gt;
&lt;p&gt;This is a critical point for Python developers building GUI applications with frameworks like PyQt or PySide. Understanding that open-source licensing does not prevent you from charging for your work opens up real business opportunities.&lt;/p&gt;
&lt;h2 id="applying-old-rules-to-modern-software"&gt;Applying Old Rules to Modern Software&lt;/h2&gt;
&lt;p&gt;It is important to note that the original GPL was published back in 1989. The software market back then was quite different than it is now. For one thing, most software was distributed on physical media like &lt;a href="https://en.wikipedia.org/wiki/Floppy_disk"&gt;floppy disks&lt;/a&gt; and later &lt;a href="https://en.wikipedia.org/wiki/Optical_disc"&gt;optical discs&lt;/a&gt; or compact discs (CD). Most of that software only contained pre-compiled binary files without any source code. This included a lot of software that was distributed free of charge. Today, of course, most of our software comes from Internet sources.&lt;/p&gt;
&lt;p&gt;One thing that may have been lost in this transition is an understanding of charging for open-source software as a normal business practice. To illustrate my point, a few years ago I was at a sale of used books held by my local public library. There was a small section of the sale dedicated to old computer books and media. It turned out that I found a sealed, boxed copy of Corel Linux, a long-forgotten Linux distribution first published in 1999. In addition to a hefty user's manual, the box contained two CD-ROMs. The first contained the pre-compiled version of Corel Linux to install on a computer. The second was the source code for all the files on the first CD-ROM.&lt;/p&gt;
&lt;p&gt;This was not an uncommon practice back in the day when you would actually buy Linux distributions at a store. To comply with GPL or similar licensing requirements, the distributor would simply publish a separate CD-ROM with all of the source code. And if you look at section 6 of the &lt;a href="https://opensource.org/licenses/GPL-3.0"&gt;current GPL&lt;/a&gt;, it states compliance may still be accomplished by distributing object code on a physical medium "accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange." But for most modern developers, it is far more practical to offer access to source code through an online repository on Github, Gitlab, or any other source code hosting service than to provide physical media.&lt;/p&gt;
&lt;h2 id="charging-for-software-under-the-gpl-and-other-open-source-licenses"&gt;Charging for Software Under the GPL and Other Open-Source Licenses&lt;/h2&gt;
&lt;p&gt;So what does the GPL actually allow -- or prohibit -- with respect to charging for software? Here is a brief rundown of some key provisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You &lt;strong&gt;can charge any price you wish&lt;/strong&gt; -- including zero -- for any copy of a licensed work that you convey in either source or binary form.&lt;/li&gt;
&lt;li&gt;You can charge for &lt;strong&gt;providing support&lt;/strong&gt; for licensed code.&lt;/li&gt;
&lt;li&gt;You can charge for &lt;strong&gt;providing a warranty&lt;/strong&gt; for licensed code.&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;may not charge a separate licensing fee&lt;/strong&gt; or royalty to anyone who exercises their rights to use or convey GPL-licensed code, nor can you require them to collect such fees or royalties on your behalf.&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;must still provide access to the accompanying source code&lt;/strong&gt; if you convey GPL-licensed code in binary form.&lt;/li&gt;
&lt;li&gt;You can charge for the &lt;strong&gt;"reasonable cost" of preparing and distributing a physical media&lt;/strong&gt; if you provide source code in this kind of format.&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;can not charge to allow access to the source code on an online server&lt;/strong&gt; -- i.e., on the Internet --&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-about-non-gpl-open-source-licenses"&gt;What About Non-GPL Open-Source Licenses?&lt;/h2&gt;
&lt;p&gt;Not all open-source licenses follow the GPL's reciprocal model. Non-reciprocal (permissive) open-source licenses generally contain no restrictions on the sale or distribution of code. For example, the &lt;a href="https://docs.python.org/3/license.html#psf-license"&gt;Python Software Foundation License&lt;/a&gt; says nothing about when you can and can not charge for software developed with Python. You are free to develop an application using Python and charge for it without even needing to provide access to your source code. The flip side of that, however, is that if you "mix" GPL and non-GPL code, the GPL's provisions will likely still apply to the entire work.&lt;/p&gt;
&lt;p&gt;This is particularly relevant for Python GUI developers. If you build an application using PySide (which is LGPL-licensed) you have more flexibility than if you use PyQt (which requires either a GPL or commercial license). Understanding these distinctions can directly affect your monetization strategy.&lt;/p&gt;
&lt;h2 id="trademarks-and-other-intellectual-property-considerations"&gt;Trademarks and Other Intellectual Property Considerations&lt;/h2&gt;
&lt;p&gt;Another thing to consider when developing a commercial application is that while open-source licenses typically address issues related to copyrights in the underlying source code, they often do not cover other intellectual property rights attached to an application such as trademarks, which will be the subject of my next article.&lt;/p&gt;</content><category term="licensing"/><category term="pyqt"/><category term="pyside"/><category term="gpl"/><category term="open-source"/><category term="oss"/><category term="python"/><category term="qt"/></entry></feed>