Category Archives: ui

PySide6 6.2 to 6.3 Upgrade

If you use PySide6 and have developed against PySide6 version 6.2.x, and you upgrade to 6.3 or later, you may find the following failures.

Module Import Error

ModuleNotFoundError: No module named 'PySide6.QtCore'

The Qt documentation hints at it, so if you performed just a pip install to the new version, then it is likely you’re issue. Instead uninstall and re-install.

In case you are updating your 6.2.x install, make sure the PySide6_Essentials and PySide6_Addons wheels are getting installed, otherwise, uninstall and install PySide6 to get the new structure.

From: https://www.qt.io/blog/qt-for-python-details-on-the-new-6.3-release

So in your virtual environment, or main environment

# ensure all previous installs uninstalled
python3 -m pip uninstall PySide6
python3 -m pip uninstall PySide6-Essentials
python3 -m pip uninstall PySide6-AddOns
# and finally re-install
python3 -m pip install PySide6

Could not load the Qt platform plugin “xcb”

After upgrading PySide6 to 6.5 on a Ubuntu 20.04, attempting to load an application functional with PySide6 6.2.4 you may find it fails on a missing plugin as follows.

qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: xcb, vnc, wayland, minimalegl, vkkhrdisplay, offscreen, wayland-egl, minimal, linuxfb, eglfs.

If you enable debug by adding the environment variable …

QT_DEBUG_PLUGINS=1

… you may see later versions of PySide6 are looking for -cursor xcb library also.

libxcb-cursor.so.0: cannot open shared object file: No such file or directory

This can be installed using apt install as follows

sudo apt install libxcb-cursor0

Page by Page Angularjs to Angular 2+ Migration

If you have worked with network capable embedded devices, you will have worked with web interfaces to control them as it is a convenient UI interface.

If you have an interface written in AngularJS, then you may be considering updating to Angular as a natural progression.

There are numerous examples on line of controlled and gradual ways to do this, and they seem very valid, but most of them require a consistent architecture on the AngularJS side.

If your main aim is to brute replace routes/pages one by one. i.e. Create a new Angular page to replace the AngularJS one, then caressing AngularJS into Angular ‘style’ is probably not good value for money.

Instead, here is a summary of some sites found which together will allow you to

  1. upgrade your entire AngularJS site into a parent Angular site.
  2. route/re-direct to the base page of your AngularJS site (and effectively hand over the router)
  3. Install a event dispatcher form AngularJS to Angular for information that the new pages need from the old ones.

The first two steps are detailed very nicely and with good detail in this reference: https://medium.com/lapis/running-angularjs-1-6-in-angular-5-side-by-side-d2ed771e2a8f

Doing that will get you an Angular project with your original AngularJS running entirely inside it.

Step 3 is the ‘tunnel’ between the two, and involves a rudimentary dispatcher from AngularJS to Angular.

Remembering that we are deprecating AngularJS, so something simple means we can get to the end result faster.

On the Angular side, in a component.ts of your choosing add.

import { Component, HostListener } from '@angular/core';
---snip---

@Component({
  selector: 'your-component',
  templateUrl: './your.component.html',
  styleUrls: ['./your.component.css']
})
export class YourComponent {

  constructor( ) { }

  @HostListener('window:message', ['$event']) sncListener(data: any) {
    console.log("Your message has arrived, decode the data and act on it");
    if (data.detail.your_msg_code === 'user_deleted') {
       // etc
    }
  }

---snip---
}

The HostListener reference is https://angular.io/api/core/HostListener

The relevant piece here is the ‘window:message’ where window is a system string, as shown in the reference above, and the message is what the AngularJS dispatcher (see further down) has in its CustomEvent.

On the AngularJS side, you need to dispatch the event, as follows.

