Qt Creator 16.0.2 released
We are happy to announce the release of Qt Creator 16.0.2!

We are happy to announce the release of Qt Creator 16.0.2!
Would you trust a safety-critical industrial device that relied on outdated technology?
With too many Supervisory Control and Data Acquisition (SCADA) systems reliant on legacy frameworks like .NET or outdated MFC technology, today’s industrial automation systems are in urgent need of modernization. With factories operating 24/7, industries must modernize these systems without disrupting operations.
In this blog post, I want to share how we collaborated with the Cologne University of Applied Sciences (German: TH Köln) to support a new generation of developers exploring C++ and Qt. As a TH Köln alumnus who began his journey at Qt, I was honored by the opportunity to return and contribute. Alongside the course story, we’re highlighting the work of five student teams — sharing their ideas, the applications they built, and what they learned along the way.
Your HMI architecture needs to stay reliable as technology evolves and user demands change. Short-term efficiency matters, but true success means designing for long-term adaptability. Here’s how to build HMIs that handle today's workloads and scale effortlessly into the future.
We are happy to announce the release of Qt Creator 17 Beta!
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.
Continue reading Modern TableView in QML: What’s New in Qt 6.8 and Beyond at basysKom GmbH.
Qt is a powerful tool and framework for building modern cross-platform applications. From embedded devices to desktop software, Qt enables […]
The latest collaboration between Qt Group and Ezurio turns design concepts into realty. Built on Ezurio’s Tungsten700 SMARC System-on-Module (SOM), powered by MediaTek’s Genio 700 processor, this dual-demo experience showcases the power of AI and HMI running simultaneously on a single connected platform.
Qt for MCUs 2.10.1 has been released and is available for download. This patch release provides bug fixes and other improvements while maintaining source compatibility with Qt for MCUs 2.10 (see Qt for MCUs 2.10 blog post). This release does not add any new functionality.
Tier-2 platforms are updated to 2.10, refer to the supported platforms page for more details about these platforms.
A light-weight and small form factor version of the Infineon TRAVEO T2G CYT4DN mcu evaluation kit named 6M-Lite is now supported as a Tech Preview platform. With this platform we have also extended support for GNU-based ModusToolbox™ tool chain. This will enable users to quickly start with the evaluation process and develop sleek GUI applications. In the next release of ModusToolbox™ , Qt for MCUs template application will be available as part of the IDE. Stay tuned !
Microchip SAM9x75 is now supported as a Tier-3 platform on Yocto Linux. The Curiosity LAN kit can be quickly setup with a Qt for MCUs application in no time. We will further extend this port for bare-metal platform.
Today, we released Qt for Android Automotive 6.9. It builds upon Qt 6.9 which by itself bring a few features and bug fixes. You can check the full list of features in Qt 6.9 Released blog post.
We cover Qt-related issues on our blog, but only recently realized that many of our readers may not even know […]
Qt and QML provide a powerful framework for developing modern, cross-platform applications with a sleek and responsive UI. While Qt […]
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.
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.
This is quite a complicated example, but we'll be walking through it slowly step by step. The full source code is available, with working examples at each stage of the development if you get stuck.
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.
To learn the basics of PySide6, check out the complete PySide6 Tutorials or my book Create GUI Applications with Python & PySide6
To store the notes between sessions, we will use SQLAlchemy
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.
With that in mind, let's create a virtual environment and install our requirements into it. To do this, you can run the following commands:
$ mkdir notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
> mkdir notes/
> cd notes
> python -m venv venv
> venv\Scripts\activate.bat
(venv)> pip install pyside6 sqlalchemy
$ mkdir notes/
$ cd notes
$ python -m venv venv
$ source venv/bin/activate
(venv)$ pip install pyside6 sqlalchemy
With these commands, you create a notes/
folder for storing your project. Inside that folder, you create a new virtual environment, activate it, and install PySide6 and SQLAlchemy from PyPi.
For platform-specific troublshooting, check the Working With Python Virtual Environments tutorial.
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.
The UI for our desktop sticky notes will be a bit strange 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.
We'll start by defining a single note, and then deal with these other issues later. Create a new file named notes.py
and add the following outline application to it.
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()
In this code we first create a Qt QApplication
instance. This needs to be done before creating our widgets. Next we define a simple custom window class NoteWindow
by subclassing QWidget
. We add a vertical layout to the window, and enter a single QTextEdit
widget. We then create an instance of this window object as note
and show it by calling .show()
. This puts the window on the desktop. Finally, we start up our application by calling app.exec()
.
You can run this file like any other Pythons script.
python notes.py
When the applicaton launches you'll see the following on your desktop.
Simple "notes" window on the desktop
If you click in the text editor in the middle, you can enter some text.
Technically this is a note, but we can do better.
Our note doesn't look anything like a sticky note yet. Let's change that by applying some simple styles to it.
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.
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()
In the code above we have set a background color of hex #ffff99
for our note window, and set the text color to hex #62622f
a sort of muddy brown. The border:0
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.
If you run the code now you'll see this, much more notely note.
The note with the QSS styling applied
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 window flags. 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.
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()
To set window flags, we need to import the Qt flags from the QtCore
namespace. Then you can set flags on the window using .setWindowFlags()
. Note that since windows have flags already set, and we don't want to replace them all, we get the current flags with .windowFlags()
and then add the additional flags to it using boolean OR |
. We've added two flags here -- Qt.WindowType.FramelessWindowHint
which removes the window decorations, and Qt.WindowType.WindowStaysOnTopHint
which keeps the windows on top.
Run this and you'll see a window with the decorations removed.
Note with the window decorations removed
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).
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 QPushButton
and hook this up to the window's .close()
method to re-implement this.
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()
Our close button is created using QPushButton
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 thing that performs an action. Finally, we connect the .clicked
signal of the button to the window's close method self.close
. The button will close the window.
Later we'll use this button to delete notes.
To add the close button to the top right of the window, we create a horizontal layout with QHBoxLayout
. 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.
Run the code now and our note is complete!
The complete note UI with close button
The note looks 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.
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.
To implement this, add the following two methods to the bottom of the NoteWindow
class.
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()
Clicking and dragging a window involves three actions: the mouse press, the mouse move and the mouse release. We have defined two methods here mousePressEvent
and mouseMoveEvent
. In mousePressEvent
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.
The mouseMoveEvent
is called on every subsequent move while the mouse button remains pressed. On each move we take the new mouse position and subtract the previous position to get the delta -- that is, the change in mouse position from the initial press to the current event. Then we move the window by that amount, storing the new previous position after the move.
The effect of this is that ever time the mouseMoveEvent
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.
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.
Currently we're creating the NoteWindow
when the application starts up, just before we call app.exec()
. 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 NoteWindow
objects so they aren't automatically deleted (and the window closed) when the function or method exits.
Python automatically deletes objects when they fall out of scope if there aren't any remaining references to them.
We can solve this by storing the NoteWindow
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.
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()
In this code we've added our active_notewindows
dictionary. This holds references to our NoteWindow
objects, keyed by id()
. 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 __init__
method.
Next we've implemented a create_notewindow()
function which creates an instance of NoteWindow
and shows it, just as before. Nothing else is needed, since the note itself handles storing it's references on creation.
Finally, we've added multiple calls to create_notewindow()
to create multiple notes.
Multiple notes on the desktop
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.
There's quite a lot to this, so we'll step through it in stages.
Update the code, adding the imports shown at the top, and the rest following the definition of create_notewindow
.
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()
In this code we've first create an QIcon
object passing in the filename of the icon to use. I'm using a sticky note icon from the Fugue icon set by designer Yusuke Kamiyamane. Feel free to use any icon you prefer.
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 or provide the path.
The system tray icon is managed through a QSystemTrayIcon
object. We set our icon on this, and set the tray icon to visible (so it is not automatically hidden by Windows).
QSystemTrayIcon
has a signal activated
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 reason
(the reason for the activation) and then checks this against QSystemTrayIcon.ActivationReason.Trigger
. This is the reason reported when a left click is used.
If the left mouse button has been clicked, we call create_notewindow()
to create a new instance of a note.
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.
The sticky note icon in the tray
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 unstoppable.
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 QMenu
as a context menu on the QSystemTrayIcon
. The actions in menus in Qt are defined using QAction
.
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()
We create the menu using QMenu
. Actions are created using QAction
passing in the label as a string. This is the text that will be shown for the menu item. The .triggered
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 create_notewindow
function. We've also added an action to quit the application. This is connected to the built-in .quit
slot on our QApplication
instance.
The menu is set on the tray using .setContextMenu()
. In Qt context menus are automatically shown when the user right clicks on the tray.
Finally, we have also disabled the behavior of closing the application when the last window is closed using app.setQuitOnLastWindowClosed(False)
. 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".
If you find this annoying while developing, just comment this line out again.
We've had a lot of changes so far, so here is the current complete code.
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()
If you run this now you will be able to right click the note in the tray to show the menu.
The sticky note icon in the tray showing its context menu
Test the Add note and Quit functionality to make sure they're working.
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.
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.
We'll define our database in a separate file, to keep the UI file manageable. So start by creating a new file named database.py
in your project folder.
In that file add the imports for SQLAlchemy, and instantiate the Base
class for our models.
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
Next in the same database.py
file, define our note database model. This inherits from the Base
class we've just created, by calling 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)
Each note object has 4 properties:
Next we need to create the engine -- in this case, this is our SQLite file, which we're calling notes.db
.We can then create the tables (if they don't already exist). Since our Note
class registers itself with the Base
we can do that by calling create_all
on the Base
class.
engine = create_engine("sqlite:///notes.db")
Base.metadata.create_all(engine)
Save the database.py
file and run it
python database.py
After it is complete, if you look in the folder you should see the notes.db
. This file contains the table structure for the Note
model we defined above.
Finally, we need a session 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.
Add the following to database.py
# Create a session to handle updates.
Session = sessionmaker(bind=engine)
session = Session()
The final complete code for our database interface is shown below
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()
Now that our data model is defined, and our database created, we can go ahead and interface our Notes
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.
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.
NoteWindow
must have it's own associated instance of the Note
object.
Note
objects should be created when creating a new NoteWindow
.
NoteWindow
should sync it's initial state to a Note
if provided.
NoteWindow
should update the data in the Note
.
Note
should be synced to the database.
We can tackle these one by one.
First let's setup our NoteWindow
to accept, and store a reference to Note
objects if provided, or create a new one if not.
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
In this code we've imported the Note
object from our database.py
file. In the __init__
of our NoteWindow
we've added an optional parameter to receive a Note
object. If this is None
(or nothing provided) a new Note
will be created instead. The passed, or created note, is then stored on the NoteWindow
so we can use it later.
This Note
object is still not being loaded, updated, or persisted to the database. So let's implement that next. We add two methods, load()
and save()
to our NoteWindow
to handle the loading and saving of data.
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()
The load()
method takes the x
and y
position from the Note
object stored in self.note
and updates the NoteWindow
position and content to match. The save()
method takes the NoteWindow
position and content and sets that onto the Note
object. It then adds the note to the current database session and commits the changes
Each commit starts a new session. Adding the Note
to the session is indicating that we want it's changes persisted.
The delete()
method handles deletion of the current note. This involves 3 things:
Note
object to session.delete
to remove it from the database,
active_notewindows
(so the object will be tidied up)
.close()
to hide the window immediately.
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 .close()
to make it immediate.
We need to modify the close_btn.clicked
signal to point to our delete
method.
Next we've added a load()
call to the __init__
when a Note
object is passed. We also call .save()
for newly created notes to persist them immediately, so our delete handler will work before editing.
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 could 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 mouseReleased
method.
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.
That's all there is to it: when the mouse button is released, we save the current content and position by calling .save()
.
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.
There have been a lot of partial code changes in this section, so here is the complete current code.
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()
If you run the application at this point it will be persisting data to the database as you edit it.
If you want to look at the contents of the SQLite database I can recommend DB Browser for SQLite. It's open source & free.
The note data persisted to the SQLite database
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.
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 Note
objects and then iterate through this, creating new NoteWindow
instances (using our create_notewindow
function).
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()
First we've modified the create_notewindow
function to accept an (optional) Note
object which is passed through to the created NoteWindow
.
Using the session we query session.query(Note).all()
to get all the Note
objects. If there any, we iterate them creating them. If not, we create a single note with no associated Note
object (this will be created inside the NoteWindow
).
That's it! The full final code is shown below:
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()
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.
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.
Try and extend this example further, for example:
list
of hex colors and random.choice
to select a new color each time a note is created. Can you persist this in the database too?
NoteWindow
objects in active_notewindows
. You can show and hide windows in Qt using .show()
and .hide()
.
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!
In this third blog post of the Model/View Drag and Drop series (part 1 and part 2), 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.
Step 1
Initial state, the email is in the inbox
Step 2
Dragging the email onto the Customers folder
Step 3
Dropping the email
Step 4
The email is now in the customers folder
Example code can be found here for flat models and here for tree models.
☑ Call view->setDragDropMode(QAbstractItemView::DragOnly)
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.
☑ Call view->setDragDropOverwriteMode(...)
true
if moving should clear cells, false
if moving should remove rows.
Note that the default is true
for QTableView
and false
for QListView
and QTreeView
. In our example, we want to remove emails that have been moved elsewhere, so false
is correct.
☑ Call view->setDefaultDropAction(Qt::MoveAction)
so that the drag defaults to a move and not a copy, adjust as needed
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:
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;
☑ Reimplement flags()
to add Qt::ItemIsDragEnabled
in the case of a valid index
☑ Reimplement supportedDragActions()
to return Qt::MoveAction | Qt::CopyAction
or whichever you want to support (the default is CopyAction only).
☑ Reimplement mimeData()
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.
☑ Reimplement removeRows()
, it will be called after a successful drop with MoveAction
. An example implementation looks like this:
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;
}
☑ Call view->setDragDropMode(QAbstractItemView::DropOnly)
unless of course it supports dragging too. In our example, we can drop onto email folders but we cannot reorganize the folders, so DropOnly
is correct.
To implement dropping items into a model's existing items, you need to do the following:
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;
};
☑ Reimplement flags()
For a valid index (and only in that case), add Qt::ItemIsDropEnabled
. As you can see, you can also restrict drops to column 0, which can be more sensible when using QTreeView
(the user should drop onto the folder name, not onto the folder size).
☑ Reimplement supportedDropActions()
to return Qt::MoveAction | Qt::CopyAction
or whichever you want to support (the default is CopyAction only).
☑ Reimplement mimeTypes()
- the list should include the MIME type used by the drag model.
☑ Reimplement dropMimeData()
to deserialize the data and handle the drop.
This could mean calling setData()
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 removeRows()
on its model.
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
}
Example code:
☑ Call widget->setDragDropMode(QAbstractItemView::DragOnly)
or DragDrop
if it should support both
☑ Call widget->setDefaultDropAction(Qt::MoveAction)
so that the drag defaults to a move and not a copy, adjust as needed
☑ Reimplement Widget::mimeData()
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).
To serialize pointers in QDataStream, cast them to quintptr, see the example code for details.
☑ Call widget->setDragDropMode(QAbstractItemView::DropOnly)
or DragDrop
if it should support both
☑ Call widget->setDragDropOverwriteMode(true)
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.
☑ Reimplement Widget::mimeTypes()
and return the same name as the one used on the drag side's mimeData
☑ Reimplement Widget::dropMimeData()
(note that the signature is different between QListWidget
, QTableWidget
and QTreeWidget
) 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.
Make sure to do all of the following:
This is a case where proper model/view separation is actually much simpler.
While writing and testing these code examples, I improved the following things in Qt, in addition to those listed in the previous blog posts:
I hope you enjoyed this blog post series and learned a few things.
The post Model/View Drag and Drop in Qt - Part 3 appeared first on KDAB.
Standard support for Qt 5 will end in May. Time is running out to port your code!
Products from the STM32 family have been a popular target for embedded Qt applications for quite a long time. One […]
In the previous blog, you learned all about moving items within a single view, to reorder them.
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).
Moving a row between treeviews, step 1
Moving a row between treeviews, step 2
Moving a row between treeviews, step 3
Example code for flat models and example code for tree models.
To allow dragging items out of the view, make sure to do the following:
☑ Call view->setDragDropMode(QAbstractItemView::DragOnly)
(or DragDrop
if it should support both).
☑ Call view->setDragDropOverwriteMode(false)
so that QTableView
calls removeRows when moving rows, rather than just clearing their cells
☑ Call view->setDefaultDropAction(Qt::MoveAction)
so it's a move and not a copy
To implement dragging items out of a model, you need to implement the following:
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
};
More precisely, the check-list is the following:
☑ Reimplement flags()
to add Qt::ItemIsDragEnabled
in the case of a valid index
☑ Reimplement supportedDragActions()
to return Qt::MoveAction
☑ Reimplement mimeData()
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:
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;
}
s_mimeType
is the name of the type of data (make up a name, it usually starts with application/x-
)
☑ Reimplement removeRows()
, it will be called after a successful drop. For instance, if your data is in a vector called m_data
, the implementation would look like this:
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;
}
☑ Call view->setDragDropMode(QAbstractItemView::DragDrop)
(already done if both views should support dragging and dropping)
To implement dropping items into a model (between existing items), you need to implement the following:
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
};
☑ Reimplement supportedDropActions()
to return Qt::MoveAction
☑ Reimplement flags()
For a valid index, make sure Qt::ItemIsDropEnabled
is NOT set (except for tree models where we need to drop onto items in order to insert a first child).
For the invalid index, add Qt::ItemIsDropEnabled
, to allow dropping between items.
☑ Reimplement mimeTypes()
and return the name of the MIME type used by the mimeData()
function on the drag side.
☑ Reimplement dropMimeData()
to deserialize the data and insert new rows.
In the special case of in-process tree models, clone the dragged nodes.
In both cases, once you're done, return true, so that the drag side then deletes the dragged rows by calling removeRows()
on its model.
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
}
Example code can be found following this link.
On the "drag" side:
☑ Call widget->setDragDropMode(QAbstractItemView::DragOnly)
or DragDrop
if it should support both
☑ Call widget->setDefaultDropAction(Qt::MoveAction)
so the drag starts as a move right away
On the "drop" side:
☑ Call widget->setDragDropMode(QAbstractItemView::DropOnly)
or DragDrop
if it should support both
☑ Reimplement supportedDropActions()
to return only Qt::MoveAction
When using QTableWidget
, in addition to the common steps above you need to:
On the "drag" side:
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
for each item, to disable dropping onto items.
☑ Call widget->setDragDropOverwriteMode(false)
so that after a move the rows are removed rather than cleared
On the "drop" side:
☑ Call widget->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells (the default is false
for the other views anyway)
☑ Another problem is that the items created by a drop will automatically get the Qt::ItemIsDropEnabled
flag, which you don't want. To solve this, use widget->setItemPrototype()
with an item that has the right flags (see the example).
When using QTreeWidget
, you cannot disable dropping onto items (which creates a child of the item).
You could call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on your own items, but when QTreeWidget
creates new items upon a drop, you cannot prevent them from having the flag Qt::ItemIsDropEnabled
set. The prototype solution used above for QTableWidget
doesn't exist for QTreeWidget
.
This means, if you want to let the user build and reorganize an actual tree, you can use QTreeWidget
. But if you just want a flat multi-column list, then you should use QTreeView
(see previous section on model/view separation).
If the user should be able to choose between copying and moving items, follow the previous section and make the following changes.
On the "drag" side:
☑ Call view->setDefaultDropAction(...)
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.
☑ Reimplement supportedDragActions()
in the model to return Qt::MoveAction | Qt::CopyAction
On the "drop" side:
☑ Reimplement supportedDropActions()
in the model to return Qt::MoveAction | Qt::CopyAction
The good news is that there's nothing else to do.
On the "drag" side:
☑ Call widget->setDefaultDropAction(...)
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.
Until Qt 6.10 there was no setSupportedDragActions()
method in the item widget classes (that was QTBUG-87465, I implemented it for 6.10). Fortunately the default behavior is to use what supportedDropActions()
returns so if you just want move and copy in both, reimplementing supportedDropActions()
is enough.
On the "drop" side:
☑ Reimplement supportedDropActions()
in the item widget class to return Qt::MoveAction | Qt::CopyAction
The good news is that there's nothing else to do.
While writing and testing these code examples, I improved the following things in Qt:
In the next blog post of this series, you will learn how to move (or copy) onto existing items, rather than between them.
The post Model/View Drag and Drop in Qt - Part 2 appeared first on KDAB.
Qt’s 3D offering is changing, so we decided to look at different options for rendering 3D content in Qt.
Continue reading 3D Rendering Solutions in Qt – an Overview at basysKom GmbH.
I welcome you to another blog post. The last one discussed a form of communication between devices using Qt Bluetooth Low […]
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.
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.
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.
Moving a row in a tableview, step 1
Moving a row in a tableview, step 2
Moving a row in a tableview, step 3
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.
The code being discussed here is extracted from the example. That example features a flat model, while this example features a tree model. The checklist is the same for these two cases.
☑ Call view->setDragDropMode(QAbstractItemView::InternalMove)
to enable the mode where only moving within the same view is allowed
☑ When using QTableView
, call view->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells (the default is false
for the other views anyway)
Reorderable ListView
Reorderable TableView
For a model being used in QListView or QTableView, all you need is something like this:
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
};
The checklist for the changes you need to make in your model is therefore the following:
☑ Reimplement flags()
For a valid index, add Qt::ItemIsDragEnabled
and make sure Qt::ItemIsDropEnabled
is NOT set (except for tree models where we need to drop onto items in order to insert a first child). \
☑ Reimplement mimeTypes()
and make up a name for the mimetype (usually starting with application/x-
)
☑ Reimplement supportedDragActions()
to return Qt::MoveAction
☑ Reimplement supportedDropActions()
to return Qt::MoveAction
☑ Reimplement moveRows()
Note that this approach is only valid when using QListView
or, assuming Qt >= 6.8.0, QTableView
- see the following sections for details.
In a model that encapsulates a QVector
called m_data
, the implementation of moveRows
can look like this:
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;
}
Reorderable treeview
Reorderable treeview with a tree model
QTreeView does not (yet?) call moveRows
in the model, so you need to:
☑ Reimplement mimeData()
to encode row numbers for flat models, and node pointers for tree models
☑ Reimplement dropMimeData()
to implement the move and return false (meaning: all done)
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).
I implemented moving of rows in QTableView
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 mimeData()
and dropMimeData()
in your model, as per the previous section.
This concludes the section on how to implement a reorderable view using a separate model class.
The alternative to model/view separation is the use of the item widgets (QListWidget
, QTableWidget
or QTreeWidget
) which you populate directly by creating items.
Reorderable QListWidget
Reorderable QTableWidget
Reorderable QTreeWidget
Here's what you need to do to allow users to reorder those items.
Example code can be found following this link.
☑ Call listWidget->setDragDropMode(QAbstractItemView::InternalMove)
to enable the mode where only moving within the same view is allowed
For a QListWidget
, this is all you need. That was easy!
When using QTableWidget
:
☑ Call tableWidget->setDragDropMode(QAbstractItemView::InternalMove)
☑ Call tableWidget->setDragDropOverwriteMode(false)
so that it inserts rows instead of replacing cells
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on each item, to disable dropping onto items
Note: Before Qt 6.8.0, QTableWidget
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 QTableWidget
's internal model, so it's all fixed there. If you really need this feature in older versions of Qt, consider switching to QTableView
.
When using QTreeWidget
:
☑ Call tableWidget->setDragDropMode(QAbstractItemView::InternalMove)
☑ Call item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
on each item, to disable dropping onto items
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).
While writing and testing these code examples, I improved the following things in Qt 6.8:
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.
In the next blog post of this series, you will learn how to move (or even copy) items from one view to another.
The post Model/View Drag and Drop in Qt - Part 1 appeared first on KDAB.
The Qt Graphs is a powerful module of the Qt framework that enables developers to create interactive, both static and […]
Mozilla is the maker of the famous Firefox web browser and the birthplace of the likes of Rust and Servo (read more about Embedding the Servo Web Engine in Qt).
Firefox is a huge, multi-platform, multi-language project with 21 million lines of code back in 2020, according to their own blog post. Navigating in projects like those is always a challenge, especially at the cross-language boundaries and in platform-specific code.
To improve working with the Firefox code-base, Mozilla hosts an online code browser tailored for Firefox called Searchfox. Searchfox analyzes C++, JavaScript, various IDLs (interface definition languages), and Rust source code and makes them all browsable from a single interface with full-text search, semantic search, code navigation, test coverage report, and git blame support. It's the combination of a number of projects working together, both internal to Mozilla (like their Clang plugin for C++ analysis) and external (such as rust-analyzer maintained by Ferrous Systems).
It takes a whole repository in and separately indexes C++, Rust, JavaScript and now Java and Kotlin source code. All those analyses are then merged together across platforms, before running a cross-reference step and building the final index used by the web front-end available at searchfox.org.
Mozilla asked KDAB to help them with adding Java and Kotlin support to Searchfox in prevision of the merge of Firefox for Android into the main mozilla-central repository and enhance their C++ support with macro expansions. Let's dive into the details of those tasks.
Mozilla merged the Firefox for Android source code into the main mozilla-central repository that Searchfox indexes. To add support for that new Java and Kotlin code to Searchfox, we reused open-source tooling built by Sourcegraph around the SemanticDB and SCIP code indexing formats. (Many thanks to them!)
Sourcegraph's semanticdb-javac and semanticdb-kotlinc compiler plugins are integrated into Firefox's CI system to export SemanticDB artifacts. The Searchfox indexer fetches those SemanticDB files and turns them into a SCIP index, using scip-semanticdb. That SCIP index is then consumed by the existing Searchfox-internal scip-indexer tool.
In the process, a couple of upstream contributions were made to rust-analyzer (which also emits SCIP data) and scip-semanticdb.
A few examples of Searchfox at work:
If you want to dive into more details, see the feature request on Bugzilla, the implementation and further discussion on GitHub and the release announcement on the mozilla dev-platform mailing list.
GeckoView is an Android wrapper around Gecko, the Firefox web engine. It extensively uses cross-language calls between Java and C++.
Searchfox already had support for cross-language interfaces, thanks to its IDL support. We built on top of that to support direct cross-language calls between Java and C++.
First, we identified the different ways the C++ and Java code interact and call each other. There are three ways Java methods marked with the native
keyword call into C++:
org.mozilla.gecko.mozglue.GeckoLoader.nativeRun
from Java will call Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun
on the C++ side.
JNIEnv::RegisterNatives
function on the C++ side to point at another function.
@WrapForJNI
and native
annotations and generates a C++ class template meant to be used through the Curiously Recurring Template Pattern. This template provides an Init
static member function that does the right JNIEnv::RegisterNatives
calls to bind the Java methods to the implementing C++ class's member functions.
We also identified two ways the C++ code calls Java methods:
JNIEnv::Call…
functions.
@WrapForJNI
(without the native
keyword this time) and generates a C++ wrapper class and member functions with the right JNIEnv::Call…
calls.
Only the C++ side has the complete view of the bindings; so that's where we decided to extract the information from, by extending Mozilla's existing Clang plugin.
First, we defined custom C++ annotations bound_as
and binding_to
that the clang plugin transforms into the right format for the cross-reference analysis. This means we can manually set the binding information:
class __attribute__((annotate("binding_to", "jvm", "class", "S_jvm_sample/Jni#"))) CallingJavaFromCpp
{
__attribute__((annotate("binding_to", "jvm", "method", "S_jvm_sample/Jni#javaStaticMethod().")))
static void javaStaticMethod()
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "method", "S_jvm_sample/Jni#javaMethod().")))
void javaMethod()
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "getter", "S_jvm_sample/Jni#javaField.")))
int javaField()
{
// Wrapper code
return 0;
}
__attribute__((annotate("binding_to", "jvm", "setter", "S_jvm_sample/Jni#javaField.")))
void javaField(int)
{
// Wrapper code
}
__attribute__((annotate("binding_to", "jvm", "const", "S_jvm_sample/Jni#javaConst.")))
static constexpr int javaConst = 5;
};
class __attribute__((annotate("bound_as", "jvm", "class", "S_jvm_sample/Jni#"))) CallingCppFromJava
{
__attribute__((annotate("bound_as", "jvm", "method", "S_jvm_sample/Jni#nativeStaticMethod().")))
static void nativeStaticMethod()
{
// Real code
}
__attribute__((annotate("bound_as", "jvm", "method", "S_jvm_sample/Jni#nativeMethod().")))
void nativeMethod()
{
// Real code
}
};
(This example is, in fact, extracted from our test suite, jni.cpp vs Jni.java.)
Then, we wrote some heuristics that try and identify cases A1 (C functions named Java_…
), A3 and B2 (C++ code generated from @WrapForJNI
decorators) and automatically generate these annotations. Cases A2 and B1 (manually calling JNIEnv::RegisterNatives
or JNIEnv::Call…
functions) are rare enough in the Firefox code base and impossible to reliably recognize; so it was decided not to cover them at the time. Developers who wish to declare such bindings could manually annotate them.
After this point, we used Searchfox's existing analysis JSON format and mostly re-used what was already available from IDL support. When triggering the context menu for a binding wrapper or bound function, the definitions in both languages are made available, with “Go to” actions that jump over the generally irrelevant binding internals.
The search results also display both sides of the bridge, for instance:
If you want to dive into more details, see the feature request and detailed problem analysis on Bugzilla, the implementation and further discussion on GitHub, and the release announcement on the Mozilla dev-platform mailing list.
Aside from this Java/Kotlin-related work, we also added support for displaying and interacting with macro expansions. This was inspired by KDAB's own codebrowser.dev, but improves it to:
Per-platform expansions
Per-definition expansions
In-macro context menu
This work mainly happened in the Mozsearch Clang plugin to extract macro expansions during the pre-processing stage and index them with the rest of the top-level code.
Again, if you want more details, the feature request is available on Bugzilla and the implementation and further technical discussion is on GitHub.
Because of the many technologies it makes use of, from compiler plugins and code analyzers written in many languages, to a web front-end written using the usual HTML/CSS/JS, by way of custom tooling and scripts in Rust, Python and Bash, Searchfox is a small but complex and really interesting project to work on. KDAB successfully added Java/Kotlin code indexing, including analyzing their C++ bindings, and are starting to improve Searchfox's C++ support itself, first with fully-indexed macro expansions and next with improved templates support.
The post Improvements to Mozilla's Searchfox Code Browser appeared first on KDAB.
When building C++ code with CMake, it is very common to want to set some pre-processor defines in the CMake code.
For instance, we might want to set the project's version number in a single place, in CMake code like this:
project(MyApp VERSION 1.5)
This sets the CMake variable PROJECT_VERSION
to 1.5, which we can then use to pass -DMYAPP_VERSION_STRING=1.5
to the C++ compiler. The about dialog of the application can then use this to show the application version number, like this:
const QString aboutString = QStringLiteral("My App version: %1").arg(MYAPP_VERSION_STRING);
QMessageBox::information(this, "My App", aboutString);
Similarly, we might have a boolean CMake option like START_MAXIMIZED
, which the user compiling the software can set to ON or OFF:
option(START_MAXIMIZED "Show the mainwindow maximized" OFF)
If it's ON, you would pass -DSTART_MAXIMIZED
, otherwise nothing. The C++ code will then use #ifdef
. (We'll see that there's a better way.)
#ifdef START_MAXIMIZED
w.showMaximized();
#else
w.show();
#endif
A solution that many people use for this is the CMake function add_definitions
. It would look like this:
add_definitions(-DMYAPP_VERSION_STRING="${PROJECT_VERSION}")
if (START_MAXIMIZED)
add_definitions(-DSTART_MAXIMIZED)
endif()
Technically, this works but there are a number of issues.
First, add_definitions
is deprecated since CMake 3.12 and add_compile_definitions
should be used instead, which allows to remove the leading -D
.
More importantly, there's a major downside to this approach: changing the project version or the value of the boolean option will force CMake to rebuild every single .cpp file used in targets defined below these lines (including in subdirectories). This is because add_definitions
and add_compile_definitions
ask to pass -D
to all cpp files, instead of only those that need it. CMake doesn't know which ones need it, so it has to rebuild everything. On large real-world projects, this could take something like one hour, which is a major waste of time.
A first improvement we can do is to at least set the defines to all files in a single target (executable or library) instead of "all targets defined from now on". This can be done like this:
target_compile_definitions(myapp PRIVATE MYAPP_VERSION_STRING="${PROJECT_VERSION}")
if(START_MAXIMIZED)
target_compile_definitions(myapp PRIVATE START_MAXIMIZED)
endif()
We have narrowed the rebuilding effect a little bit, but are still rebuilding all cpp files in myapp, which could still take a long time.
There is a proper way to do this, such that only the files that use these defines will be rebuilt; we simply have to ask CMake to generate a header with #define
in it and include that header in the few cpp files that need it. Then, only those will be rebuilt when the generated header changes. This is very easy to do:
configure_file(myapp_config.h.in myapp_config.h)
We have to write the input file, myapp_config.h.in
, and CMake will generate the output file, myapp_config.h
, after expanding the values of CMake variables. Our input file would look like this:
#define MYAPP_VERSION_STRING "${PROJECT_VERSION}"
#cmakedefine01 START_MAXIMIZED
A good thing about generated headers is that you can read them if you want to make sure they contain the right settings. For instance, myapp_config.h
in your build directory might look like this:
#define MYAPP_VERSION_STRING "1.5"
#define START_MAXIMIZED 1
For larger use cases, we can even make this more modular by moving the version number to another input file, say myapp_version.h.in
, so that upgrading the version doesn't rebuild the file with the showMaximized()
code and changing the boolean option doesn't rebuild the about dialog.
If you try this and you hit a "file not found" error about the generated header, that's because the build directory (where headers get generated) is missing in the include path. You can solve this by adding set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
near the top of your CMakeLists.txt
file. This is part of the CMake settings that I recommend should always be set; you can make it part of your new project template and never have to think about it again.
There's just one thing left to explain: what's this #cmakedefine01
thing?
If your C++ code uses #ifdef
, you want to use #cmakedefine
, which either sets or doesn't set the define. But there's a major downside of doing that -- if you forget to include myapp_config.h
, you won't get a compile error; it will just always go to the #else
code path.
We want a solution that gives an error if the #include
is missing. The generated header should set the define to either 0 or 1 (but always set it), and the C++ code should use #if
. Then, you get a warning if the define hasn't been set and, because people tend to ignore warnings, I recommend that you upgrade it to an error by adding the compiler flag -Werror=undef
, with gcc or clang. Let me know if you are aware of an equivalent flag for MSVC.
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(myapp PRIVATE -Werror=undef)
endif()
And these are all the pieces we need. Never use add_definitions
or add_compile_definitions
again for things that are only used by a handful of files. Use configure_file
instead, and include the generated header. You'll save a lot of time compared to recompiling files unnecessarily.
I hope this tip was useful.
For more content on CMake, we curated a collection of resources about CMake with or without Qt. Check out the videos.
The post Setting C++ Defines with CMake appeared first on KDAB.
Early-Bird tickets are on sale for the KDAB Training Day 2025 until 2025-03-31 23:59
The KDAB Training Day 2025 will take place in Munich on May 8th, right after the Qt World Summit on May 6th-7th. Choose to buy a combo ticket here (for access to QtWS and Training Day) or here (for access to Training Day only).
Seats are limited, so don't wait too long if you want to participate in a specific course. Tickets include access to the selected training course, training material, lunch buffet, beverages, and coffee breaks. Note: The Training Day is held at Hotel NH Collection München Bavaria, located at the Munich Central Station (not the same location as Qt World Summit).
Why should you attend the KDAB Training Day?
With over 20 years of experience and a rich store of well-structured, constantly updated training material, KDAB offers hands-on, practical programming training in Qt/QML, Modern C++, 3D/OpenGL, Debugging & Profiling, and lately Rust - both for beginners as well as experienced developers.
All courses provided at the Training Day include central parts of the regular 3- to 4-day courses available as scheduled or customized on-site training. Choosing a compact, learning-rich one-day course, lets you experience the quality and effectiveness of KDAB’s usual training offerings.
Courses available at the KDAB Training Day 2025
In this training, we do a step-by-step walkthrough of how to build a QML-based embedded application from the ground up and discuss some challenges that are typically met along the way.
An important part of that journey is an investigation of where to put the boundaries between what you do in C++ and what you do in QML. We also look at some of the tools and building blocks we have available in QML that can help us achieve well-performing, well-structured, and well-maintainable applications.
This course is for
(Qt) developers looking to improve their understanding of how to construct maintainable and efficient larger-scale QML applications.
Prerequisite
Some real-world experience working on QML applications as well as a basic understanding of Qt and C++.
In this training, we start with a recap of fundamentals:
Afterward, we explore several more advanced techniques, often widely deployed within Qt's QML modules, such as Qt Quick.
This will answer questions such as:
This course is for
Qt/QML developers who are familiar with the QML APIs of QtQuick and related modules and who have wondered how these are implemented and want to use similar techniques in their project-specific APIs.
Prerequisite
Some real-world experience working on QML applications as well as a basic understanding of Qt and C++.
Modern C++ emphasizes safer, more efficient, and maintainable code through higher-level abstractions that reduce error-prone manual work.
This training will explore key paradigms shaping recent C++ evolution, starting with value semantics in class design, which enhances code safety, local reasoning, and thread safety. We will examine modern C++ tools for creating value-oriented types, including move semantics, smart pointers, and other library enablers.
Next, we will look at expressive, type and value-based error handling.
Finally, we'll cover range-based programming, which enables clean, declarative code and unlocks new patterns through lazy, composable transformations.
This course is for
C++ developers who wish to improve the quality of their code, in particular those who wish to write future-proof APIs.
Prerequisites
Prior professional experience in C++. Experience with the latest C++ standards (C++20/23/26) is a plus. We will use several examples inspired by Qt APIs, so Qt knowledge is also a plus (but this is not going to be a Qt training).
In this step-by-step course, we start with a Qt/C++ application and add Rust code to it piece by piece. To achieve this, we will cover:
We discuss when to use Rust compared to C++ to make the best of both languages and how to use them together effectively to make Qt applications safer and easier to maintain.
This course is for
Qt/C++ Developers with an interest in Rust who want to learn how to use Rust in their existing applications.
Prerequisites
Basic Qt/C++ knowledge, as well as basic Rust knowledge, is required. A working Qt installation with CMake and a working Rust installation is needed. We will provide material before the training day that participants should use to check their setup before the training.
In this training, we look into all the new developments in QML over the last few years and how they lead to more expressive, performant, and maintainable code.
This includes:
- The qt_add_qml_module CMake API
- Declarative type registration
- The different QML compilers
- New language and library features
- New developments in Qt Quick Controls
- Usage of tools like qmllint, QML Language Server, and qmlformat
The focus will be on gradually modernizing existing codebases with new tools and practices.
This course is for
Developers who learned QML back in the days of Qt 5 and want to catch up with recent developments in QML and modernize their knowledge as well as codebases.
Prerequities
Some real-world experience with QML and a desire to learn about modern best practices.
Qt has long offered ways of using low-level 3d libraries such as OpenGL to do custom rendering. Whether at the Window, the Widget, or Quick Item level, the underlying rendering system can be accessed in ways that make it safe to integrate such 3rd party renderers. This remains true in the Qt 6 timeline, although the underlying rendering system has changed and OpenGL has been replaced by RHI.
In this course, we look at how the graphic stack is structured in Qt 6 and how third-party renderers can be integrated on the various platforms supported by Qt.
We then focus on the specific case of integrating Vulkan-based renderers. Vulkan is the successor to OpenGL; it's much more powerful but harder to learn. To facilitate the initial use of Vulkan, we introduce KDGpu, a library that encapsulates Vulkan while preserving the underlying concepts of pipeline objects, buffer handling, synchronization, etc.
This course is for
This course targets developers wanting to understand the recent state of the graphics stack in Qt, discover the fundamental principles of modern graphics API, and integrate their custom renderers in their applications.
Prerequisite
Prior knowledge of Qt will be required. A basic understanding of 3d graphics would be beneficial.
Video from KDAB Training Day 2023 held in Berlin
The post KDAB Training Day - May 8th, 2025 appeared first on KDAB.
We just released CXX-Qt version 0.7!
CXX-Qt is a set of Rust crates for creating bidirectional Rust ⇄ C++ bindings with Qt. It supports integrating Rust into C++ applications using CMake or building Rust applications with Cargo. CXX-Qt provides tools for implementing QObject subclasses in Rust that can be used from C++, QML, and JavaScript.
For 0.7, we have stabilized the cxx-qt
bridge macro API and there have been many internal refactors to ensure that we have a consistent baseline to support going forward. We encourage developers to reach out if they find any unclear areas or missing features, to help us ensure a roadmap for them, as this may be the final time we can adapt the API. In the next releases, we're looking towards stabilizing the cxx-qt-build
and getting the cxx-qt-lib
APIs ready for 1.0.
Check out the new release through the usual channels:
CXX-Qt 0.7 reaches a major milestone by stabilizing the bridge macro that is at the heart of CXX-Qt.
You can now depend on your CXX-Qt bridges to remain compatible with future CXX-Qt versions.
As we're still pre-1.0, we may still introduce very minor breaking changes to fix critical bugs in the edge-cases of the API, but the vast majority of bridges should remain compatible with future versions.
This stabilization is also explicitly limited to the bridge API itself. Breaking changes may still occur in e.g. cxx-qt-lib
, cxx-qt-build
, and cxx-qt-cmake
. We plan to stabilize those crates in the next releases.
The handling of names internally has been refactored to ensure consistency across all usages. During this process, implicit automatic case conversion has been removed, so cxx_name
and rust_name
are now used to specify differing Rust and C++ names. Since the automatic case conversion is useful, it can be explicitly enabled using per extern block attributes auto_cxx_name
and auto_rust_name
, while still complimenting CXX. For more details on how these attributes can be used, visit the attributes page in the CXX-Qt book.
// with 0.6 implicit automatic case conversion
#[cxx_qt::bridge]
mod ffi {
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number) // myNumber in C++
type MyObject = super::MyObjectRust;
fn my_method(self: &MyObject); // myMethod in C++
}
}
// with 0.7 cxx_name / rust_name
#[cxx_qt::bridge]
mod ffi {
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number, cxx_name = "myNumber")
type MyObject = super::MyObjectRust;
#[cxx_name = "myMethod"]
fn my_method(self: &MyObject);
}
}
// with 0.7 auto_cxx_name / auto_rust_name
#[cxx_qt::bridge]
mod ffi {
#[auto_cxx_name] // <-- enables automatic cxx_name generation within the `extern "RustQt"` block
unsafe extern "RustQt" {
#[qobject]
#[qproperty(i32, my_number) // myNumber in C++
type MyObject = super::MyObjectRust;
fn my_method(self: &MyObject); // myMethod in C++
}
}
In previous releases, the output filename of generated C++ files used the cxx_file_stem
attribute of the CXX-Qt bridge. This has been changed to use the filename of the Rust source file including the directory structure.
Previously, the code below would generate a C++ header path of my_file.cxxqt.h.
After the changes, the cxx_file_stem
must be removed and the generated C++ header path changes to crate-name/src/my_bridge.cxxqt.h.
This follows a similar pattern to CXX.
// crate-name/src/my_bridge.rs
// with 0.6 a file stem was specified
#[cxx_qt::bridge(cxx_file_stem = "my_file")]
mod ffi {
...
}
// with 0.7 the file path is used
#[cxx_qt::bridge]
mod ffi {
...
}
The internals of the build system have changed so that dependencies are automatically detected and configured by cxx-qt-build
, libraries can pass build information to cxx-qt-build
, and a CXX-Qt CMake module is now available providing convenience wrappers around corrosion. This means that the cxx-qt-lib-headers
crate has been removed and only cxx-qt-lib
is required. With these changes, there is now no need for the -header
crates that existed before. Previously, some features were enabled by default in cxx-qt-lib.
Now these are all opt-in. We have provided full
and qt_full
as convenience to enable all features; however, we would recommend opting in to the specific features you need.
We hope to improve the API of cxx-qt-build
in the next cycle to match the internal changes and become more modular.
CXX-Qt can now be successfully built for WASM, with documented steps available in the book and CI builds for WASM to ensure continued support.
Locking generation on the C++ side for all methods has been removed, which simplifies generation and improves performance. Using queue
from cxx_qt::CxxQtThread
is still safe, as it provides locking, but it is up to the developer to avoid incorrect multi-threading in C++ code (as in the CXX
crate). Note that Qt generally works well here, with the signal/slot mechanism working safely across threads.
As with most releases, there are more Qt types wrapped in cxx-qt-lib
and various other changes are detailed in the CHANGELOG.
Make sure to subscribe to the KDAB YouTube channel, where we'll post more videos on CXX-Qt in the coming weeks.
The post CXX-Qt 0.7 Release appeared first on KDAB.