This service tests the validity of an RSS 2.0 feed, checking to see that it follows the rules of the RSS specification. For advice from the RSS Advisory Board on how to implement RSS and handle issues such as enclosures and HTML encoding, read the RSS Best Practices Profile. This checker is also a validator of Atom and RSS 1.0 feeds.
Use this tester regularly to ensure that your RSS feed continues to work well in the wide audience of RSS readers, podcast clients and other software that supports the format.
This feed does not validate.
... - Create GUI Applications with Python & Qt, Released —</title>
^
In addition, interoperability with the widest range of feed readers could be improved by implementing the following recommendations.
... rel="self" type="application/rss+xml"/>
^
... &#8211; The Complete Guide with demo</title>
^
<title/>
^
<?xml version="1.0"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Planet Qt</title>
<link>http://planet.qt.io</link>
<language>en</language>
<description>"Planet Qt - http://planet.qt.io/"</description>
<atom:link href="http://planet.qt.io/rss20.xml" rel="self" type="application/rss+xml"/>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/qt-for-android-automotive-6.8.4</guid>
<title>The Qt Company Blog: Qt for Android Automotive 6.8.4</title>
<pubDate>Wed, 16 Jul 2025 03:00:00 GMT</pubDate>
<link>https://www.qt.io/blog/qt-for-android-automotive-6.8.4</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/qt-for-android-automotive-6.8.4?hsLang=en" title=""><img alt="Qt for Android Automotive 6.8.4" class="hs-featured-image" src="https://www.qt.io/hubfs/Screenshot%202025-07-08%20at%2015-11-42%20cluster-web-04%20%281%29.webp%20%28WEBP%20Image%201360%20%C3%97%201088%20pixels%29.png" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
The latest patch release for Android Automotive 6.8.4 is just released. This release is based on <a href="https://www.qt.io/blog/commercial-lts-qt-6.8.4-released?hsLang=en">Qt LTS 6.8.4</a> with 1200 <span>bug fixes, security updates, and other improvements done to Qt base</span>. There are no additional Qt for Android Automotive features delivered with 6.8.4.
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fqt-for-android-automotive-6.8.4&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/commercial-lts-qt-6.8.4-released</guid>
<title>The Qt Company Blog: Commercial LTS Qt 6.8.4 Released</title>
<pubDate>Fri, 11 Jul 2025 12:59:38 GMT</pubDate>
<link>https://www.qt.io/blog/commercial-lts-qt-6.8.4-released</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/commercial-lts-qt-6.8.4-released?hsLang=en" title=""><img alt="Commercial LTS Qt 6.8.4 Released" class="hs-featured-image" src="https://www.qt.io/hubfs/_website/Qt_8M0A6557_overlay.jpg" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
We have released Qt 6.8.4 LTS for commercial license holders today. This is the first LTS Commercial release on the Qt 6.8 series. As a patch release, Qt 6.8.4 does not add any new functionality but provides bug fixes and other improvements.
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fcommercial-lts-qt-6.8.4-released&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/security-advisory-recently-reported-denial-of-service-issue-in-qcolortransfergenericfunction-impacts-qt</guid>
<title>The Qt Company Blog: Security advisory: Recently reported denial of service issue in QColorTransferGenericFunction impacts Qt</title>
<pubDate>Fri, 11 Jul 2025 09:00:00 GMT</pubDate>
<link>https://www.qt.io/blog/security-advisory-recently-reported-denial-of-service-issue-in-qcolortransfergenericfunction-impacts-qt</link>
<description><p>
When passing values outside of the expected range to QColorTransferGenericFunction it can cause a denial of service, for example, this can happen when passing a specifically crafted ICC profile to QColorSpace::fromICCProfile.
</p>
<p>
This has been assigned the CVE id CVE-2025-5992.
</p><br>
<p>
 
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fsecurity-advisory-recently-reported-denial-of-service-issue-in-qcolortransfergenericfunction-impacts-qt&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/how-to-use-qt-webassembly-the-complete-guide-with-demo</guid>
<title>Scythe Studio Qt Blog: How to use Qt WebAssembly &#8211; The Complete Guide with demo</title>
<pubDate>Thu, 10 Jul 2025 15:38:15 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/how-to-use-qt-webassembly-the-complete-guide-with-demo</link>
<description><p>
Hey, welcome back to another blog post. Today we’re going to talk about the new Qt WebAssembly. This post will […]
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/get-better-qml-code-completions-with-codellama-13b-qml-and-7b-qml-v2.0</guid>
<title>The Qt Company Blog: Get Better QML Code Completions with CodeLlama 13B-QML and 7B-QML v3</title>
<pubDate>Thu, 10 Jul 2025 11:40:10 GMT</pubDate>
<link>https://www.qt.io/blog/get-better-qml-code-completions-with-codellama-13b-qml-and-7b-qml-v2.0</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/get-better-qml-code-completions-with-codellama-13b-qml-and-7b-qml-v2.0?hsLang=en" title=""><img alt="Get Better QML Code Completions with CodeLlama 13B-QML and 7B-QML v3" class="hs-featured-image" src="https://www.qt.io/hubfs/CodeCompletionRectangularShadowClip-converted.gif" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
<span>Today, we released updated versions of the fine-tuned CodeLlama 13B-QML and 7B -QML models.</span> <span>The updated versions include the first skills to complete code for Qt Quick enhancements in Qt 6.9 and Qt 6.10.</span>
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fget-better-qml-code-completions-with-codellama-13b-qml-and-7b-qml-v2.0&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/testing-qt-quick-for-android-applications-with-squish</guid>
<title>The Qt Company Blog: Testing Qt Quick for Android applications with Squish</title>
<pubDate>Fri, 04 Jul 2025 11:02:46 GMT</pubDate>
<link>https://www.qt.io/blog/testing-qt-quick-for-android-applications-with-squish</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/testing-qt-quick-for-android-applications-with-squish?hsLang=en" title=""><img alt="Testing Qt Quick for Android applications with Squish" class="hs-featured-image" src="https://www.qt.io/hubfs/AI-Generated%20Media/Images/Automated%20Android%20testing-3.jpeg" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
<span>In previous blog posts (</span><a href="https://www.qt.io/blog/qt-quick-in-android-as-a-view?hsLang=en"><span>here</span></a><span>,</span> <a href="https://www.qt.io/blog/qt-tools-for-android-studio?hsLang=en"><span>here</span></a> <span>and</span> <a href="https://www.qt.io/blog/qt-for-android-a-novel-approach?hsLang=en"><span>here</span></a><span>), we’ve talked about embedding QML components into native Android apps – a concept we call </span><a href="https://doc.qt.io/qt-6/qtquick-for-android.html"><span>Qt Quick for Android</span></a><span>. It provides an easy way to spice up your Android application with Qt or even write the whole UI in QML without leaving the warm, cuddly comfort of Android Studio and the Android build system.</span> <span>In this blog post we'll take a look at how you can leverage Qt Quality Assurance tools to test the apps you create with this technology.</span>
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Ftesting-qt-quick-for-android-applications-with-squish&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/how-to-reach-cra-compliance</guid>
<title>The Qt Company Blog: How to Reach CRA Compliance</title>
<pubDate>Thu, 03 Jul 2025 09:55:01 GMT</pubDate>
<link>https://www.qt.io/blog/how-to-reach-cra-compliance</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/how-to-reach-cra-compliance?hsLang=en" title=""><img alt="How to Reach CRA Compliance" class="hs-featured-image" src="https://www.qt.io/hubfs/CRA%20Compliance%20Interview%20with%20Maurice%20Kalinowski.webp" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p style="font-size: 18px;">
As the deadlines for the <a href="https://www.qt.io/cyber-resilience-act?hsLang=en">EU Cyber Resilience Act (CRA)</a> are fast approaching, reaching CRA compliance is turning cybersecurity an essential theme for many. Already strictly regulated industries, such as medical or automotive, have shown how regulation can foster creating secure solutions. Now with the CRA, a similar level of security is about to be required from a broader number of product manufacturers.
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fhow-to-reach-cra-compliance&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/qt-extension-1.6.0-for-vs-code-released</guid>
<title>The Qt Company Blog: Qt Extension 1.6.0 for VS Code released</title>
<pubDate>Thu, 03 Jul 2025 07:37:04 GMT</pubDate>
<link>https://www.qt.io/blog/qt-extension-1.6.0-for-vs-code-released</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/qt-extension-1.6.0-for-vs-code-released?hsLang=en" title=""><img alt="Screenshot of Visual Studio Code on the extensions page with one of the Qt extensions selected." class="hs-featured-image" src="https://www.qt.io/hubfs/vscodeext-installation.webp" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
<span>We’re happy to announce the official release of <span style="font-weight: normal;">version 1.6.0</span> of the <span style="font-weight: normal;">Qt Extension for Visual Studio Code</span>! After spending some time as pre-release <span style="font-weight: normal;">1.5.0</span> in the Marketplace, it’s now graduated to a full release. Let's have a look at what's new!</span>
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fqt-extension-1.6.0-for-vs-code-released&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/qtgrpc-tips-tricks-sweet-spots</guid>
<title/>
<pubDate>Mon, 30 Jun 2025 09:42:39 GMT</pubDate>
<link>https://www.qt.io/blog/qtgrpc-tips-tricks-sweet-spots</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/qtgrpc-tips-tricks-sweet-spots?hsLang=en" title=""><img alt="QtGrpc - Tips, Tricks & Sweet Spots" class="hs-featured-image" src="https://www.qt.io/hubfs/togrpc_or_not_to.png" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p>
Since Qt 6.8, the <a href="https://doc.qt.io/qt-6/qtgrpc-index.html">Qt GRPC</a> and <a href="https://doc.qt.io/qt-6/qtprotobuf-index.html" style="font-weight: normal;">Qt Protobuf</a> modules have officially moved out of technical preview and are now fully supported parts of the Qt framework. In this blog post, we’ll take a look at what’s changed, where things are headed, and what we’ve learned along the way—from performance benchmarks to hidden gems worth knowing about.
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fqtgrpc-tips-tricks-sweet-spots&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/security-advisory-recently-reported-incomplete-cleanup-issue-in-qts-schannel-handling-can-impact-qt</guid>
<title>The Qt Company Blog: Security advisory: Recently reported incomplete cleanup issue in Qt's Schannel handling can impact Qt</title>
<pubDate>Mon, 30 Jun 2025 09:00:00 GMT</pubDate>
<link>https://www.qt.io/blog/security-advisory-recently-reported-incomplete-cleanup-issue-in-qts-schannel-handling-can-impact-qt</link>
<description><p>
There is a "Incomplete Cleanup" problem in Qt’s Schannel handling when it is used to provide a server handling incoming TLS connections. 
</p>
<p>
This has been assigned the CVE id CVE-2025-6338.
</p><br>
<p>
 
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fsecurity-advisory-recently-reported-incomplete-cleanup-issue-in-qts-schannel-handling-can-impact-qt&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.qt.io/blog/why-cyber-resilience-requires-a-cultural-shift</guid>
<title>The Qt Company Blog: Why Cyber Resilience Requires a Cultural Shift</title>
<pubDate>Thu, 26 Jun 2025 07:04:24 GMT</pubDate>
<link>https://www.qt.io/blog/why-cyber-resilience-requires-a-cultural-shift</link>
<description><div class="hs-featured-image-wrapper">
<a class="hs-featured-image-link" href="https://www.qt.io/blog/why-cyber-resilience-requires-a-cultural-shift?hsLang=en" title=""><img alt="Why Cyber Resilience Requires a Cultural Shift" class="hs-featured-image" src="https://www.qt.io/hubfs/Oyku-Isik-CyberResilience-QtWS2025-tinified.webp" style="width: auto !important; float: left; margin: 0 15px 15px 0;"></a>
</div>
<p style="font-size: 18px;">
The countdown to the European Union’s <a href="https://www.qt.io/cyber-resilience-act?hsLang=en">Cyber Resilience Act (CRA)</a> is underway. The new regulation lays out an extensive set of cybersecurity requirements throughout the lifecycle of products with digital elements (PDEs) sold in the EU market. This brings about a need for a fundamental shift for organizations from cyber security to cyber resilience.
</p><img alt="" height="1" src="https://track.hubspot.com/__ptq.gif?a=149513&k=14&r=https%3A%2F%2Fwww.qt.io%2Fblog%2Fwhy-cyber-resilience-requires-a-cultural-shift&bu=https%253A%252F%252Fwww.qt.io%252Fblog&bvt=rss" width="1"></description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?p=12122</guid>
<title>Qt – basysKom GmbH: Pitfalls in PySide6</title>
<pubDate>Mon, 23 Jun 2025 08:56:31 GMT</pubDate>
<link>https://www.basyskom.de/pitfalls-in-pyside6/</link>
<description><p>
<a href="https://www.basyskom.de/pitfalls-in-pyside6/"><img align="left" alt="Pitfalls in PySide6" height="160" src="https://www.basyskom.de/wp-content/uploads/2025/06/Qtforpython.png" style="margin: 0 20px 20px 0;" width="224"></a>
</p>
<p>
PySide6 is easy to use and powerful but there’s a few pitfalls to look out for. We’ll investigate a performance issue in a large data model, find out that function calls are expensive, and that some of Qt’s C++ APIs had to be adapted to work in a Python environment.
</p>
<p>
<a href="https://www.basyskom.de/pitfalls-in-pyside6/" rel="nofollow">Continue reading Pitfalls in PySide6 at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?p=12064</guid>
<title>Qt – basysKom GmbH: Improved Black Box Testing with Cucumber-CPP</title>
<pubDate>Fri, 13 Jun 2025 09:11:03 GMT</pubDate>
<link>https://www.basyskom.de/improved-black-box-testing-with-cucumber-cpp/</link>
<description><p>
<a href="https://www.basyskom.de/improved-black-box-testing-with-cucumber-cpp/"><img align="left" alt="Improved Black Box Testing with Cucumber-CPP" height="300" src="https://www.basyskom.de/wp-content/uploads/2025/06/cucumber-logo.svg" style="margin: 0 20px 20px 0;" width="300"></a>
</p>
<p>
Learn how to use Cucumber-CPP and Gherkin to implement better black box tests for a C++ library. We developed a case-study based on Qt OPC UA.
</p>
<p>
<a href="https://www.basyskom.de/improved-black-box-testing-with-cucumber-cpp/" rel="nofollow">Continue reading Improved Black Box Testing with Cucumber-CPP at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">tag:www.pythonguis.com,2025-06-11:/blog/pyqt6-pyside6-books-updated-2025/</guid>
<title>Python GUIs - qt: 6th Edition - Create GUI Applications with Python & Qt, Released —</title>
<pubDate>Wed, 11 Jun 2025 08:00:00 GMT</pubDate>
<link>https://www.pythonguis.com/blog/pyqt6-pyside6-books-updated-2025/</link>
<description/>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/modbus-protocol-and-qt-integration-for-embedded-systems</guid>
<title>Scythe Studio Qt Blog: Modbus Protocol and Qt Integration for Embedded Systems</title>
<pubDate>Tue, 10 Jun 2025 13:41:13 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/modbus-protocol-and-qt-integration-for-embedded-systems</link>
<description><p>
Technical managers in the embedded space often face a classic challenge: integrating industrial communication protocols into modern applications. One such […]
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?page_id=11922</guid>
<title>Qt – basysKom GmbH: Interactive Plots with PySide6</title>
<pubDate>Wed, 28 May 2025 09:25:12 GMT</pubDate>
<link>https://www.basyskom.de/interactive-plots-with-pyside6/</link>
<description><p>
<a href="https://www.basyskom.de/interactive-plots-with-pyside6/"><img align="left" alt="Interactive Plots with PySide6" height="150" src="https://www.basyskom.de/wp-content/uploads/2020/07/Qt_logo_space-01-01-300x150.png" style="margin: 0 20px 20px 0;" width="300"></a>
</p>
<p>
Nowadays it is getting more and more popular to write Qt applications in Python using a binding module like PySide6. One reason for this is probably Python's rich data science ecosystem which makes it a breeze to load and visualize complex datasets. In this article we focus (although not exclusively) on the widespread plotting library Matplotlib: We demonstrate how you can embed it in PySide applications and how you can customize the default look and feel to your needs. We round off the article with an outlook into Python plotting libaries beyond Matplotlib and their significance for Qt.
</p>
<p>
<a href="https://www.basyskom.de/interactive-plots-with-pyside6/" rel="nofollow">Continue reading Interactive Plots with PySide6 at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?p=11858</guid>
<title>Qt – basysKom GmbH: Modern TableView in QML: What’s New in Qt 6.8 and Beyond</title>
<pubDate>Thu, 08 May 2025 08:02:20 GMT</pubDate>
<link>https://www.basyskom.de/modern-tableview-in-qml-whats-new-in-qt-6-8-and-beyond/</link>
<description><p>
<a href="https://www.basyskom.de/modern-tableview-in-qml-whats-new-in-qt-6-8-and-beyond/"><img align="left" alt="Modern TableView in QML: What’s New in Qt 6.8 and Beyond" height="150" src="https://www.basyskom.de/wp-content/uploads/2020/07/Qt_logo_space-01-01-300x150.png" style="margin: 0 20px 20px 0;" width="300"></a>
</p>
<p>
Over the years, the capabilities of QtQuick's TableView have evolved dramatically-from early custom implementations to well supported feature in Qt 6.8 and newer. In this article we explore the progression of QtQuick/QML's TableView, outline the limitations of early versions, and highlight newer features such as custom selection modes, header synchronization, and lightweight editing delegates. Check it out.
</p>
<p>
<a href="https://www.basyskom.de/modern-tableview-in-qml-whats-new-in-qt-6-8-and-beyond/" rel="nofollow">Continue reading Modern TableView in QML: What’s New in Qt 6.8 and Beyond at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/how-to-ensure-high-cyber-security-in-qt-apps</guid>
<title>Scythe Studio Qt Blog: How to Ensure High Cyber-security in Qt apps?</title>
<pubDate>Mon, 05 May 2025 15:33:45 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/how-to-ensure-high-cyber-security-in-qt-apps</link>
<description><p>
Qt is a powerful tool and framework for building modern cross-platform applications. From embedded devices to desktop software, Qt enables […]
</p></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/what-is-qt-framework-and-how-to-create-gui-with-it</guid>
<title>Scythe Studio Qt Blog: What is Qt framework and how to use it for GUI development?</title>
<pubDate>Fri, 25 Apr 2025 13:15:23 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/what-is-qt-framework-and-how-to-create-gui-with-it</link>
<description><p>
We cover Qt-related issues on our blog, but only recently realized that many of our readers may not even know […]
</p></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/how-to-use-qt-and-qml-with-visual-studio-code-and-wsl</guid>
<title>Scythe Studio Qt Blog: How to use Qt and QML with Visual Studio Code and WSL?</title>
<pubDate>Fri, 18 Apr 2025 15:04:30 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/how-to-use-qt-and-qml-with-visual-studio-code-and-wsl</link>
<description><p>
Qt and QML provide a powerful framework for developing modern, cross-platform applications with a sleek and responsive UI. While Qt […]
</p></description>
</item>
<item>
<guid isPermaLink="false">tag:www.pythonguis.com,2025-04-03:/examples/pyside6-desktop-sticky-notes/</guid>
<title>Python GUIs - qt: Build a Desktop Sticky Notes Application with PySide6 & SQLAlchemy — Create moveable desktop reminders with Python</title>
<pubDate>Thu, 03 Apr 2025 12:01:00 GMT</pubDate>
<link>https://www.pythonguis.com/examples/pyside6-desktop-sticky-notes/</link>
<description><p>
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.
</p>
<p>
The application is written in PySide6 and the notes are implemented as decoration-less windows, that is windows without any 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.
</p>
<p class="admonition admonition-note">
This is quite a complicated example, but we'll be walking through it slowly step by step. The <a href="https://www.pythonguis.com/d/sticky-notes-pyside6.zip">full source code</a> is available, with working examples at each stage of the development if you get stuck.
</p>
<div class="toc">
<span class="toctitle">Table of Contents</span>
<ul>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#setting-up-the-working-environment">Setting Up the Working Environment</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#building-the-notes-gui">Building the Notes GUI</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#styling-our-notes">Styling our notes</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#remove-window-decorations">Remove Window Decorations</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#movable-notes">Movable notes</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#multiple-notes">Multiple notes</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#adding-notes-to-the-tray">Adding Notes to the Tray</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#adding-a-menu">Adding a Menu</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#setting-up-the-notes-database">Setting up the Notes database</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#integrating-the-data-model-into-our-ui">Integrating the Data Model into our UI</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#starting-up">Starting up</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#conclusion">Conclusion</a>
</li>
</ul>
</div>
<h2 id="setting-up-the-working-environment">
Setting Up the Working Environment
</h2>
<p>
In this tutorial, we'll use the PySide6 library to build the note app's GUI. We'll assume that you have a basic understanding of PySide6 apps.
</p>
<p class="admonition admonition-info">
To learn the basics of PySide6, check out the complete <a href="https://www.pythonguis.com/pyside6-tutorial/">PySide6 Tutorials</a> or my book <a href="https://www.pythonguis.com/pyside6-book/">Create GUI Applications with Python & PySide6</a>
</p>
<p>
To store the notes between sessions, we will use <a href="https://www.sqlalchemy.org/"><code>SQLAlchemy</code></a> 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 & have working examples you can copy.
</p>
<p>
With that in mind, let's create a <a href="https://www.pythonguis.com/tutorials/python-virtual-environments/">virtual environment</a> and install our requirements into it. To do this, you can run the following commands:
</p>
<div class="tabbed-area multicode">
<ul class="tabs">
<li class="tab-link current">macOS
</li>
<li class="tab-link">Windows
</li>
<li class="tab-link">Linux
</li>
</ul>
<div class="tab-content current code-block-outer" id="bff18f077dd14488aeaf24276846a792">
<div class="code-block">
<span class="code-block-language code-block-sh">sh</span>
<pre><code class="sh">$ mkdir notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="d7a8ec492304480c902622dcfeefd5d3">
<div class="code-block">
<span class="code-block-language code-block-cmd">cmd</span>
<pre><code class="cmd">> mkdir notes/
> cd notes
> python -m venv venv
> venv\Scripts\activate.bat
(venv)> pip install pyside6 sqlalchemy
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="c56b1323795a4d5ea1e7c8022a849378">
<div class="code-block">
<span class="code-block-language code-block-sh">sh</span>
<pre><code class="sh">$ mkdir notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
</code></pre>
</div>
</div>
</div>
<p>
With these commands, you create a <code>notes/</code> folder for storing your project. Inside that folder, you create a new virtual environment, activate it, and install <a href="https://pypi.org/project/PySide6/">PySide6</a> and <a href="https://pypi.org/project/SQLAlchemy/">SQLAlchemy</a> from PyPi.
</p>
<p class="admonition admonition-info">
For platform-specific troublshooting, check the <a href="https://www.pythonguis.com/tutorials/python-virtual-environments/">Working With Python Virtual Environments</a> tutorial.
</p>
<h2 id="building-the-notes-gui">
Building the Notes GUI
</h2>
<p>
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.
</p>
<p>
The UI for our desktop sticky notes will be <em>a bit strange</em> 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.
</p>
<p>
We'll start by defining a single note, and then deal with these other issues later. Create a new file named <code>notes.py</code> and add the following outline application to it.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
In this code we first create a Qt <code>QApplication</code> instance. This needs to be done before creating our widgets. Next we define a simple custom window class <code>NoteWindow</code> by subclassing <code>QWidget</code>. We add a vertical layout to the window, and enter a single <code>QTextEdit</code> widget. We then create an instance of this window object as <code>note</code> and show it by calling <code>.show()</code>. This puts the window on the desktop. Finally, we start up our application by calling <code>app.exec()</code>.
</p>
<p>
You can run this file like any other Pythons script.
</p>
<div class="code-block">
<span class="code-block-language code-block-sh">sh</span>
<pre><code class="sh">python notes.py
</code></pre>
</div>
<p>
When the applicaton launches you'll see the following on your desktop.
</p>
<p>
<img alt="Our editable "note" on the desktop" height="533" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-window.png" width="730"> <em>Simple "notes" window on the desktop</em>
</p>
<p>
If you click in the text editor in the middle, you can enter some text.
</p>
<p>
<em>Technically</em> this is a note, but we can do better.
</p>
<h2 id="styling-our-notes">
Styling our notes
</h2>
<p>
Our note doesn't look anything like a sticky note yet. Let's change that by applying some simple styles to it.
</p>
<p>
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.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
In the code above we have set a background color of hex <code>#ffff99</code> for our note window, and set the text color to hex <code>#62622f</code> a sort of muddy brown. The <code>border:0</code> 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.
</p>
<p>
If you run the code now you'll see this, much more notely note.
</p>
<p>
<img alt="The note with our QSS styles applied" height="541" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-styled.png" width="791"> <em>The note with the QSS styling applied</em>
</p>
<h2 id="remove-window-decorations">
Remove Window Decorations
</h2>
<p>
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 <em>window flags</em>. 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.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
To set window flags, we need to import the Qt flags from the <code>QtCore</code> namespace. Then you can set flags on the window using <code>.setWindowFlags()</code>. Note that since windows have flags already set, and we don't want to replace them all, we get the current flags with <code>.windowFlags()</code> and then add the additional flags to it using boolean OR <code>|</code>. We've added two flags here -- <code>Qt.WindowType.FramelessWindowHint</code> which removes the window decorations, and <code>Qt.WindowType.WindowStaysOnTopHint</code> which keeps the windows on top.
</p>
<p>
Run this and you'll see a window with the decorations removed.
</p>
<p>
<img alt="Note with the window decorations removed" height="462" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-nodecoration.png" width="781"> <em>Note with the window decorations removed</em>
</p>
<p class="admonition admonition-tip">
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).
</p>
<p>
While you can close the window, it'd be nicer if there was a button to do it. We can add a custom button using <code>QPushButton</code> and hook this up to the window's <code>.close()</code> method to re-implement this.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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("×")
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()
</code></pre>
</div>
<p>
Our close button is created using <code>QPushButton</code> 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 <em>thing</em> that performs an action. Finally, we connect the <code>.clicked</code> signal of the button to the window's close method <code>self.close</code>. The button will close the window.
</p>
<p class="admonition admonition-note">
Later we'll use this button to delete notes.
</p>
<p>
To add the close button to the top right of the window, we create a horizontal layout with <code>QHBoxLayout</code>. 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.
</p>
<p>
Run the code now and our note is complete!
</p>
<p>
<img alt="The complete note UI with close button" height="503" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-complete.png" width="888"> <em>The complete note UI with close button</em>
</p>
<h2 id="movable-notes">
Movable notes
</h2>
<p>
The note <em>looks</em> 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.
</p>
<p>
This is fairly straightforward to achieve in PySide because Qt makes the raw mouse events available on all widgets. To implement moving, we can intercept these events and update the position of the window based on the distance the mouse has moved.
</p>
<p>
To implement this, add the following two methods to the bottom of the <code>NoteWindow</code> class.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
Clicking and dragging a window involves three actions: the mouse press, the mouse move and the mouse release. We have defined two methods here <code>mousePressEvent</code> and <code>mouseMoveEvent</code>. In <code>mousePressEvent</code> 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.
</p>
<p>
The <code>mouseMoveEvent</code> is called on every <em>subsequent</em> move while the mouse button <em>remains</em> pressed. On each move we take the new mouse position and subtract the previous position to get the <em>delta</em> -- that is, the change in mouse position from the initial press to the current event. Then we <em>move</em> the window by that amount, storing the new previous position after the move.
</p>
<p>
The effect of this is that ever time the <code>mouseMoveEvent</code> 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.
</p>
<h2 id="multiple-notes">
Multiple notes
</h2>
<p>
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.
</p>
<p>
Currently we're creating the <code>NoteWindow</code> when the application starts up, just before we call <code>app.exec()</code>. 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 <code>NoteWindow</code> objects so they aren't automatically deleted (and the window closed) when the function or method exits.
</p>
<p class="admonition admonition-note">
Python automatically deletes objects when they fall out of scope if there aren't any remaining references to them.
</p>
<p>
We can solve this by storing the <code>NoteWindow</code> 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.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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("×")
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()
</code></pre>
</div>
<p>
In this code we've added our <code>active_notewindows</code> dictionary. This holds references to our <code>NoteWindow</code> objects, keyed by <code>id()</code>. 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 <code>__init__</code> method.
</p>
<p>
Next we've implemented a <code>create_notewindow()</code> function which creates an instance of <code>NoteWindow</code> and shows it, just as before. Nothing else is needed, since the note itself handles storing it's references on creation.
</p>
<p>
Finally, we've added multiple calls to <code>create_notewindow()</code> to create multiple notes.
</p>
<p>
<img alt="Multiple notes on the desktop" height="881" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-multiple.png" width="1282"> <em>Multiple notes on the desktop</em>
</p>
<h2 id="adding-notes-to-the-tray">
Adding Notes to the Tray
</h2>
<p>
We can now create multiple notes programatically, 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 al the notes had been closed or hidden. Instead, we'll create a 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.
</p>
<p>
There's quite a lot to this, so we'll step through it in stages.
</p>
<p>
Update the code, adding the imports shown at the top, and the rest following the definition of <code>create_notewindow</code>.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
In this code we've first create an <code>QIcon</code> object passing in the filename of the icon to use. I'm using a sticky note icon from the <a href="http://p.yusukekamiyamane.com/">Fugue icon set</a> by designer Yusuke Kamiyamane. Feel free to use any icon you prefer.
</p>
<p class="admonition admonition-note">
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 <em>or</em> provide the path.
</p>
<p>
The system tray icon is managed through a <code>QSystemTrayIcon</code> object. We set our icon on this, and set the tray icon to visible (so it is not automatically hidden by Windows).
</p>
<p>
<code>QSystemTrayIcon</code> has a signal <code>activated</code> 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 <code>reason</code> (the reason for the activation) and then checks this against <code>QSystemTrayIcon.ActivationReason.Trigger</code>. This is the reason reported when a left click is used.
</p>
<p>
If the left mouse button has been clicked, we call <code>create_notewindow()</code> to create a new instance of a note.
</p>
<p>
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.
</p>
<p>
<img alt="The sticky note icon in the tray" height="146" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-tray-windows.png" width="322"> <em>The sticky note icon in the tray</em>
</p>
<p class="admonition admonition-note">
This is happening because by default Qt will close an application once all it's 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 <em>unstoppable</em>.
</p>
<h2 id="adding-a-menu">
Adding a Menu
</h2>
<p>
To allow the notes application to be closed from the tray, we need a menu. Sytem tray menus are normally accessible through right-clicking on the icon. To implement that we can set a <code>QMenu</code> as a <em>context</em> menu on the <code>QSystemTrayIcon</code>. The actions in menus in Qt are defined using <code>QAction</code>.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
We create the menu using <code>QMenu</code>. Actions are created using <code>QAction</code> passing in the label as a string. This is the text that will be shown for the menu item. The <code>.triggered</code> 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 <code>create_notewindow</code> function. We've also added an action to quit the application. This is connected to the built-in <code>.quit</code> slot on our <code>QApplication</code> instance.
</p>
<p>
The menu is set on the tray using <code>.setContextMenu()</code>. In Qt context menus are automatically shown when the user right clicks on the tray.
</p>
<p>
Finally, we have also disabled the behavior of closing the application when the last window is closed using <code>app.setQuitOnLastWindowClosed(False)</code>. 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".
</p>
<p class="admonition admonition-tip">
If you find this annoying while developing, just comment this line out again.
</p>
<p>
We've had a lot of changes so far, so here is the current complete code.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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("×")
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()
</code></pre>
</div>
<p>
If you run this now you will be able to right click the note in the tray to show the menu.
</p>
<p>
<img alt="The sticky note icon in the tray showing its context menu" height="143" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-tray-active-windows.png" width="333"> <em>The sticky note icon in the tray showing its context menu</em>
</p>
<p>
Test the <em>Add note</em> and <em>Quit</em> functionality to make sure they're working.
</p>
<p>
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 & 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.
</p>
<h2 id="setting-up-the-notes-database">
Setting up the Notes database
</h2>
<p>
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.
</p>
<p>
We'll define our database in a separate file, to keep the UI file manageable. So start by creating a new file named <code>database.py</code> in your project folder.
</p>
<p>
In that file add the imports for SQLAlchemy, and instantiate the <code>Base</code> class for our models.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
</code></pre>
</div>
<p>
Next in the same <code>database.py</code> file, define our note database model. This inherits from the <code>Base</code> class we've just created, by calling <code>declarative_base()</code>
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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)
</code></pre>
</div>
<p>
Each note object has 4 properties:
</p>
<ul>
<li>
<strong>id</strong> the unique ID for the given note, used to delete from the database
</li>
<li>
<strong>text</strong> the text content of the note
</li>
<li>
<strong>x</strong> the <em>x</em> position on the screen
</li>
<li>
<strong>y</strong> the <em>y</em> position on the screen
</li>
</ul>
<p>
Next we need to create the <em>engine</em> -- in this case, this is our SQLite file, which we're calling <code>notes.db</code>.We can then create the tables (if they don't already exist). Since our <code>Note</code> class registers itself with the <code>Base</code> we can do that by calling <code>create_all</code> on the <code>Base</code> class.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">engine = create_engine("sqlite:///notes.db")
Base.metadata.create_all(engine)
</code></pre>
</div>
<p>
Save the <code>database.py</code> file and run it
</p>
<div class="code-block">
<span class="code-block-language code-block-sh">sh</span>
<pre><code class="sh">python database.py
</code></pre>
</div>
<p>
After it is complete, if you look in the folder you should see the <code>notes.db</code>. This file contains the table structure for the <code>Note</code> model we defined above.
</p>
<p>
Finally, we need a <em>session</em> 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.
</p>
<p>
Add the following to <code>database.py</code>
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python"># Create a session to handle updates.
Session = sessionmaker(bind=engine)
session = Session()
</code></pre>
</div>
<p>
The final complete code for our database interface is shown below
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
Now that our data model is defined, and our database created, we can go ahead and interface our <code>Notes</code> 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.
</p>
<h2 id="integrating-the-data-model-into-our-ui">
Integrating the Data Model into our UI
</h2>
<p>
Our data model holds the text content and x & y positions of the notes. To keep the active notes and model in sync we need a few things.
</p>
<ol>
<li>Each <code>NoteWindow</code> must have it's own associated instance of the <code>Note</code> object.
</li>
<li>New <code>Note</code> objects should be created when creating a new <code>NoteWindow</code>.
</li>
<li>The <code>NoteWindow</code> should sync it's initial state to a <code>Note</code> if provided.
</li>
<li>Moving & editing a <code>NoteWindow</code> should update the data in the <code>Note</code>.
</li>
<li>Changes to <code>Note</code> should be synced to the database.
</li>
</ol>
<p>
We can tackle these one by one.
</p>
<p>
First let's setup our <code>NoteWindow</code> to accept, and store a reference to <code>Note</code> objects if provided, or create a new one if not.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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
</code></pre>
</div>
<p>
In this code we've imported the <code>Note</code> object from our <code>database.py</code> file. In the <code>__init__</code> of our <code>NoteWindow</code> we've added an optional parameter to receive a <code>Note</code> object. If this is <code>None</code> (or nothing provided) a new <code>Note</code> will be created instead. The passed, or created note, is then stored on the <code>NoteWindow</code> so we can use it later.
</p>
<p>
This <code>Note</code> object is still not being loaded, updated, or persisted to the database. So let's implement that next. We add two methods, <code>load()</code> and <code>save()</code> to our <code>NoteWindow</code> to handle the loading and saving of data.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
The <code>load()</code> method takes the <code>x</code> and <code>y</code> position from the <code>Note</code> object stored in <code>self.note</code> and updates the <code>NoteWindow</code> position and content to match. The <code>save()</code> method takes the <code>NoteWindow</code> position and content and sets that onto the <code>Note</code> object. It then adds the note to the current database session and commits the changes
</p>
<p class="admonition admonition-note">
Each commit starts a new session. Adding the <code>Note</code> to the session is indicating that we want it's changes persisted.
</p>
<p>
The <code>delete()</code> method handles deletion of the current note. This involves 3 things:
</p>
<ol>
<li>passing the <code>Note</code> object to <code>session.delete</code> to remove it from the database,
</li>
<li>deleting the reference to our window from the <code>active_notewindows</code> (so the object will be tidied up)
</li>
<li>calling <code>.close()</code> to hide the window immediately.
</li>
</ol>
<p class="admonition admonition-note">
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 <code>.close()</code> to make it immediate.
</p>
<p>
We need to modify the <code>close_btn.clicked</code> signal to point to our <code>delete</code> method.
</p>
<p>
Next we've added a <code>load()</code> call to the <code>__init__</code> when a <code>Note</code> object is passed. We also call <code>.save()</code> for newly created notes to persist them immediately, so our delete handler will work before editing.
</p>
<p>
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 <em>could</em> 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 <code>mouseReleased</code> method.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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.
</code></pre>
</div>
<p>
That's all there is to it: when the mouse button is released, we save the current content and position by calling <code>.save()</code>.
</p>
<p class="admonition admonition-note">
You might be wondering why we don't just save the position at this point? Usually it's better to implement a single load & save (persist/restore) handler that can be called for all situations. It avoids needing implementations for each case.
</p>
<p>
There have been a lot of partial code changes in this section, so here is the complete current code.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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("×")
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()
</code></pre>
</div>
<p>
If you run the application at this point it will be persisting data to the database as you edit it.
</p>
<p class="admonition admonition-tip">
If you want to look at the contents of the SQLite database I can recommend <a href="https://sqlitebrowser.org/">DB Browser for SQLite</a>. It's open source & free.
</p>
<p>
<img alt="The note data persisted to the SQLite database" height="988" src="https://www.pythonguis.com/static/examples/qt/desktop-notes/notes-sqlite-browser.png" width="1668"> <em>The note data persisted to the SQLite database</em>
</p>
<h2 id="starting-up">
Starting up
</h2>
<p>
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.
</p>
<p>
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 <code>Note</code> objects and then iterate through this, creating new <code>NoteWindow</code> instances (using our <code>create_notewindow</code> function).
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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()
</code></pre>
</div>
<p>
First we've modified the <code>create_notewindow</code> function to accept an (optional) <code>Note</code> object which is passed through to the created <code>NoteWindow</code>.
</p>
<p>
Using the session we query <code>session.query(Note).all()</code> to get all the <code>Note</code> objects. If there any, we iterate them creating them. If not, we create a single note with no associated <code>Note</code> object (this will be created inside the <code>NoteWindow</code>).
</p>
<p>
That's it! The full final code is shown below:
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">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("×")
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()
</code></pre>
</div>
<p>
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.
</p>
<h2 id="conclusion">
Conclusion
</h2>
<p>
That's it! We have a fully functional desktop sticky note application, which you can use 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 window flags to control the appearance of notes on the desktop. We've also seen how to create a system tray application, adding context menus and default behaviours (via a left mouse click). Finally, we've created a simple data model using SQLAlchemy and hooked that into our UI to persist the UI state between runs of the applications.
</p>
<p>
Try and extend this example further, for example:
</p>
<ul>
<li>Add multicolor notes, using a Python <code>list</code> of hex colors and <code>random.choice</code> to select a new color each time a note is created. Can you persist this in the database too?
</li>
<li>Add an option in the tray to show/hide all notes. Remember we have all the <code>NoteWindow</code> objects in <code>active_notewindows</code>. You can show and hide windows in Qt using <code>.show()</code> and <code>.hide()</code>.
</li>
</ul>
<p>
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!
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.kdab.com/modelview-drag-and-drop-in-qt-part-3/</guid>
<title>KDAB on Qt: Model/View Drag and Drop in Qt - Part 3</title>
<pubDate>Thu, 03 Apr 2025 08:43:00 GMT</pubDate>
<link>https://www.kdab.com/modelview-drag-and-drop-in-qt-part-3/</link>
<description><h1>
Model/View Drag and Drop in Qt - Part 3
</h1>
<div class="rich-text">
<p>
In this third blog post of the Model/View Drag and Drop series (<a href="https://www.kdab.com/modelview-drag-and-drop-part-1/">part 1</a> and <a href="https://www.kdab.com/modelview-drag-and-drop-in-qt-part-2/">part 2</a>), the idea is to implement dropping onto items, rather than in between items. QListWidget and QTableWidget have out of the box support for replacing the value of existing items when doing that, but there aren't many use cases for that. What is much more common is to associate a custom semantic to such a drop. For instance, the examples detailed below show email folders and their contents, and dropping an email onto another folder will move (or copy) the email into that folder.
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part3-treeview-step1" class="Blog_Drag&Drop_Qt_part3-treeview-step1" id="Blog_Drag&Drop_Qt_part3-treeview-step1" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part3-treeview-step1.original.png" name="Blog_Drag&Drop_Qt_part3-treeview-step1">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Step 1
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
Initial state, the email is in the inbox
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part3-treeview-step2" class="Blog_Drag&Drop_Qt_part3-treeview-step2" id="Blog_Drag&Drop_Qt_part3-treeview-step2" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part3-treeview-step2.original.png" name="Blog_Drag&Drop_Qt_part3-treeview-step2">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Step 2
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
Dragging the email onto the Customers folder
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part3-treeview-step3" class="Blog_Drag&Drop_Qt_part3-treeview-step3" id="Blog_Drag&Drop_Qt_part3-treeview-step3" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part3-treeview-step3.original.png" name="Blog_Drag&Drop_Qt_part3-treeview-step3">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Step 3
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
Dropping the email
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part3-treeview-step4" class="Blog_Drag&Drop_Qt_part3-treeview-step4" id="Blog_Drag&Drop_Qt_part3-treeview-step4" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part3-treeview-step4.original.png" name="Blog_Drag&Drop_Qt_part3-treeview-step4">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Step 4
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
The email is now in the customers folder
</p>
<h2>
With Model/View separation
</h2>
<p>
Example code can be found <a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/model-view/drop-onto-items-with-model-view.cpp" rel="noopener noreferrer" target="_blank">here</a> for flat models and <a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/treemodel/drop-onto-items-with-treemodel.cpp" rel="noopener noreferrer" target="_blank">here</a> for tree models.
</p>
<h3>
Setting up the view on the drag side
</h3>
<p>
☑ Call <code>view->setDragDropMode(QAbstractItemView::DragOnly)</code><br>
unless of course the same view should also support drops. In our example, only emails can be dragged, and only folders allow drops, so the drag and drop sides are distinct.
</p>
<p>
☑ Call <code>view->setDragDropOverwriteMode(...)</code><br>
<code>true</code> if moving should clear cells, <code>false</code> if moving should remove rows.<br>
Note that the default is <code>true</code> for <code>QTableView</code> and <code>false</code> for <code>QListView</code> and <code>QTreeView</code>. In our example, we want to remove emails that have been moved elsewhere, so <code>false</code> is correct.
</p>
<p>
☑ Call <code>view->setDefaultDropAction(Qt::MoveAction)</code> so that the drag defaults to a move and not a copy, adjust as needed
</p>
<h3>
Setting up the model on the drag side
</h3>
<p>
To implement dragging items out of a model, you need to implement the following -- this is very similar to the section of the same name in the previous blog post, obviously:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">class EmailsModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return {};
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}
// the default is "copy only", change it
Qt::DropActions supportedDragActions() const override { return Qt::MoveAction | Qt::CopyAction; }
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool removeRows(int position, int rows, const QModelIndex &parent) override;</code></pre>
</div>
<div class="rich-text">
<p>
☑ Reimplement <code>flags()</code> to add <code>Qt::ItemIsDragEnabled</code> in the case of a valid index
</p>
<p>
☑ Reimplement <code>supportedDragActions()</code> to return <code>Qt::MoveAction | Qt::CopyAction</code> or whichever you want to support (the default is CopyAction only).
</p>
<p>
☑ Reimplement <code>mimeData()</code> to serialize the complete data for the dragged items. If the views are always in the same process, you can get away with serializing only node pointers (if you have that) and application PID (to refuse dropping onto another process). See the previous part of this blog series for more details.
</p>
<p>
☑ Reimplement <code>removeRows()</code>, it will be called after a successful drop with <code>MoveAction</code>. An example implementation looks like this:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">bool EmailsModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(parent, position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
m_emailFolder->emails.removeAt(position);
}
endRemoveRows();
return true;
}</code></pre>
</div>
<div class="rich-text">
<h3>
Setting up the view on the drop side
</h3>
<p>
☑ Call <code>view->setDragDropMode(QAbstractItemView::DropOnly)</code> unless of course it supports dragging too. In our example, we can drop onto email folders but we cannot reorganize the folders, so <code>DropOnly</code> is correct.
</p>
<h3>
Setting up the model on the drop side
</h3>
<p>
To implement dropping items into a model's existing items, you need to do the following:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">class FoldersModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
CHECK_flags(index);
if (!index.isValid())
return {}; // do not allow dropping between items
if (index.column() > 0)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // don't drop on other columns
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
}
// the default is "copy only", change it
Qt::DropActions supportedDropActions() const override { return Qt::MoveAction | Qt::CopyAction; }
QStringList mimeTypes() const override { return {QString::fromLatin1(s_emailsMimeType)}; }
bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
};</code></pre>
</div>
<div class="rich-text">
<p>
☑ Reimplement <code>flags()</code><br>
For a valid index (and only in that case), add <code>Qt::ItemIsDropEnabled</code>. As you can see, you can also restrict drops to column 0, which can be more sensible when using <code>QTreeView</code> (the user should drop onto the folder name, not onto the folder size).
</p>
<p>
☑ Reimplement <code>supportedDropActions()</code> to return <code>Qt::MoveAction | Qt::CopyAction</code> or whichever you want to support (the default is CopyAction only).
</p>
<p>
☑ Reimplement <code>mimeTypes()</code> - the list should include the MIME type used by the drag model.
</p>
<p>
☑ Reimplement <code>dropMimeData()</code><br>
to deserialize the data and handle the drop.<br>
This could mean calling <code>setData()</code> to replace item contents, or anything else that should happen on a drop: in the email example, this is where we copy or move the email into the destination folder. Once you're done, return true, so that the drag side then deletes the dragged rows by calling <code>removeRows()</code> on its model.
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">bool FoldersModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
~~~ // safety checks, see full example code
EmailFolder *destFolder = folderForIndex(parent);
const QByteArray encodedData = mimeData->data(s_emailsMimeType);
QDataStream stream(encodedData);
~~~ // code to detect and reject dropping onto the folder currently holding those emails
while (!stream.atEnd()) {
QString email;
stream >> email;
destFolder->emails.append(email);
}
emit dataChanged(parent, parent); // update count
return true; // let the view handle deletion on the source side by calling removeRows there
}</code></pre>
</div>
<div class="rich-text">
<h2>
Using item widgets
</h2>
<p>
Example code:
</p>
<ul>
<li>
<a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/qlistwidget/drop-onto-qlistwidgetitems.cpp" rel="noopener noreferrer" target="_blank">QListWidget</a>
</li>
<li>
<a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/qtablewidget/drop-onto-qtablewidgetitems.cpp" rel="noopener noreferrer" target="_blank">QTableWidget</a>
</li>
<li>
<a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/qtreewidget/drop-onto-qtreewidgetitems.cpp" rel="noopener noreferrer" target="_blank">QTreeWidget</a>
</li>
</ul>
<h3>
On the "drag" side
</h3>
<p>
☑ Call <code>widget->setDragDropMode(QAbstractItemView::DragOnly)</code> or <code>DragDrop</code> if it should support both
</p>
<p>
☑ Call <code>widget->setDefaultDropAction(Qt::MoveAction)</code> so that the drag defaults to a move and not a copy, adjust as needed
</p>
<p>
☑ Reimplement <code>Widget::mimeData()</code> to serialize the complete data for the dragged items. If the views are always in the same process, you can get away with serializing only item pointers and application PID (to refuse dropping onto another process). In our email folders example we also serialize the pointer to the source folder (where the emails come from) so that we can detect dropping onto the same folder (which should do nothing).
</p>
<p>
To serialize pointers in QDataStream, cast them to quintptr, see the <a href="https://github.com/KDABLabs/blogs-qt/blob/main/ItemViews-DragAndDrop/part3-dropping-onto-items/qlistwidget/drop-onto-qlistwidgetitems.cpp#L58" rel="noopener noreferrer" target="_blank">example code</a> for details.
</p>
<h3>
On the "drop" side
</h3>
<p>
☑ Call <code>widget->setDragDropMode(QAbstractItemView::DropOnly)</code> or <code>DragDrop</code> if it should support both
</p>
<p>
☑ Call <code>widget->setDragDropOverwriteMode(true)</code> for a minor improvement: no forbidden cursor when moving the drag between folders. Instead Qt only computes drop positions which are onto items, as we want here.
</p>
<p>
☑ Reimplement <code>Widget::mimeTypes()</code> and return the same name as the one used on the drag side's <code>mimeData</code>
</p>
<p>
☑ Reimplement <code>Widget::dropMimeData()</code> (note that the signature is different between <code>QListWidget</code>, <code>QTableWidget</code> and <code>QTreeWidget</code>) This is where you deserialize the data and handle the drop. In the email example, this is where we copy or move the email into the destination folder.
</p>
<p>
Make sure to do all of the following:
</p>
<ul>
<li>any necessary behind the scenes work (in our case, moving the actual email)
</li>
<li>updating the UI (creating or deleting items as needed)
</li>
</ul>
<p>
This is a case where proper model/view separation is actually much simpler.
</p>
<h2>
Improvements to Qt
</h2>
<p>
While writing and testing these code examples, I improved the following things in Qt, in addition to those listed in the previous blog posts:
</p>
<ul>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-2553" rel="noopener noreferrer" target="_blank">QTBUG-2553</a> QTreeView with setAutoExpandDelay() collapses items while dragging over it, fixed in Qt 6.8.1
</li>
</ul>
<h2>
Conclusion
</h2>
<p>
I hope you enjoyed this blog post series and learned a few things.
</p>
</div>
<p>
The post <a href="https://www.kdab.com/modelview-drag-and-drop-in-qt-part-3/">Model/View Drag and Drop in Qt - Part 3</a> appeared first on <a href="https://www.kdab.com">KDAB</a>.
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.ics.com/4963 at https://www.ics.com</guid>
<title>ICS Insights: Qt and QML: Still on Qt 5? Time is Running Out!</title>
<pubDate>Fri, 28 Mar 2025 15:58:37 GMT</pubDate>
<link>https://www.ics.com/blog/still-qt-5-time-running-out</link>
<description><p>
Standard support for Qt 5 will end in May. Time is running out to port your code!
</p></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/qt-gui-on-stm32-building-efficient-embedded-applications</guid>
<title>Scythe Studio Qt Blog: Qt GUI on STM32: Building Efficient Embedded Applications</title>
<pubDate>Thu, 13 Mar 2025 12:19:25 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/qt-gui-on-stm32-building-efficient-embedded-applications</link>
<description><p>
Products from the STM32 family have been a popular target for embedded Qt applications for quite a long time. One […]
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.kdab.com/modelview-drag-and-drop-in-qt-part-2/</guid>
<title>KDAB on Qt: Model/View Drag and Drop in Qt - Part 2</title>
<pubDate>Mon, 10 Mar 2025 08:19:00 GMT</pubDate>
<link>https://www.kdab.com/modelview-drag-and-drop-in-qt-part-2/</link>
<description><h1>
Model/View Drag and Drop in Qt - Part 2
</h1>
<div class="rich-text">
<p>
In the <a href="https://www.kdab.com/modelview-drag-and-drop-part-1/">previous blog</a>, you learned all about moving items within a single view, to reorder them.
</p>
<p>
In part 2, we are still talking about moving items, and still about inserting them between existing items (never overwriting items) but this time the user can move items from one view to another. A typical use case is a list of available items on the left, and a list of selected items on the right (one concrete example would be to let the user customize which buttons should appear in a toolbar). This also often includes reordering items in the right-side list, the good news being that this comes for free (no extra code needed).
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part2-step1" class="Blog_Drag&Drop_Qt_part2-step1" id="Blog_Drag&Drop_Qt_part2-step1" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part2-step1.original.png" name="Blog_Drag&Drop_Qt_part2-step1">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row between treeviews, step 1
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part2-step2" class="Blog_Drag&Drop_Qt_part2-step2" id="Blog_Drag&Drop_Qt_part2-step2" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part2-step2.original.png" name="Blog_Drag&Drop_Qt_part2-step2">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row between treeviews, step 2
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="Blog_Drag&Drop_Qt_part2-step3" class="Blog_Drag&Drop_Qt_part2-step3" id="Blog_Drag&Drop_Qt_part2-step3" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/Blog_DragDrop_Qt_part2-step3.original.png" name="Blog_Drag&Drop_Qt_part2-step3">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row between treeviews, step 3
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<h2>
With Model/View separation
</h2>
<p>
<a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part2-moving-items-between-views/model-view" rel="noopener noreferrer" target="_blank">Example code</a> for flat models and <a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part2-moving-items-between-views/treemodel" rel="noopener noreferrer" target="_blank">example code</a> for tree models.
</p>
<h3>
Setting up the view on the drag side
</h3>
<p>
To allow dragging items out of the view, make sure to do the following:
</p>
<p>
☑ Call <code>view->setDragDropMode(QAbstractItemView::DragOnly)</code> (or <code>DragDrop</code> if it should support both).
</p>
<p>
☑ Call <code>view->setDragDropOverwriteMode(false)</code> so that <code>QTableView</code> calls removeRows when moving rows, rather than just clearing their cells
</p>
<p>
☑ Call <code>view->setDefaultDropAction(Qt::MoveAction)</code> so it's a move and not a copy
</p>
<h3>
Setting up the model on the drag side
</h3>
<p>
To implement dragging items out of a model, you need to implement the following:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">class CountryModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return {}; // depending on whether you want drops as well (next section)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}
// the default is "return supportedDropActions()", let's be explicit
Qt::DropActions supportedDragActions() const override { return Qt::MoveAction; }
QMimeData *mimeData(const QModelIndexList &indexes) const override; // see below
bool removeRows(int position, int rows, const QModelIndex &parent) override; // see below
};</code></pre>
</div>
<div class="rich-text">
<p>
More precisely, the check-list is the following:
</p>
<p>
☑ Reimplement <code>flags()</code> to add <code>Qt::ItemIsDragEnabled</code> in the case of a valid index
</p>
<p>
☑ Reimplement <code>supportedDragActions()</code> to return <code>Qt::MoveAction</code>
</p>
<p>
☑ Reimplement <code>mimeData()</code> to serialize the complete data for the dragged items. If the views are always in the same process, you can get away with serializing only node pointers (if you have that, e.g. for tree models) and application PID (to refuse dropping onto another process). Otherwise you can encode the actual data, like this:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">QMimeData *CountryModel::mimeData(const QModelIndexList &indexes) const
{
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
// This calls operator<<(QDataStream &stream, const CountryData &countryData), which you must implement
stream << m_data.at(index.row());
}
QMimeData *mimeData = new QMimeData;
mimeData->setData(s_mimeType, encodedData);
return mimeData;
}</code></pre>
</div>
<div class="rich-text">
<p>
<code>s_mimeType</code> is the name of the type of data (make up a name, it usually starts with <code>application/x-</code>)
</p>
<p>
☑ Reimplement <code>removeRows()</code>, it will be called after a successful drop. For instance, if your data is in a vector called <code>m_data</code>, the implementation would look like this:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">bool CountryModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(parent, position, position + rows - 1);
for (int row = 0; row < rows; ++row)
m_data.removeAt(position);
endRemoveRows();
return true;
}</code></pre>
</div>
<div class="rich-text">
<h3>
Setting up the view on the drop side
</h3>
<p>
☑ Call <code>view->setDragDropMode(QAbstractItemView::DragDrop)</code> (already done if both views should support dragging and dropping)
</p>
<h3>
Setting up the model on the drop side
</h3>
<p>
To implement dropping items into a model (between existing items), you need to implement the following:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">class DropModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return Qt::ItemIsDropEnabled;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable; // and optionally Qt::ItemIsDragEnabled (previous section)
}
// the default is "copy only", change it
Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
QStringList mimeTypes() const override { return {QString::fromLatin1(s_mimeType)}; }
bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override; // see below
};</code></pre>
</div>
<div class="rich-text">
<p>
☑ Reimplement <code>supportedDropActions()</code> to return <code>Qt::MoveAction</code>
</p>
<p>
☑ Reimplement <code>flags()</code><br>
For a valid index, make sure <code>Qt::ItemIsDropEnabled</code> is NOT set (except for tree models where we need to drop onto items in order to insert a first child).<br>
For the invalid index, add <code>Qt::ItemIsDropEnabled</code>, to allow dropping between items.
</p>
<p>
☑ Reimplement <code>mimeTypes()</code> and return the name of the MIME type used by the <code>mimeData()</code> function on the drag side.
</p>
<p>
☑ Reimplement <code>dropMimeData()</code><br>
to deserialize the data and insert new rows.<br>
In the special case of in-process tree models, clone the dragged nodes.<br>
In both cases, once you're done, return true, so that the drag side then deletes the dragged rows by calling <code>removeRows()</code> on its model.
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">bool DropModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
~~~ // safety checks, see full example code
if (row == -1) // drop into empty area = append
row = rowCount(parent);
// decode data
const QByteArray encodedData = mimeData->data(s_mimeType);
QDataStream stream(encodedData);
QVector<CountryData> newCountries;
while (!stream.atEnd()) {
CountryData countryData;
stream >> countryData;
newCountries.append(countryData);
}
// insert new countries
beginInsertRows(parent, row, row + newCountries.count() - 1);
for (const CountryData &countryData : newCountries)
m_data.insert(row++, countryData);
endInsertRows();
return true; // let the view handle deletion on the source side by calling removeRows there
}</code></pre>
</div>
<div class="rich-text">
<h2>
Using item widgets
</h2>
<p>
<a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part2-moving-items-between-views/itemwidgets" rel="noopener noreferrer" target="_blank">Example code</a> can be found following this link.
</p>
<h3>
For all kinds of widgets
</h3>
<p>
On the "drag" side:
</p>
<p>
☑ Call <code>widget->setDragDropMode(QAbstractItemView::DragOnly)</code> or <code>DragDrop</code> if it should support both
</p>
<p>
☑ Call <code>widget->setDefaultDropAction(Qt::MoveAction)</code> so the drag starts as a move right away
</p>
<p>
On the "drop" side:
</p>
<p>
☑ Call <code>widget->setDragDropMode(QAbstractItemView::DropOnly)</code> or <code>DragDrop</code> if it should support both
</p>
<p>
☑ Reimplement <code>supportedDropActions()</code> to return only <code>Qt::MoveAction</code>
</p>
<h3>
Additional requirements for QTableWidget
</h3>
<p>
When using <code>QTableWidget</code>, in addition to the common steps above you need to:
</p>
<p>
On the "drag" side:
</p>
<p>
☑ Call <code>item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);</code> for each item, to disable dropping onto items.
</p>
<p>
☑ Call <code>widget->setDragDropOverwriteMode(false)</code> so that after a move the rows are removed rather than cleared
</p>
<p>
On the "drop" side:
</p>
<p>
☑ Call <code>widget->setDragDropOverwriteMode(false)</code> so that it inserts rows instead of replacing cells (the default is <code>false</code> for the other views anyway)
</p>
<p>
☑ Another problem is that the items created by a drop will automatically get the <code>Qt::ItemIsDropEnabled</code> flag, which you don't want. To solve this, use <code>widget->setItemPrototype()</code> with an item that has the right flags (see the example).
</p>
<h3>
Additional requirements for QTreeWidget
</h3>
<p>
When using <code>QTreeWidget</code>, you cannot disable dropping onto items (which creates a child of the item).
</p>
<p>
You could call <code>item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);</code> on your own items, but when <code>QTreeWidget</code> creates new items upon a drop, you cannot prevent them from having the flag <code>Qt::ItemIsDropEnabled</code> set. The prototype solution used above for <code>QTableWidget</code> doesn't exist for <code>QTreeWidget</code>.
</p>
<p>
This means, if you want to let the user build and reorganize an actual tree, you can use <code>QTreeWidget</code>. But if you just want a flat multi-column list, then you should use <code>QTreeView</code> (see previous section on model/view separation).
</p>
<h2>
Addendum: Move/copy items between views
</h2>
<p>
If the user should be able to choose between copying and moving items, follow the previous section and make the following changes.
</p>
<h3>
With Model/View separation
</h3>
<p>
On the "drag" side:
</p>
<p>
☑ Call <code>view->setDefaultDropAction(...)</code> to choose whether the default should be move or copy. The user can press Shift to force a move, and Ctrl to force a copy.
</p>
<p>
☑ Reimplement <code>supportedDragActions()</code> in the model to return <code>Qt::MoveAction | Qt::CopyAction</code>
</p>
<p>
On the "drop" side:
</p>
<p>
☑ Reimplement <code>supportedDropActions()</code> in the model to return <code>Qt::MoveAction | Qt::CopyAction</code>
</p>
<p>
The good news is that there's nothing else to do.
</p>
<h3>
Using item widgets
</h3>
<p>
On the "drag" side:
</p>
<p>
☑ Call <code>widget->setDefaultDropAction(...)</code> to choose whether the default should be move or copy. The user can press Shift to force a move, and Ctrl to force a copy.
</p>
<p>
Until Qt 6.10 there was no <code>setSupportedDragActions()</code> method in the item widget classes (that was <a href="https://bugreports.qt.io/browse/QTBUG-87465" rel="noopener noreferrer" target="_blank">QTBUG-87465</a>, I implemented it for 6.10). Fortunately the default behavior is to use what <code>supportedDropActions()</code> returns so if you just want move and copy in both, reimplementing <code>supportedDropActions()</code> is enough.
</p>
<p>
On the "drop" side:
</p>
<p>
☑ Reimplement <code>supportedDropActions()</code> in the item widget class to return <code>Qt::MoveAction | Qt::CopyAction</code>
</p>
<p>
The good news is that there's nothing else to do.
</p>
<h3>
Improvements to Qt
</h3>
<p>
While writing and testing these code examples, I improved the following things in Qt:
</p>
<ul>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-1387" rel="noopener noreferrer" target="_blank">QTBUG-1387</a> "Drag and drop multiple columns with item views. Dragging a row and dropping it in a column > 0 creates multiple rows.", fixed in 6.8.1
</li>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-36831" rel="noopener noreferrer" target="_blank">QTBUG-36831</a> "Drop indicator painted as single pixel when not shown" fixed in 6.8.1
</li>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-87465" rel="noopener noreferrer" target="_blank">QTBUG-87465</a> ItemWidgets: add supportedDragActions()/setSupportedDragActions(), implemented in 6.10
</li>
</ul>
<h2>
Conclusion
</h2>
<p>
In the next blog post of this series, you will learn how to move (or copy) onto existing items, rather than between them.
</p>
</div>
<p>
The post <a href="https://www.kdab.com/modelview-drag-and-drop-in-qt-part-2/">Model/View Drag and Drop in Qt - Part 2</a> appeared first on <a href="https://www.kdab.com">KDAB</a>.
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?p=11713</guid>
<title>Qt – basysKom GmbH: 3D Rendering Solutions in Qt – an Overview</title>
<pubDate>Mon, 03 Mar 2025 13:45:22 GMT</pubDate>
<link>https://www.basyskom.de/3d-rendering-solutions-in-qt-an-overview/</link>
<description><p>
<a href="https://www.basyskom.de/3d-rendering-solutions-in-qt-an-overview/"><img align="left" alt="3D Rendering Solutions in Qt – an Overview" height="150" src="https://www.basyskom.de/wp-content/uploads/2020/07/Qt_logo_space-01-01-300x150.png" style="margin: 0 20px 20px 0;" width="300"></a>
</p>
<p>
Qt’s 3D offering is changing, so we decided to look at different options for rendering 3D content in Qt.
</p>
<p>
<a href="https://www.basyskom.de/3d-rendering-solutions-in-qt-an-overview/" rel="nofollow">Continue reading 3D Rendering Solutions in Qt – an Overview at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">https://scythe-studio.com/en/blog/qt-can-bus-example-how-to-start</guid>
<title>Scythe Studio Qt Blog: Qt CAN Bus Example – How to start?</title>
<pubDate>Mon, 03 Mar 2025 12:08:21 GMT</pubDate>
<link>https://scythe-studio.com/en/blog/qt-can-bus-example-how-to-start</link>
<description><p>
I welcome you to another blog post. The last one discussed a form of communication between devices using Qt Bluetooth Low […]
</p></description>
</item>
<item>
<guid isPermaLink="false">tag:www.pythonguis.com,2025-02-26:/faq/which-python-gui-library/</guid>
<title>Python GUIs - qt: Which Python GUI library should you use? — Comparing the Python GUI libraries available in 2025</title>
<pubDate>Wed, 26 Feb 2025 06:00:00 GMT</pubDate>
<link>https://www.pythonguis.com/faq/which-python-gui-library/</link>
<description><p>
Python is a popular programming used for everything from scripting routine tasks to building websites and performing complex data analysis. While you can accomplish a lot with command line tools, some tasks are better suited to graphical interfaces. You may also find yourself wanting to build a desktop front-end for an existing tool to improve usability for non-technical users. Or maybe you're building some hardware or a mobile app and want an intuitive touchscreen interface.
</p>
<p>
To create <em>graphical user interfaces</em> with Python, you need a GUI library. Unfortunately, at this point things get pretty confusing -- there are many different GUI libraries available for Python, all with different capabilities and licensing. <em>Which Python GUI library should you use for your project?</em>
</p>
<p>
In this article, we will look at a selection of the most popular Python GUI frameworks currently available and why you should consider using them for your own projects. You'll learn about the relative strengths of each library, understand the licensing limitations and see a simple <em>Hello, World!</em> application written in each. By the end of the article you should feel confident choosing the right library for your project.
</p>
<p class="admonition admonition-note">
<strong>tldr</strong> If you're looking to build professional quality software, start with PySide6 or PyQt6. The Qt framework is batteries-included — whatever your project, you'll be able to get it done. We have a complete <a href="https://www.pythonguis.com/pyside6-tutorial/">PySide6 tutorial</a> and <a href="https://www.pythonguis.com/pyqt6-tutorial/">PyQt6 tutorial</a>.
</p>
<p>
There is <em>some</em> bias here -- I have been working with Qt for about 10 years, writing books & developing commercial software. But I chose it and continue to use it because it's the best option for getting things done. That said, the other libraries all have their place & may be a better fit for your own project -- read on to find out!
</p>
<p class="admonition admonition-tip">
If you're looking for more demos to see what you can do with Python, we have a Github respository full of <a href="https://github.com/pythonguis/pythonguis-examples">Python GUI examples</a> to get you started.
</p>
<div class="toc">
<span class="toctitle">Table of Contents</span>
<ul>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#pyqt-or-pyside">PyQt or PySide</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#tkinter">Tkinter</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#kivy">Kivy</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#beeware-toga">BeeWare Toga</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#pyqtpyside-with-qml">PyQt/PySide with QML</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#pysimplegui">PySimpleGUI</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#wxpython">WxPython</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#pygobject-gtk">PyGObject (GTK+)</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#remi">Remi</a>
</li>
<li>
<a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#conclusion">Conclusion</a>
</li>
</ul>
</div>
<h2 id="pyqt-or-pyside">
PyQt or PySide
</h2>
<p>
<em><strong>Best for</strong> commercial, multimedia, scientific or engineering desktop applications. Best all-rounder with batteries included.</em>
</p>
<p>
PyQt and PySide are wrappers around the Qt framework. They allow you to easily create modern interfaces that look right at home on any platform, including Windows, macOS, Linux and even Android. They also have solid tooling with the most notable being <em>Qt Creator</em>, which includes a WYSIWYG editor for designing GUI interfaces quickly and easily. Being backed by a commercial project means that you will find plenty of support and online learning resources to help you develop your application.
</p>
<p>
Qt (and by extension PyQt & PySide) is not just a GUI library, but a complete application development framework. In addition to standard UI elements, such as widgets and layouts, Qt provides MVC-like data-driven views (spreadsheets, tables), database interfaces & models, graph plotting, vector graphics visualization, multimedia playback, sound effects & playlists and built-in interfaces for hardware such as printing. The Qt signals and slots models allows large applications to be built from re-usable and isolated components.
</p>
<p>
While other toolkits can work great when building small & simple tools, Qt really comes into its own for building real <em>commercial-quality</em> applications where you will benefit from the pre-built components. This comes at the expense of a slight learning curve. However, for smaller projects Qt is not really any more complex than other libraries. Qt Widgets-based applications use platform native widgets to ensure they look and feel at home on Windows, macOS and Qt-based Linux desktops.
</p>
<p>
<strong>Installation</strong> <code>pip install pyqt6</code> or <code>pip install pyside6</code>
</p>
<p>
A simple <em>hello world</em> application in PyQt6, using the Qt Widgets API is shown below.
</p>
<div class="tabbed-area multicode">
<ul class="tabs">
<li class="tab-link current">PyQt6
</li>
<li class="tab-link">PySide6
</li>
</ul>
<div class="tab-content current code-block-outer" id="42fca544c9d4459b9ec3db768c87da19">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">from PyQt6.QtWidgets import QMainWindow, QApplication, QPushButton
import sys
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()
app = QApplication(sys.argv)
w = MainWindow()
app.exec()
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="08f40e151e4b4c2ab296393f768a101c">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton
import sys
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()
app = QApplication(sys.argv)
w = MainWindow()
app.exec()
</code></pre>
</div>
</div>
</div>
<p class="admonition admonition-tip">
As you can see, the code is almost identical between PyQt & PySide, so it's not something to be concerned about when you start developing with either: you can always migrate easily if you need to.
</p>
<p>
<img alt="PyQt6 Application Screenshot" height="346" src="https://www.pythonguis.com/static/faq/python-gui-libraries/pyqt6-windows.jpg" width="473"> <em>Hello world application built using PyQt6, running on Windows 11</em>
</p>
<p>
Before the Qt Company (under Nokia) released the officially supported PySide library in 2009, Riverbank Computing had released PyQt in 1998. The main difference between these two libraries is in <em>licensing</em>.
</p>
<ul>
<li>
<strong>PyQt</strong> The free-to-use version of PyQt is licensed under <strong>GNU General Public License (GPL) v3</strong>. This means that you must also license your applications with the GPL <em>unless</em> you purchase a commercial version
</li>
<li>
<strong>PySide</strong> is licensed under <strong>GNU Lesser General Public License (LGPL)</strong>. This means PySide may be used in your applications without any additional fee.
</li>
</ul>
<p>
Note that both these libraries are separate from Qt itself which also has a free-to-use, open source version and a paid, commercial version.
</p>
<p class="admonition admonition-tip">
For a more information see our article on <a href="https://www.pythonguis.com/faq/pyqt-vs-pyside/">PyQt vs PySide licensing</a>. <strong>tldr</strong> If you want to develop closed-source software without paying for a license, use PySide6.
</p>
<ul>
<li>
<p>
<strong>PySide6</strong>
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/pyside6-book/">PySide6 Book</a>
</li>
<li>
<a href="https://www.pythonguis.com/pyside6-tutorial/">PySide6 Tutorial</a>
</li>
<li>
<a href="https://www.qt.io/qt-for-python">PySide Website</a>
</li>
<li>
<a href="https://doc.qt.io/qtforpython/">PySide Documentation</a>
</li>
<li>
<a href="https://github.com/PySide">GitHub Repository</a>
</li>
</ul>
</li>
<li>
<p>
<strong>PyQt6</strong>
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/pyqt6-book/">PyQt6 Book</a>
</li>
<li>
<a href="https://www.pythonguis.com/pyqt6-tutorial/">PyQt6 Tutorial</a>
</li>
<li>
<a href="https://www.martinfitzpatrick.com/pyqt6-crash-course/">PyQt6 Video Course</a>
</li>
<li>
<a href="https://www.riverbankcomputing.com/software/pyqt/intro">PyQt Website</a>
</li>
<li>
<a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/">PyQt6 Documentation</a>
</li>
</ul>
</li>
<li>
<p>
<strong>PyQt5</strong>
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/pyqt5-book/">PyQt5 Book</a>
</li>
<li>
<a href="https://www.pythonguis.com/pyqt5-tutorial/">PyQt5 Tutorial</a>
</li>
<li>
<a href="https://www.riverbankcomputing.com/static/Docs/PyQt5/">PyQt6 Documentation</a>
</li>
</ul>
</li>
</ul>
<h2 id="tkinter">
Tkinter
</h2>
<p>
<em><strong>Best for</strong> simple portable Python GUIs and applications</em>
</p>
<p>
Tkinter is the defacto GUI framework for Python. It comes bundled with Python on both Windows and macOS. (On Linux, it may require downloading an additional package from your distribution's repo.) Tkinter is a wrapper written around the Tk GUI toolkit. Its name is an amalgamation of the words <em>Tk</em> and <em>Interface</em>.
</p>
<p>
Tkinter is a simple library with support for standard layouts and widgets, as well as more complex widgets such as tabbed views & progressbars. Tkinter is a pure GUI library, not a framework. There is no built-in support for GUIs driven from 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. Tkinter is cross-platform however the widgets can look outdated, particularly on Windows.
</p>
<p>
<strong>Installation</strong> Already installed with Python on Windows and macOS. Ubuntu/Debian Linux <code>sudo apt install python3-tk</code>
</p>
<p>
A simple <em>hello world</em> application in Tkinter is shown below.
</p>
<div class="tabbed-area multicode">
<ul class="tabs">
<li class="tab-link current">Standard
</li>
<li class="tab-link">Class-based
</li>
</ul>
<div class="tab-content current code-block-outer" id="fbf57b37c3bd47a2888b7256f8d36d14">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">import tkinter as tk
window = tk.Tk()
window.title("Hello World")
def handle_button_press(event):
window.destroy()
button = tk.Button(text="My simple app.")
button.bind("", handle_button_press)
button.pack()
# Start the event loop.
window.mainloop()
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="2cc5e90450ee48d0871bbbff559d5f3d">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">from tkinter import Tk, Button
class Window(Tk):
def __init__(self):
super().__init__()
self.title("Hello World")
self.button = Button(text="My simple app.")
self.button.bind("", self.handle_button_press)
self.button.pack()
def handle_button_press(self, event):
self.destroy()
# Start the event loop.
window = Window()
window.mainloop()
</code></pre>
</div>
</div>
</div>
<p>
<img alt="Tkinter Application Screenshot" height="218" src="https://www.pythonguis.com/static/faq/python-gui-libraries/tkinter-windows.jpg" width="387"> <em>Hello world application built using Tkinter, running on Windows 11</em>
</p>
<p>
Tkinter was originally developed by Steen Lumholt and Guido Van Rossum, who designed Python itself. Both the GUI framework and the language are licensed under the same Python Software Foundation (PSF) License. While the license is compatible with the GPL, it is a 'permissive' license (similar to the MIT License) that allows it to be used for proprietary applications and modifications.
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/tkinter/">Tkinter tutorial</a>
</li>
<li>
<a href="https://docs.python.org/3/library/tkinter.html">Tkinter Documentation</a>
</li>
</ul>
<h2 id="kivy">
Kivy
</h2>
<p>
<em><strong>Best for</strong> Python mobile app development</em>
</p>
<p>
While most other GUI frameworks are <em>bindings</em> to toolkits written in other programming languages, Kivy is perhaps the only framework which is primarily written in pure Python. If you want to create touchscreen-oriented interfaces with a focus on mobile platforms such as Android and iOS, this is the way to go. This does run on desktop platforms (Windows, macOS, Linux) as well but note that your application may not look and behave like a <em>native application</em>. However, there is a pretty large community around this framework and you can easily find resources to help you learn it online.
</p>
<p>
The look and feel of Kivy is extremely customizable, allowing it to be used as an alternative to libraries like Pygame (for making games with Python). The developers have also released a number of separate libraries for Kivy. Some provide Kivy with better integration and access to certain platform-specific features, or help package your application for distribution on platforms like Android and iOS. Kivy has it's own design language called Kv, which is similar to QML for Qt. It allows you to easily separate the interface design from your application's logic.
</p>
<p class="admonition admonition-tip">
There is a 3rd party add-on for Kivy named KivyMD that replaces Kivy's widgets with ones that are compliant with Google's Material Design.
</p>
<p>
A simple <em>hello world</em> application in Kivy is shown below.
</p>
<p>
<strong>Installation</strong> <code>pip install kivy</code>
</p>
<p>
A simple <em>hello world</em> application in Kivy is shown below.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window
Window.size = (300, 200)
class MainWindow(BoxLayout):
def __init__(self):
super().__init__()
self.button = Button(text="Hello, World?")
self.button.bind(on_press=self.handle_button_clicked)
self.add_widget(button)
def handle_button_clicked(self, event):
self.button.text = "Hello, World!"
class MyApp(App):
def build(self):
self.title = "Hello, World!"
return MainWindow()
app = MyApp()
app.run()
</code></pre>
</div>
<p>
<img alt="Kivy Application Screenshot" height="466" src="https://www.pythonguis.com/static/faq/python-gui-libraries/kivy-windows.jpg" width="608"> <em>Hello world application built using Kivy, running on Windows 11</em>
</p>
<p>
An equivalent application built using the Kv declarative language is shown below.
</p>
<div class="tabbed-area multicode">
<ul class="tabs">
<li class="tab-link current">main.py
</li>
<li class="tab-link">controller.kv
</li>
</ul>
<div class="tab-content current code-block-outer" id="1f4204d642f64a669940de30539cc9f7">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">import kivy
kivy.require('1.0.5')
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty
class Controller(FloatLayout):
'''Create a controller that receives a custom widget from the kv lang file.
Add an action to be called from the kv lang file.
'''
def button_pressed(self):
self.button_wid.text = 'Hello, World!'
class ControllerApp(App):
def build(self):
return Controller()
if __name__ == '__main__':
ControllerApp().run()
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="34fa2478bebd4144b3aef7cabc78d92c">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">#:kivy 1.0
:
button_wid: custom_button
BoxLayout:
orientation: 'vertical'
padding: 20
Button:
id: custom_button
text: 'Hello, World?'
on_press: root.button_pressed()
</code></pre>
</div>
</div>
</div>
<p class="admonition admonition-note">
The name of the Kv file <em>must</em> match the name of the class from the main application -- here <code>Controller</code> and <code>controller.kv</code>.
</p>
<p>
<img alt="Kivy Kv Application Screenshot" height="912" src="https://www.pythonguis.com/static/faq/python-gui-libraries/kivykv-windows.jpg" width="1131"> <em>Hello world application built using Kivy + Kv, running on Windows 11</em>
</p>
<p>
In February 2011, Kivy was released as the spiritual successor to a similar framework called <strong>PyMT</strong>. While they shared similar goals and was also led by the current core developers of Kivy, where Kivy differs is in its underlying design and a professional organization which actively develops and maintains it. Kivy is licensed under the MIT license, which is a 'permissive' license that allows you to use it freely in both open source and proprietary applications. As such, you are even allowed to make proprietary modifications to the framework itself.
</p>
<ul>
<li>
<a href="https://kivy.org">Kivy Website</a>
</li>
<li>
<a href="https://kivy.org/doc/stable/">Kivy Documentation</a>
</li>
<li>
<a href="https://github.com/kivy">GitHub Repository</a>
</li>
<li>
<a href="https://kivymd.readthedocs.io/en/latest/">KivyMD Documentation</a>
</li>
<li>
<a href="https://kivy.org/doc/stable/guide/lang.html">Kv language documentation</a>
</li>
</ul>
<h2 id="beeware-toga">
BeeWare Toga
</h2>
<p>
<em><strong>Best for</strong> simple cross-platform native GUI development</em>
</p>
<p>
BeeWare is a collection of tools and libraries which work together to help you write cross platform Python applications with <em>native</em> GUIs. That means, the applications you create use the OS-provided widgets and behaviors, appearing just like any other application despite being written in Python.
</p>
<p>
The BeeWare system includes -
</p>
<ul>
<li>
<strong>Toga</strong> a cross platform widget toolkit, which we'll demo below.
</li>
<li>
<strong>Briefcase</strong> a tool for packaging Python projects as distributable artefacts.
</li>
<li>Libraries for accessing platform-native APIs.
</li>
<li>Pre-compiled builds of Python for platforms where official Python installers aren't avaiable.
</li>
</ul>
<p>
<strong>Installation</strong> <code>pip install toga</code>
</p>
<p>
A simple <em>hello world</em> application using BeeWare/Toga is shown below.
</p>
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">import toga
from toga.style import Pack
class HelloWorld(toga.App):
def startup(self):
layout = toga.Box()
self.button = toga.Button(
"Say Hello!",
on_press=self.say_hello,
style=Pack(margin=5),
)
layout.add(self.button)
self.main_window = toga.MainWindow(title="Hello world!")
self.main_window.content = layout
self.main_window.show()
def say_hello(self, source_widget):
# Receives the button that was clicked.
source_widget.text = "Hello, world!"
app = HelloWorld(formal_name="Hello, world!", app_id="hello.world")
app.main_loop()
</code></pre>
</div>
<p>
<img alt="Toga Application Screenshot" height="382" src="https://www.pythonguis.com/static/faq/python-gui-libraries/toga-windows.jpg" width="705"> <em>Hello world application built using BeeWare Toga, running on Windows 11</em>
</p>
<p>
BeeWare is developed by Russell Keith-Magee. It is licensed under the BSD 3-Clause "New" or "Revised" License. The above example is using a single Python file for simplicity, see the linked tutorial below to see how to set up an application using Briefcase for cross-platform deployment.
</p>
<ul>
<li>
<a href="https://docs.beeware.org/en/latest/">Toga tutorial</a>
</li>
<li>
<a href="https://beeware.org/">BeeWare Homepage</a>
</li>
<li>
<a href="https://github.com/beeware/toga">Toga Source Code</a>
</li>
</ul>
<h2 id="pyqtpyside-with-qml">
PyQt/PySide with QML
</h2>
<p>
<em><strong>Best for</strong> Raspberry Pi, microcontrollers, industrial and consumer electronics</em>
</p>
<p>
When using PyQt and PySide you actually have <em>two</em> options for building your GUIs. We've already introduced the Qt Widgets API which is well-suited for building desktop applications. But Qt also provides a <em>declarative</em> API in the form of Qt Quick/QML.
</p>
<p>
Using Qt Quick/QML you have access to the entire Qt framework for building your applications. Your UI consists of two parts: the Python code which handles the business logic and the QML which defines the structure and behavior of the UI itself. You can control the UI from Python, or use embedded Javascript code to handle events and animations.
</p>
<p>
Qt Quick/QML is ideally suited for building modern touchscreen interfaces for microcontrollers or device interfaces -- for example, building interfaces for microcontrollers like the Raspberry Pi. However you can also use it on desktop to build completely customized application experiences, like those you find in media player applications like Spotify, or to desktop games.
</p>
<p>
<strong>Installation</strong> <code>pip install pyqt6</code> or <code>pip install pyside6</code>
</p>
<p>
A simple <em>Hello World</em> app in PyQt6 with QML. Save the QML file in the same folder as the Python file, and run as normally.
</p>
<div class="tabbed-area multicode">
<ul class="tabs">
<li class="tab-link current">main.py
</li>
<li class="tab-link">main.qml
</li>
</ul>
<div class="tab-content current code-block-outer" id="52635e456b264461b2ada2a0e177dcd1">
<div class="code-block">
<span class="code-block-language code-block-python">python</span>
<pre><code class="python">import sys
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('main.qml')
sys.exit(app.exec())
</code></pre>
</div>
</div>
<div class="tab-content code-block-outer" id="41a504134b0e497594bdcf0b57a770ca">
<div class="code-block">
<span class="code-block-language code-block-qml">qml</span>
<pre><code class="qml">import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 600
height: 500
title: "HelloApp"
Text {
anchors.centerIn: parent
text: "Hello World"
font.pixelSize: 24
}
}
</code></pre>
</div>
</div>
</div>
<p>
Licensing for Qt Quick/QML applications is the same as for other PyQt/PySide apps.
</p>
<ul>
<li>
<p>
<strong>PyQt</strong>
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/tutorials/qml-qtquick-python-application/">QML/PyQt5 Tutorial</a>
</li>
<li>
<a href="https://www.pythonguis.com/tutorials/pyqt6-qml-qtquick-python-application/">QML/PyQt6 Tutorial</a>
</li>
<li>
<a href="https://www.riverbankcomputing.com/software/pyqt/intro">PyQt Website</a>
</li>
<li>
<a href="https://www.riverbankcomputing.com/static/Docs/PyQt6/">PyQt6 Documentation</a>
</li>
</ul>
</li>
<li>
<p>
<strong>PySide</strong>
</p>
<ul>
<li>
<a href="https://www.pythonguis.com/tutorials/pyside-qml-qtquick-python-application/">QML/PySide2 Tutorial</a>
</li>
<li>
<a href="https://www.pythonguis.com/tutorials/pyside6-qml-qtquick-python-application/">QML/PySide6 Tutorial</a>
</li>
<li>
<a href="https://www.qt.io/qt-for-python">PySide Website</a>
</li>
<li>
<a href="https://doc.qt.io/qtforpython/">PySide Documentation</a>
</li>
<li>
<a href="https://github.com/PySide">GitHub Repository</a>
</li>
</ul>
</li>
</ul>
<p>
<img alt="PyQt6 QML Application Screenshot" height="861" src="https://www.pythonguis.com/static/faq/python-gui-libraries/pyqt6qml-windows.jpg" width="952"> <em>Hello world application built using PyQt6 & QML, running on Windows 11</em>
</p>
<h2 id="pysimplegui">
PySimpleGUI
</h2>
<p class="admonition admonition-warning">
PySimpleGUI is no longer being developed. We do not recommend it if you are starting a new project in 2025. Take a look at <a href="https://www.pythonguis.com/feeds/qt.tag.atom.xml#pyqt-or-pyside">PySide6</a> instead.
</p>
<p>
PySimpleGUI aims to simplify GUI application development for Python. It doesn't reinvent the wheel but provides a wrapper around other existing frameworks such as Tkinter, Qt (PySide 2), WxPython and Remi. By doing so, it lowers the barrier to creating a GUI but also allows you to easily migrate from one GUI framework to another by simply changing the import statement.
</p>
<p>
While there is a separate port of PySimpleGUI for each of these frameworks, the Tkinter version is considered the most feature complete. Wrapping other libraries comes at a cost however —</description>
</item>
<item>
<guid isPermaLink="false">https://www.basyskom.de/?p=11678</guid>
<title>Qt – basysKom GmbH: PySide6, Qt for Python</title>
<pubDate>Thu, 13 Feb 2025 09:00:00 GMT</pubDate>
<link>https://www.basyskom.de/pyside6-qt-for-python/</link>
<description><p>
<a href="https://www.basyskom.de/pyside6-qt-for-python/"><img align="left" alt="PySide6, Qt for Python" height="150" src="https://www.basyskom.de/wp-content/uploads/2020/07/Qt_logo_space-01-01-300x150.png" style="margin: 0 20px 20px 0;" width="300"></a>
</p>
<p>
In this article, we’re going to have a look at Qt for Python, how it integrates with Qt Creator, and why you might want to consider using it, too.
</p>
<p>
<a href="https://www.basyskom.de/pyside6-qt-for-python/" rel="nofollow">Continue reading PySide6, Qt for Python at basysKom GmbH.</a>
</p></description>
</item>
<item>
<guid isPermaLink="false">https://www.kdab.com/modelview-drag-and-drop-part-1/</guid>
<title>KDAB on Qt: Model/View Drag and Drop in Qt - Part 1</title>
<pubDate>Thu, 06 Feb 2025 08:07:00 GMT</pubDate>
<link>https://www.kdab.com/modelview-drag-and-drop-part-1/</link>
<description><h1>
Model/View Drag and Drop in Qt - Part 1
</h1>
<div class="rich-text">
<p>
This blog series is all about implementing drag-and-drop in the Qt model/view framework. In addition to complete code examples, you'll find checklists that you can go through to make sure that you did not forget anything in your own implementation, when something isn't working as expected.
</p>
<p>
At first, we are going to look at Drag and Drop within a single view, to change the order of the items. The view can be a list, a table or a tree, there are very little differences in what you have to do.
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-table-step1" class="part1-table-step1" id="part1-table-step1" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-table-step1.original.png" name="part1-table-step1">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row in a tableview, step 1
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-table-step2" class="part1-table-step2" id="part1-table-step2" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-table-step2.original.png" name="part1-table-step2">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row in a tableview, step 2
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-table-step3" class="part1-table-step3" id="part1-table-step3" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-table-step3.original.png" name="part1-table-step3">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Moving a row in a tableview, step 3
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
The main question, however, is whether you are using QListView/QTableView/QTreeView on top of a custom item model, or QListWidget/QTableWidget/QTreeWidget with items in them. Let's explore each one in turn.
</p>
<h2>
With Model/View separation
</h2>
<p>
The code being discussed here is extracted from <a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part1-reordering-elements/model-view" rel="noopener noreferrer" target="_blank">the example</a>. That example features a flat model, while <a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part1-reordering-elements/treemodel" rel="noopener noreferrer" target="_blank">this example</a> features a tree model. The checklist is the same for these two cases.
</p>
<h3>
Setting up the view
</h3>
<p>
☑ Call <code>view->setDragDropMode(QAbstractItemView::InternalMove)</code> to enable the mode where only moving within the same view is allowed
</p>
<p>
☑ When using <code>QTableView</code>, call <code>view->setDragDropOverwriteMode(false)</code> so that it inserts rows instead of replacing cells (the default is <code>false</code> for the other views anyway)
</p>
<h3>
Adding drag-n-drop support to the model
</h3>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-list" class="part1-list" id="part1-list" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-list.original.png" name="part1-list">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable ListView
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-table" class="part1-table" id="part1-table" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-table.original.png" name="part1-table">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable TableView
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
For a model being used in QListView or QTableView, all you need is something like this:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">class CountryModel : public QAbstractTableModel
{
~~~
Qt::ItemFlags flags(const QModelIndex &index) const override
{
if (!index.isValid())
return Qt::ItemIsDropEnabled; // allow dropping between items
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
}
// the default is "copy only", change it
Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
// the default is "return supportedDropActions()", let's be explicit
Qt::DropActions supportedDragActions() const override { return Qt::MoveAction; }
QStringList mimeTypes() const override { return {QString::fromLatin1(s_mimeType)}; }
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override; // see below
};</code></pre>
</div>
<div class="rich-text">
<p>
The checklist for the changes you need to make in your model is therefore the following:
</p>
<p>
☑ Reimplement <code>flags()</code><br>
For a valid index, add <code>Qt::ItemIsDragEnabled</code> and make sure <code>Qt::ItemIsDropEnabled</code> is NOT set (except for tree models where we need to drop onto items in order to insert a first child). \
</p>
<p>
☑ Reimplement <code>mimeTypes()</code> and make up a name for the mimetype (usually starting with <code>application/x-</code>)
</p>
<p>
☑ Reimplement <code>supportedDragActions()</code> to return <code>Qt::MoveAction</code>
</p>
<p>
☑ Reimplement <code>supportedDropActions()</code> to return <code>Qt::MoveAction</code>
</p>
<p>
☑ Reimplement <code>moveRows()</code>
</p>
<p>
Note that this approach is only valid when using <code>QListView</code> or, assuming Qt >= 6.8.0, <code>QTableView</code> - see the following sections for details.
</p>
<p>
In a model that encapsulates a <code>QVector</code> called <code>m_data</code>, the implementation of <code>moveRows</code> can look like this:
</p>
</div>
<div class="formatted-code">
<pre><code class="language-cpp line-numbers">bool CountryModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
{
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild))
return false; // invalid move, e.g. no-op (move row 2 to row 2 or to row 3)
for (int i = 0; i < count; ++i) {
m_data.move(sourceRow + i, destinationChild + (sourceRow > destinationChild ? 0 : -1));
}
endMoveRows();
return true;
}</code></pre>
</div>
<div class="rich-text">
<h3>
QTreeView does not call moveRows
</h3>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-tree" class="part1-tree" id="part1-tree" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-tree.original.png" name="part1-tree">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable treeview
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-treemodel" class="part1-treemodel" id="part1-treemodel" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-treemodel.original.png" name="part1-treemodel">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable treeview with a tree model
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
QTreeView does not (yet?) call <code>moveRows</code> in the model, so you need to:
</p>
<p>
☑ Reimplement <code>mimeData()</code> to encode row numbers for flat models, and node pointers for tree models
</p>
<p>
☑ Reimplement <code>dropMimeData()</code> to implement the move and return false (meaning: all done)
</p>
<p>
Note that this means a move is in fact an insertion and a deletion, so the selection isn't automatically updated to point to the moved row(s).
</p>
<h3>
QTableView in Qt < 6.8.0
</h3>
<p>
I implemented moving of rows in <code>QTableView</code> itself for Qt 6.8.0, so that moving rows in a table view is simpler to implement (one method instead of two), more efficient, and so that selection is updated. If you're not yet using Qt >= 6.8.0 then you'll have to reimplement <code>mimeData()</code> and <code>dropMimeData()</code> in your model, as per the previous section.
</p>
<p>
This concludes the section on how to implement a reorderable view using a separate model class.
</p>
<h2>
Using item widgets
</h2>
<p>
The alternative to model/view separation is the use of the item widgets (<code>QListWidget</code>, <code>QTableWidget</code> or <code>QTreeWidget</code>) which you populate directly by creating items.
</p>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-listwidget" class="part1-listwidget" id="part1-listwidget" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-listwidget.original.png" name="part1-listwidget">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable QListWidget
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-tablewidget" class="part1-tablewidget" id="part1-tablewidget" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-tablewidget.original.png" name="part1-tablewidget">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable QTableWidget
</p>
</div>
</div>
</div>
</div>
<div class="image-variable-size-block">
<div class="image-variable-positioning-block right-margin-auto left-margin-auto width-100">
<div class="image-variable-size-image">
<img alt="part1-treewidget" class="part1-treewidget" id="part1-treewidget" src="https://eu-central-1.linodeobjects.com/wagtail-production/images/part1-treewidget.original.png" name="part1-treewidget">
</div>
<div class="image-variable-size-caption text-center">
<div class="rich-text">
<p>
Reorderable QTreeWidget
</p>
</div>
</div>
</div>
</div>
<div class="rich-text">
<p>
Here's what you need to do to allow users to reorder those items.
</p>
<p>
<a href="https://github.com/KDABLabs/blogs-qt/tree/main/ItemViews-DragAndDrop/part1-reordering-elements/itemwidgets" rel="noopener noreferrer" target="_blank">Example code</a> can be found following this link.
</p>
<h3>
Reorderable QListWidget
</h3>
<p>
☑ Call <code>listWidget->setDragDropMode(QAbstractItemView::InternalMove)</code> to enable the mode where only moving within the same view is allowed
</p>
<p>
For a <code>QListWidget</code>, this is all you need. That was easy!
</p>
<h3>
Reorderable QTableWidget
</h3>
<p>
When using <code>QTableWidget</code>:
</p>
<p>
☑ Call <code>tableWidget->setDragDropMode(QAbstractItemView::InternalMove)</code>
</p>
<p>
☑ Call <code>tableWidget->setDragDropOverwriteMode(false)</code> so that it inserts rows instead of replacing cells
</p>
<p>
☑ Call <code>item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);</code> on each item, to disable dropping onto items
</p>
<p>
Note: Before Qt 6.8.0, <code>QTableWidget</code> did not really support moving rows. It would instead move data into cells (like Excel). The example code shows a workaround, but since it calls code that inserts a row and deletes the old one, header data is lost in the process. My changes in Qt 6.8.0 implement support for moving rows in <code>QTableWidget</code>'s internal model, so it's all fixed there. If you really need this feature in older versions of Qt, consider switching to <code>QTableView</code>.
</p>
<h3>
Reorderable QTreeWidget
</h3>
<p>
When using <code>QTreeWidget</code>:
</p>
<p>
☑ Call <code>tableWidget->setDragDropMode(QAbstractItemView::InternalMove)</code>
</p>
<p>
☑ Call <code>item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);</code> on each item, to disable dropping onto items
</p>
<h3>
Conclusion about reorderable item widgets
</h3>
<p>
Of course, you'll also need to iterate over the items at the end to grab the new order, like the example code does. As usual, item widgets lead to less code to write, but the runtime performance is worse than when using model/view separation. So, only use item widgets when the number of items is small (and you don't need proxy models).
</p>
<h2>
Improvements to Qt
</h2>
<p>
While writing and testing these code examples, I improved the following things in Qt 6.8:
</p>
<ul>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-13873" rel="noopener noreferrer" target="_blank">QTBUG-13873</a> / <a href="https://bugreports.qt.io/browse/QTBUG-101475" rel="noopener noreferrer" target="_blank">QTBUG-101475</a> - QTableView: implement moving rows by drag-n-drop
</li>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-69807" rel="noopener noreferrer" target="_blank">QTBUG-69807</a> - Implement QTableModel::moveRows
</li>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-130045" rel="noopener noreferrer" target="_blank">QTBUG-130045</a> - QTableView: fix dropping between items when precisely on the cell border
</li>
<li>
<a href="https://bugreports.qt.io/browse/QTBUG-1656" rel="noopener noreferrer" target="_blank">QTBUG-1656</a> - Implement full-row drop indicator when the selection behavior is SelectRows
</li>
</ul>
<h2>
Conclusion
</h2>
<p>
I hope this checklist will be useful when you have to implement your own reordering of items in a model or an item-widget. Please post a comment if anything appears to be incorrect or missing.
</p>
<p>
In the next blog post of this series, you will learn how to move (or even copy) items from one view to another.
</p>
</div>
<p>
The post <a href="https://www.kdab.com/modelview-drag-and-drop-part-1/">Model/View Drag and Drop in Qt - Part 1</a> appeared first on <a href="https://www.kdab.com">KDAB</a>.
</p></description>
</item>
</channel>
</rss>