user2727167
user2727167

Reputation: 440

Custom object referencing in QML Python

I am trying to extend QML with qmlRegisterType. I have a python class - PyQml.py, main.qml files and boilerplate code for it. Problem is I cant reference(import) PyQml object in main.qml file, I get an error --> QML module not found (PyQml) .

So far I have determined QML_IMPORT_PATH variable paths. Since I was hopelles, I created folder named PyQml with PyQml.py in it in one of the paths but still no success. Furthermore, I cant find *.pro file in Qt Creator project. I suppose I should add another path to my custom object to it.

PyQml.py

class PyQml(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Initialise the value of the properties.
        self._name = ''
        self._shoeSize = 0

    # Define the getter of the 'name' property.  The C++ type of the
    # property is QString which Python will convert to and from a string.
    @Property('str')
    def name(self):
        return self._name

    # Define the setter of the 'name' property.
    @name.setter
    def name(self, name):
        self._name = name

    # Define the getter of the 'shoeSize' property.  The C++ type and
    # Python type of the property is int.
    @Property(int)
    def shoeSize(self):
        return self._shoeSize

    # Define the setter of the 'shoeSize' property.
    @shoeSize.setter
    def shoeSize(self, shoeSize):
        self._shoeSize = shoeSize

qmlengine.py

import sys
import sqlite3
from PySide2 import QtCore, QtGui, QtWidgets, QtQuick
from PySide2.QtCore import Qt,QUrl
from PySide2.QtQml import QQmlApplicationEngine,qmlRegisterType
from PySide2.QtGui import QGuiApplication
from ViewModel import PyQml

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    print(QQmlApplicationEngine.importPathList(engine))
    ctx = engine.rootContext()
    ctx.setContextProperty("qmlapp", engine) #the string can be anything
    qmlRegisterType(PyQml.PyQml, 'People', 1, 0, 'Person');
    engine.load('Documents/ctmd/Qml/main.qml')
    win = engine.rootObjects()[0]
    win.show()
    sys.exit(app.exec_())

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.4
import PyQml 1.0 ---- Error QML module not found ( PyQml)
ApplicationWindow {

    menuBar: MenuBar {
        Menu {
            title: "File"
            MenuItem { text: "Open..." }
            MenuItem { text: "Close" }
        }

        Menu {
            title: "Edit"
            MenuItem { text: "Cut" }
            MenuItem { text: "Copy" }
            MenuItem { text: "Paste" }
        }
    }
    Grid {
        columns: 3
        spacing: 2
        Rectangle { color: "red"; width: 50; height: 50 }
        Rectangle { color: "green"; width: 20; height: 50 }
        Rectangle { color: "blue"; width: 50; height: 20 }
        Rectangle { color: "cyan"; width: 50; height: 50 }
        Rectangle { color: "magenta"; width: 10; height: 10 }
    }

}

Project folder tree

Qml
   -main.qml
PyQml.py
qmlengine.py

PyQml is just a sample class, at the end of day I want to pass custom data I calculated in python ( x,y coordinates ) and plot that data with qml

Upvotes: 3

Views: 2611

Answers (2)

user2727167
user2727167

Reputation: 440

I found out if I simply ignore this error

in qml window everything works fine. I even tried reinstalling IDE, error stays. Thank you for clarification.

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 243955

TL;DR; There is no solution to eliminate the error message since it is a limitation of QtCreator with Qt for Python. But QtCreator is only an IDE so it does not need to work there, instead you just have to run the code from the console/CMD:

python /path/of/script.py

You have the following errors:

  • When you register a QObject with qmlRegisterType "People" is the name of the package in QML and "Person" is the name of the component, so you should not use PyQml in the import unless you change the registry parameters.

  • QtCreator/QtQuickDesigner still has limitations with Python support, so the message: "Qml module not found (FooPackage)" is a sample of this. As the developers of Qt for Python/PySide2 point out in future versions they will add new features but it is currently not possible.

  • I see that the structure that you indicate in your publication does not match your project since for example you indicate that the main.qml is in the QML folder that is at the same level as qmlengine.py but in the load you use "Documents/ctmd/Qml/main.qml".

  • PySide2 has limitations with the Property decorator and its setter since it is not recognized by QML, instead it uses the extensive declaration: name_of_property = Property(type_of_property, fget = getter_of_property, ...)

  • If a Qt Property with a setter then it must have an associated signal.

Considering the above, the solution is:

├── PyQml.py
├── Qml
│   └── main.qml
└── qmlengine.py

qmlengine.py

import os
import sys

from PySide2.QtCore import QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType

import PyQml

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    qmlRegisterType(PyQml.PyQml, "People", 1, 0, "Person")
    engine = QQmlApplicationEngine()

    ctx = engine.rootContext()
    ctx.setContextProperty("qmlapp", engine)  # the string can be anything

    current_dir = os.path.dirname(os.path.realpath(__file__))
    filename = os.path.join(current_dir, "Qml/main.qml")

    engine.load(QUrl.fromLocalFile(filename))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

PyQml.py

from PySide2.QtCore import Property, Signal, QObject


class PyQml(QObject):
    nameChanged = Signal(str)
    shoeSizeChanged = Signal(int)

    def __init__(self, parent=None):
        super().__init__(parent)
        # Initialise the value of the properties.
        self._name = ""
        self._shoeSize = 0

    # Define the getter of the 'name' property.  The C++ type of the
    # property is QString which Python will convert to and from a string.
    def get_name(self):
        return self._name

    # Define the setter of the 'name' property.
    def set_name(self, name):
        if self._name != name:
            self._name = name
            self.nameChanged.emit(name)

    name = Property(str, fget=get_name, fset=set_name, notify=nameChanged)

    # Define the getter of the 'shoeSize' property.  The C++ type and
    # Python type of the property is int.
    def get_shoeSize(self):
        return self._shoeSize

    # Define the setter of the 'shoeSize' property.
    def set_shoeSize(self, shoeSize):
        if self._shoeSize != shoeSize:
            self._shoeSize = shoeSize
            self.shoeSizeChanged.emit(shoeSize)

    shoeSize = Property(
        int, fget=get_shoeSize, fset=set_shoeSize, notify=shoeSizeChanged
    )

main.qml

import QtQuick 2.0
import QtQuick.Controls 1.4
import People 1.0

ApplicationWindow {
    visible: true

    Person{
        name: "foo"
        shoeSize: 10
    }

    menuBar: MenuBar {
        Menu {
            title: "File"
            MenuItem { text: "Open..." }
            MenuItem { text: "Close" }
        }

        Menu {
            title: "Edit"
            MenuItem { text: "Cut" }
            MenuItem { text: "Copy" }
            MenuItem { text: "Paste" }
        }
    }
    Grid {
        columns: 3
        spacing: 2
        Rectangle { color: "red"; width: 50; height: 50 }
        Rectangle { color: "green"; width: 20; height: 50 }
        Rectangle { color: "blue"; width: 50; height: 20 }
        Rectangle { color: "cyan"; width: 50; height: 50 }
        Rectangle { color: "magenta"; width: 10; height: 10 }
    }
}

Upvotes: 3

Related Questions