Thursday, February 3, 2022

Python GUIs: PyQt5 vs PyQt6: What are the differences, and is it time to upgrade?

If you are already developing Python GUI apps with PyQt5, you might be asking yourself whether it's time to upgrade to PyQt6 and use the latest version of the Qt library. In this article we'll look at the main differences between PyQt5 and PyQt6, benefits of upgrading and problems you might encounter when doing so.

Background

Qt is a GUI framework written in the C++ programming language created by Trolltech, now developed by The Qt Company. There are two Python bindings: PySide and PyQt. The former is developed in-house by The Qt Company while PyQt is developed independently by Riverbank Computing Ltd. The first version of PyQt6 was released on January 4th, 2021, just one month after the release of Qt6 itself.

For more information on the differences between the latest versions of the two bindings, take a look at PyQt6 vs PySide6.

Upgrading from PyQt5 to PyQt6

The upgrade path from PyQt5 to PyQt6 is fairly straightforward, with one main gotcha. For some applications, just renaming the imports from PyQt5 to PyQt6 will be enough to convert your application to work with the new library. For most however, you will need to account for changes in both PyQt and Qt itself.

Let’s get acquainted with a few differences between the two versions to know how to write code that works seamlessly with both. After reading this, you should be able to take any PyQt5 example online and convert it to work with PyQt6.

Enums

The change mostly likely to impact your projects is the removal of the short-form names for enum members. In PyQt6 all enum members must be named using their fully qualified names. This applies to all enums and flags, including those in the QtCore.Qt namespace. For example the Qt.Checked flag in PyQt6 becomes Qt.CheckState.Checked.

python
widget = QCheckBox("This is a checkbox")
widget.setCheckState(Qt.Checked)
python
widget = QCheckBox("This is a checkbox")
widget.setCheckState(Qt.CheckState.Checked)

There are too many updated values to mention them all here. But if you're converting a codebase you can usually just search online for the short-form and the longer form will be in the results.

The good news is that the change is backwards compatible: the fully-qualified names also work in PyQt5. So you can make the changes in your PyQt5 code before beginning the upgrade progress.

.exec() or .exec_()?

The .exec() method in Qt starts the event loop of your QApplication or dialog boxes. In Python 2.7, exec was a keyword, meaning that it could not be used as a variable name, a function name, or a method name. In earlier versions of PyQt the method was renamed as .exec_() – adding a trailing underscore – to avoid a conflict.

Python 3.0 removed the exec keyword, freeing up the name to be used. And since PyQt6 targets only Python 3.x versions, the underscored names have been removed. These were previously deprecated in PyQt5, and the .exec() method will work there too.

No more QResources

PyQt6 has removed support for Qt's Resource Framework. For packaging data files with your applications, you can use PyInstaller's data file support.

Differences in Qt6

In addition to the changes above there are many other minor changes that reflect underlying differences in Qt6 vs. Qt5 and aren't unique to PyQt itself.

QAction moved

In Qt6 the QAction class, which is used for creation toolbars and menus, has been moved from the QtWidgets to the QtGui module.

python
from PyQt5.QtWidgets import QAction
python
from PyQt6.QtGui import QAction

This may seem strange, but the move makes sense since actions can also be used in QML (non-widgets) applications.

High DPI Scaling

The high DPI (dots per inch) scaling attributes Qt.AA_EnableHighDpiScaling, Qt.AA_DisableHighDpiScaling and Qt.AA_UseHighDpiPixmaps have been deprecated because high DPI is enabled by default in PyQt6 and can’t be disabled.

QMouseEvent

QMouseEvent.pos() and QMouseEvent.globalPos() methods returning a QPoint object as well as QMouseEvent.x() and QMouseEvent.y() returning an int object have been deprecated – use QMouseEvent.position() and QMouseEvent.globalPosition() returning a QPointF object instead, so like QMouseEvent.position().x() and QMouseEvent.position().y().

Qt.MidButton has been renamed to Qt.MiddleButton

Platform specific

Finally, platform-specific methods in the QtWin and QtMac modules have been deprecated, in favor of using the native calls instead. In PyQt applications the only likely consequence of this will be the setCurrentProcessExplicitAppUserModelID call to set an application ID, for taskbar icon grouping on Windows.

python
try:
    # Include in try/except block if you're also targeting Mac/Linux
    from PyQt5.QtWinExtras import QtWin
    myappid = 'com.learnpyqt.examples.helloworld'
    QtWin.setCurrentProcessExplicitAppUserModelID(myappid)
except ImportError:
    pass
python
try:
    # Include in try/except block if you're also targeting Mac/Linux
    from ctypes import windll  # Only exists on Windows.
    myappid = 'mycompany.myproduct.subproduct.version'
    windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except ImportError:
    pass

Miscellaneous

  • QDesktopWidget has been removed – use QScreen instead, which can be retrieved using QWidget.screen(), QGuiApplication.primaryScreen(), or QGuiApplication.screens().
  • .width() of QFontMetrics has been renamed to .horizontalAdvance().
  • .get() of QOpenGLVersionFunctionsFactory() has been recommended to be used instead of .versionFunctions() of QOpenGLContext() when obtaining any functions of the Open GL library.
  • QRegularExpression has replaced QRegExp.
  • QWidget.mapToGlobal() and QWidget.mapFromGlobal() now accept and return a QPointF object.

Missing modules

This isn’t a concern anymore, but when Qt6 was new, not all of the Qt modules had been ported yet, and so were not available in PyQt. If you needed any of these modules, the upgrade to PyQt6 was not previously workable. Fast forward to Qt 6.2 and PyQt 6.2, the good news is that all of those missing modules are now back, so you can upgrade without problems.

Is it time to upgrade?

Whether or not it's time to upgrade depends on your project. If you're starting out learning PyQt (or GUI programming in general), you may prefer to stick with PyQt5 for the time being as there are far more examples available online. While the differences are relatively minor, anything not working is confusing when you are learning something new. Anything you learn with PyQt5 will carry over to PyQt6, so you can switch once you're a bit more confident.

If you're starting a new project however, or are reasonably familiar with PyQt, I'd recommend jumping into PyQt6 now.

If you want to get started with PyQt6, the PyQt6 book is available with all code examples updated for this latest edition of PyQt.

PyQt Backwards compatibility

If you're developing software that's targeting both PyQt5 and PyQt6 you can use conditional imports to import the classes from whichever module is loaded.

python
try:
    from PyQt6 import QtWidgets, QtGui, QtCore # ...
except ImportError:
    from PyQt5 import QtWidgets, QtGui, QtCore # ...

If you add these imports to a file in the root of your project named qt.py. You can then, in your own code files import use from qt import QtWidgets and the available library will be imported automatically.

If you use the fully-qualified names for enums and exec() then many applications previously written in PyQt5 will work as-is. However, importing in this way won't work around any of the differences between Qt5 and Qt6 mentioned above. For that, we recommend using the QtPy library.

Universal compatibility

If you need to support all Python Qt libraries (PyQt5, PyQt6, PySide2, PySide6) or are dependent on features which have changed between versions of Qt, then you should consider using QtPy. This package is a small abstraction layer around all versions of the Qt libraries, which allows you to use them interchangeably (as far as possible) in your applications.

If you're developing Python libraries or applications that need to be portable across different versions it is definitely worth a look.

Conclusion

As we've discovered, there are no major differences between PyQt5 and PyQt6. The changes that are there can be easily worked around. If you are new to Python GUI programming with Qt you may find it easier to start with PyQt5 still, but for any new project I'd suggest starting with PyQt6.

For more, see the complete PySide6 tutorial.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...