angular.module('Main', ['ngTouch'])

        .controller('MainController', ['$window', '$scope', '$q', '$http', '$location', '$interval') {
        
---snip---

        $scope.someFunction = function () {
        
          // data here is some variable/object
          yourDispatcher({'your_msg_code': data});

---snip---
        }

        function yourDispatcher(data) {
          // message and detail are part of the custom event so need to be just that.
          var event = new CustomEvent('message', {
	          'detail' : data
	        });
          window.dispatchEvent(event);
        }

---snip---

        
        }

The use of CustomEvent and dispatchEvent was gleaned from here https://stackoverflow.com/questions/41050573/when-angularjs-controller-meets-postmessage-events

and a reference is https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent and https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent

Finally now you have the means to create new pages in Angular and replace the old ones one by one.

PySide6 Snippets

These snippets may apply to PySide2 also but are untested there.

Plain Text to Rich Text Conversion

Handling special characters in Qt Rich Text Widget.

If you are using a rich text widget, then any special characters, like say the less than sign <, if written to the text object will cause your output to be broken. Qt provides a function for these cases: convertFromPlainText

from PySide6.QtGui import Qt
...
    def my_method(plain_text)
        myQmlModel.rich_text_property = Qt.convertFromPlainText(plain_text)

Carriage Returns and Line Feeds in Rich Text do not work with WordWrap

In addition to the convertFromPlainText method, you can not have line feeds in your text, so you need to find and convert those to <br/>. If you do not, WordWrap option will not work correctly and your lines will extend past your widget width. At least in PySide 6.2.3.

Reference: QtGui — Qt for Python


Qt ® is a registered trademark of The Qt Company Ltd. and its subsidiaries.

Python ® is a registered trademark of the Python Software Foundation.

Qt5 to Qt6 Qml/Pyside 2 to 6 Porting Adventures

Here are a few things noticed during a port of a Pyside2 project and associated Qml to Pyside6 (6.2.1).

Injection of parameters into signal handlers is deprecated.

If you hit the following warning, it is formatting quirk now being enforced by Qt 6.

Injection of parameters into signal handlers is deprecated. Use JavaScript functions with form al parameters instead.

In our case it came about in this snippet of Qt5/Qml code on the Main Window’s onClosing handling to avoid closing on a certain condition. As the documentations quotes, you are allowed to set closing.accepted to true or false: Window QML Type | Qt Quick 6.2.1

ApplicationWindow {
    id: application
    onClosing: {
        ...
        if (condition) {
            close.accepted = false
            userDialog.open()
        } else {
            close.accepted = true
        }
    }

The fix is fortunately simple through the addition of the function(close). Refer for more details: Signal and Handler Event System | Qt QML 6.3.0

ApplicationWindow {
    id: application
    onClosing: function(close) {
        ...
        if (condition) {
            close.accepted = false
            userDialog.open()
        } else {
            close.accepted = true
        }
    }

Qml ComboBox indicator is null

Leaving the indicator element as the “default” one by not providing it in Qml in Qt5 (5.12 at least) “worked” in that a sub component could reference its with indicator.width. However porting the Qml to PySide6 resulted in a null dereference.

The contentItem’s rightPadding below references control.indicator.width, which threw the null dereference in PySide6 but not PySide2.

import QtQuick.Controls 2.12
ComboBox {
    id: control
    ...
    contentItem: Text {
        leftPadding: 0
        rightPadding: control.indicator.width + control.spacing
        ...
    }
    delegate: ItemDelegate {
        width: control.width
        ...
    }
    background: Rectangle {
        border.width: 1
        ...
    }
}

Simply adding a dummy Canvas worked around this problem, but naturally that is a bad work around.

import QtQuick.Controls 2.12
ComboBox {
    id: control
    ...
    all the above content
    ...
    indicator: Canvas {
    }
}

It seems that this issue was specific to the 2.12 import, changing the import to leave out the version selection or using 2.5, which was the installed version for PySide6, resolved the null de-refence.

import QtQuick.Controls 2.5

QtQuick.Dialogs 1.2 not installed

The modal dialog import in the main window resulted in an error

module “QtQuick.Dialogs” version 1.2 is not installed

Removing the 1.2 version removes the error, but introduces a new Dialog which results in the next error with the dialog being opened in the Top Left of the parent.

import QtQuick.Dialogs

Qml Dialog is centered Top Left of window

QtQuick.Dialogs 1.2 seemed to center the Modal Dialog to your window. In moving to 2.x version, it inherits from Popup which has x,y coordinates, which default to top left. Alternatively you can use anchors to center the dialog.

anchors.centerIn: Overlay.overlay

Refer popup documentation for use of anchoring or x,y positioning: Popup QML Type | Qt Quick Controls 6.3.0

Qt 6 Qml seems not to have a TreeView

A small set back for our project was what seems to be a lack of the TreeView from QtQuick.Controls 1.4, which was dropped at least initially in version 6. It seems it can be obtained from the Qt Marketplace, but the license is GPLv3 or proprietary, which makes one concerned to use it, if your own project is one of the permissive free software licenses, which many QML projects seem to be.

In our particular case an alternate implementation using ListView was adopted to avoid licensing complications.


Qt ® is a registered trademark of The Qt Company Ltd. and its subsidiaries.

Python ® is a registered trademark of the Python Software Foundation.

QML Listview With Custom Classes

When it comes to user interfaces Qt® software is good at it, and we love QtQuick and the QML language.

If you have developed UIs after the mobile boom (post 2005), you will have developed List Views. Every platform has them.

There are a fair view examples for Qt software List Views, both for PySide2/PyQt and C++, which point to using QAbstractListModel.

Many of them though point you at list views which show multiple string values. For example a list view of Users with first name and last name. The examples show how to extracts each of these strings in the QAbstractListModel using the Roles in the model.

Instead it would be nice to use a ‘variant’ to obtain a reference to your own class, which will have properties and methods which you can reference directly from your QML Qt software.

In Python this is reasonable straight forward because everything is a variant and the integration works ‘naturally’ as it were. So returning your class via the data() method of the QAbstractListModel works ‘out of the box’.

For C++ classes it is not as intuitive, primarily because to expose your class members and properties to QML, you need a QObject, as detailed in the guidelines provided in the Qt software documentation on integrating C++ with QML language. Take special note of the Q_OBJECT, Q_PROPERTY and Q_INVOKABLE macros.

Unfortunately, to return a custom class as a QVariant in the QAbstractListModel, we need it registered using meta data, which states:

Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.

from Detailed Description in qMetaType documentation.

Which a QObject does not fulfil as it does not have a copy constructor. So, we are between a rock and a hard place.

QAbstractListView is not the only container class we can use in a list view, but it is arguably the correct one. You could use say QList<Object *>, but then adding and removing items will not be correctly reflected.

The solution in C++ is to ensure that we return a pointer via the QVariant.

Lets look at an example. Firstly, we create our class, and we provide two strings store internally as mText and mCommand, and a submit method. mText is exposed via a property we will call name, and we expose the submit method using a Q_INVOKABLE macro. To provide an example of multiple ways to expose your members and methods, we also provide a public slot alternate_submit, as they can be referenced from QML language directly. Which you will use will be a subjective choice.

#define YOURCLASS_H

#include <QObject>
#include <QMetaType>

class YourClass: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ Text WRITE setText)
public:
    YourClass();
    YourClass(const QString& txt, const QString& command);
    QString Text(void) const;
    QString Command(void) const;
    Q_INVOKABLE void submit(void);
signals:

public slots:
    void alternate_submit(void);
private:
    QString mText;
    QString mCommand;
};

#endif // YOURCLASS_H

Next, we create our abstract list. Although we do not need to, we will create two roles, one called text, the other item, which will return the pointer to our whole class, rather than just a string. The key here is the static_cast on returning our item.

QVariant YourListModel::data(const QModelIndex &index, int role) const
{
   if (!index.isValid())
       return QVariant();
   if ( role == ETEXT)
   {
       return QVariant::fromValue(mDatas[index.row()]->Text());
   }
   else if (role == EITEM)
   {
       return QVariant::fromValue(static_cast<QObject *> mDatas[index.row()]));
   }
   return QVariant();
}

