Qt version 6.5 was released in April 2023, and now the Python API, PySide has been updated to map to the new Qt libraries. Try to install PySide2 using pip, and you’ll run into a road block. It’s been deprecated and the wheels are gone. Luckily, getting up and running fast with PySide6 is really straightforward.
Major Differences
Importing
Using PySide6 in your code is generally straightforward. You can simply replace the call to PySide2 with PySide6 in your import header.
Replace:
from PySide2.QtWidgets import QWidget
With…
from PySide6.QtWidgets import QWidget
QAction
This class has moved from QtWidgets
to QtGui
. Makes sense, because it’s not visibly presented.
from PySide6.QtQui import QAction
High DPI Scaling
One problem which could occur with PySide2 was the relative scaling of applications on devices with different DPI resolutions. This has been solved in PySide6 as high DPI is enabled by default. So you no longer have to engage boilerplate such as:
Qt.AA_EnableHighDpiScaling
Qt.AA_UseHighDpiPixmaps
QMouseEvent
To get the position of a mouse click, we now use the following code:
QMouseEvent.pos() # deprecated: returned a tuple of ints
QMouseEvent.position() # new: returns a QPointF object
QMouseEvent.globalPos() # deprecated: return a tuple of ints
QMouseEvent.globalPosition() # new: returns a QPointF object
# Qt.MidButton has been renamed to Qt.MiddleButton
QLayout
QLayout
objects no longer have a setMargin()
method.
Use setContentsMargins()
instead supply either a QMargins()
object or int
for top
, left
, right
, bottom
parameters.
Snake Casing & Widget Properties
Bringing PySide inline with PEP8 standards, snake-casing has now been introduced in the method naming. But you have to import snake_case
from the feature library. You can also set properties directly by invoking true_property
.
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QApplication
from __feature__ import snake_case, true_property
if __name__ == "__main__":
app = QApplication()
widget = QWidget()
widget.set_layout(QVBoxLayout())
label = QLabel()
widget.layout().add_widget(label)
label.text = "Hello" # as opposed to label.setText("Hello")
widget.show()
app.exec()
Backwards Compatibility
One way to work with PySide6 is to address the classes using the base libraries. These are common to both PySide2 and PySide6 and you can import them using the following boiler-plate block:
try:
from PySide6 import QtWidgets, QtGui, QtCore
except ImportError:
from PySide2 import QtWidgets, QtGui, QtCore
Example Widget
So here’s an example widget just to show off some of the subtle differences in the style of code we can now use in PySide6 widgets.
from PySide6.QtCore import Signal
from typing import Sequence
from functools import partial
from __feature__ import true_property, snake_case
from core.enums import Alignment
class RadioButtonWidget(QWidget):
clicked = Signal(int)
def __init__(self, labels: Sequence[str], active_id: int = 0, alignment: Alignment = Alignment.vertical,
margin: int = 10, spacing: int = 2):
"""
Self-contained widget featuring a set of radio buttons
:param labels:
:param active_id:
:param alignment:
:param margin:
:param spacing:
"""
super(RadioButtonWidget, self).__init__()
self.window_title = self.__class__.__name__
self.labels = labels
self.radio_buttons = []
layout: QLayout = QHBoxLayout() if alignment is Alignment.horizontal else QVBoxLayout()
layout.contents_margins.fset = (margin, margin, margin, margin)
layout.spacing = spacing
self.set_layout(layout)
button_group = QButtonGroup(self)
for idx, button in enumerate(labels):
button = QRadioButton(button)
layout.add_widget(button)
button_group.add_button(button)
button.clicked.connect(partial(self.radio_button_clicked, idx))
self.radio_buttons.append(button)
self.radio_buttons[active_id].checked = True
def radio_button_clicked(self, value):
self.clicked.emit(value)
@property
def active_button(self) -> int:
return next(i for i, value in enumerate(self.radio_buttons) if value.isChecked())
@property
def active_text(self) -> int:
return self.labels[self.active_button]
if __name__ == '__main__':
from PySide6.QtWidgets import QApplication
app = QApplication()
widget = RadioButtonWidget(['Tom', 'Dick', 'Harry'], alignment=Alignment.horizontal)
widget.show()
app.exec()
Things To Note
- Margins:
layout.contents_margins.fset = (margin, margin, margin, margin)
set_layout
can’t currently be set usinglayout = layout
- I used a custom enum to set the alignment.
Conclusions
My feeling about using true_property
and snake_case
so far is that it can be inconsistent. And once it is invoked, you are setting yourself a task to update each script when you are updating your widget library. There are specific Python environments which have yet to update their current working versions of PySide to PySide6, so updating the code using these methods make it hard to get things working. I might avoid that for a while, but I like the idea.
The deprecation of PySide2 means we tech artists now face a transition period of failing ui’s, just as we saw during the transition between PySide and PySide2. But I don’t see any major pains.
Addendum: Emergency PySide2
If you really need to install PySide2, while deprecated, the wheels are still available on the PySide2 website: https://pypi.org/project/PySide2/#files
- Download a distribution.
- You must be using Python 3.10 or prior, there is not a wheel for Python 3.11
- Use
pip install <distribution_path>
to install the PySide2 module.