With that in place, your QML language can now reference your mText, in two ways, via model.text as provided by the the abstract list ETEXT role, or model.item.name as provided by the property of the EITEM role. Useful, practically no, but as a demonstration, yes. The other duplicate we added was the submit() and alternate_submit() methods. These can both be used to call a function from your QML language. Lets exemplify with a QML language snippet.

import QtQuick 2.0
import QtQuick.Controls 2.0

Item {
    width:parent.width
    height: 40
    clip: true
    Button {
        anchors.fill: parent
        onClicked: model.item.alternate_submit()  // or model.item.submit() or item.submit()
        text: model.item.name  // or model.text or item.name
    }
}

A number of alternatives are given for obtaining the properties. This is because model can be implicit to the QML file, so you can drop it. Adding it is arguably an easier read.

Errors you may hit

Undefined Reference in qmetatype.h

If you hit an undefined reference in the qmetatype.h, QMetaTypeFunctionHelper, as follows

...include\QtCore\qmetatype.h:766: error: undefined reference to `YourClass::YourClass()'

Then you have forgotten your default constructor. Referring to the documentation (which of course we read very carefully).

Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.

from Detailed Description in qmetatype documentation.

In YourClass.ccp simply add

YourClass::YourClass()
{}

TypeError: Property ‘submit’ of object QVariant(YourClass) is not a function

To be able to execute a function in a C++ class from the QML language you need to inherit your class from QObject, assign it the QOBJECT macro. Also the ‘function’ needs to be assigned the Q_INVOKABLE macro if it is a normal C++ method, or be a public slot.

In the YourClass example above, submit() and alternate_submit() can both be used within the QML language.

error: use of deleted function ‘QObject::QObject(const QObject&)’

error: use of deleted function ‘QObject::QObject(const QObject&)’

This will occur if you are trying to return a QVariant of a class inheriting a QObject. This can not be done as QObjects do not have a copy constructor. Instead cast and return the pointer to your object.

return QVariant::fromValue(static_cast<QObject *> mDatas[index.row()]));

depends on non-NOTIFYable properties

QQmlExpression: Expression qrc:/ui/YourClass.qml:10:15 depends on non-NOTIFYable properties:

This simply means you have not added a NOTIFY method for the QML language binding system to use. Refer The Property System | Qt Core 5.15.5, and in their example it is the priorityChanged method.


Qt is a registered trademark of The Qt Company Ltd. and its subsidiaries.

“Python” is a registered trademark of the Python Software Foundation.