Python dynamically instantiate QML Components - python

I want to use Python to dynamically add custom components onto my view.qml, but I am not sure about my method because I can't see the Button.qml component in the resulting window. Ideally, I hope to be able to instantiate several rows of buttons into the ColumnLayout. By the way, Button.qml custom quick example/demo button whose's source code I have included too below. It is not QtQuick Button.qml from the PySide6 library
I thought I could just call functions from the view.qml but apparently not? I have seen another method that involves using a separate Javascript file, but I would like to avoid doing that if possible.
Main.py
import os
from pathlib import Path
import sys
from PySide6.QtCore import QUrl, QObject
from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView
class CreateWidgets(QObject):
def instantiate_widgets(self, root, widgetsNeeded):
#for i in widgetsNeeded:
root.doSomething
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView);
qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml')
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
root = view.rootObject()
widgetCreator = CreateWidgets()
widgetCreator.instantiate_widgets(root, 6)
view.show()
res = app.exec()
# Deleting the view before it goes out of scope is required to make sure all child QML instances
# are destroyed in the correct order.
del view
sys.exit(res)
view.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item{
function doSomething(){
var component = Qt.createComponent("Button.qml");
if (component.status === Component.Ready) {
var button = component.createObject(colLayout);
button.color = "red";
}
console.log("Button created");
}
ColumnLayout{
id: colLayout
Rectangle {
id: page
width: 500; height: 200
color: "lightgray"
}
}
}
Button.qml
import QtQuick 2.0
Rectangle { width: 80; height: 50; color: "red"; anchors.fill: parent}
(Code reference for questions in comments section)
Main.py
import os
import random
import sys
from pathlib import Path
from PySide6.QtCore import Property, QUrl, QObject, Qt
from PySide6.QtGui import QColor, QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView
ColorRole = Qt.UserRole
BorderRole = Qt.UserRole
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self._model.setItemRoleNames({Qt.DisplayRole: b"display", ColorRole: b"custom", BorderRole: b"custom2"})
#Property(QObject, constant=True)
def model(self):
return self._model
def add_button(self, text, color, bColor):
item = QStandardItem(text)
item.setData(color, ColorRole)
item.setData(bColor, BorderRole)
self._model.appendRow(item)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
manager = Manager()
view = QQuickView()
view.rootContext().setContextProperty("manager", manager)
view.setResizeMode(QQuickView.SizeRootObjectToView)
qml_file = os.fspath(Path(__file__).resolve().parent / "view.qml")
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
for i in range(6):
color = QColor(*random.sample(range(0, 255), 3))
border = QColor(*random.sample(range(0, 255), 3))
manager.add_button(f"button-{i}", color, border)
view.show()
res = app.exec()
sys.exit(res)
View.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item {
ColumnLayout {
id: colLayout
anchors.fill: parent
Repeater{
model: manager.model
Button{
color: model.custom
text: model.display
border.color: model.custom2
}
}
}
}
Button.qml
import QtQuick 2.0
Rectangle {
id: root
property alias text: txt.text
width: 80
height: 50
color: "red"
border.color: "black"
Text{
id: txt
anchors.centerIn: parent
}
}

The idea is that Python (or C++) provide the information to QML to create the items for example using a model and a Repeater.
On the other hand, if an item is going to be a child of a ColumnLayout then it should not use anchors since there will be conflicts since they both handle the geometry of the item.
Considering the above I have added more elements such as variable text, variable color, etc. to demonstrate the logic.
import os
import random
import sys
from pathlib import Path
from PySide6.QtCore import Property, QUrl, QObject, Qt
from PySide6.QtGui import QColor, QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView
ColorRole = Qt.UserRole
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self._model.setItemRoleNames({Qt.DisplayRole: b"display", ColorRole: b"custom"})
#Property(QObject, constant=True)
def model(self):
return self._model
def add_button(self, text, color):
item = QStandardItem(text)
item.setData(color, ColorRole)
self._model.appendRow(item)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
manager = Manager()
view = QQuickView()
view.rootContext().setContextProperty("manager", manager)
view.setResizeMode(QQuickView.SizeRootObjectToView)
qml_file = os.fspath(Path(__file__).resolve().parent / "view.qml")
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
sys.exit(-1)
for i in range(6):
color = QColor(*random.sample(range(0, 255), 3))
manager.add_button(f"button-{i}", color)
view.show()
res = app.exec()
sys.exit(res)
import QtQuick 2.0
import QtQuick.Layouts 1.12
Item {
ColumnLayout {
id: colLayout
anchors.fill: parent
Repeater{
model: manager.model
Button{
color: model.custom
text: model.display
}
}
}
}
import QtQuick 2.0
Rectangle {
id: root
property alias text: txt.text
width: 80
height: 50
color: "red"
Text{
id: txt
anchors.centerIn: parent
}
}
Update:
Each role must have a different numerical value since otherwise Qt cannot identify it, in your case you could change:
BorderRole = Qt.UserRole + 1

Related

Qml, Reference error <signalName> is not defined when signal defined

I have a very simple qml+python app to play and test signal/slot communication.
All works fine so far, but when I run the app, a ReferenceError is reported on the QML side.
However, all works fine, it is so simple code:
QML:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
width: 1000
height: 480
visible: true
title: qsTr("Hello World")
Connections {
target: signalEmitter
ignoreUnknownSignals : true
function onSignal() {
console.log("HELLO QML")
}
}
Rectangle{
height: 100
width: 100
color: "green"
MouseArea {
anchors.fill: parent
onClicked: {
signalEmitter.sayHello()
}
}
}
Rectangle{
anchors.fill: parent
color: "transparent"
border.color: "black"
}
}
Python:
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import sys
class PythonSignalEmitter(QObject):
signal = Signal(str)
#Slot()
def sayHello(self):
print("HELLO PYTHON")
self.signal.emit("HELLO")
if __name__ == '__main__':
app = QGuiApplication([])
engine = QQmlApplicationEngine()
engine.load("main.qml")
signal_emitter = PythonSignalEmitter()
engine.rootContext().setContextProperty("signalEmitter", signal_emitter)
sys.exit(app.exec())
Why do I keep getting the error:
ReferenceError: signalEmitter is not defined
on line 12 in qml file. (app runs and signal/slot works as expected)
You should add the context properties before loading any qml:
if __name__ == '__main__':
app = QGuiApplication([])
engine = QQmlApplicationEngine()
signal_emitter = PythonSignalEmitter()
engine.rootContext().setContextProperty("signalEmitter", signal_emitter)
engine.load("main.qml") # new place
sys.exit(app.exec())

How to pass table-model data to qml?

I see some code write in c++ style, and I try to write it in python style.
I write a table model in python file, and then pass it to qml file.
But when I run my main.py file, The windows show nothing.
And my program don't show any error and I don't know what's wrong here?
Can someone know the reason?
main.py
import os
from pathlib import Path
import sys
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QAbstractTableModel, Qt
class TableModel(QAbstractTableModel):
def __init__(self):
super().__init__()
def rowCount(self, parent):
return 10
def columnCount(self, parent) -> int:
return 10
def data(self, index, role: int):
if index.isValid() and role == Qt.DisplayRole:
return f"{index.row()},{index.column()}"
return None
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty('myModel', TableModel())
engine.load(os.fspath(Path(__file__).resolve().parent / "table.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
table.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 320
height: 320
visible: true
TableView {
anchors.fill: parent
rowSpacing: 5
columnSpacing: 5
model: myModel
delegate: myDele
}
Component {
id: myDele
Rectangle {
implicitHeight: 50
implicitWidth: 50
width: 50
height: 50
color: "#abc"
Text {
anchors.centerIn: parent
text: display
}
}
}
}
Result
The problem is caused by memory management, it is better to assign to a variable than to pass the object directly to a method since it should be assumed that the object will be used but its memory will not be managed, unless the docs indicate.
In this case you must save the object in a variable and then use it:
myModel = TableModel()
engine.rootContext().setContextProperty('myModel', myModel)

How show a glowing label in QMainWindow

I'm trying to figure out how to use RectangularGlow to create a glowing label with PyQt5, but I don't know about QML, and I can't figure out how to do. Here is what I have so far:
example.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.12
Label {
text: "Identifiant"
width: 400
height: 200
Rectangle {
id: background
anchors.fill: parent
color: "black"
}
RectangularGlow {
id: effect
anchors.fill: rect
glowRadius: 10
spread: 0.2
color: "white"
cornerRadius: rect.radius + glowRadius
}
Rectangle {
id: rect
color: "black"
anchors.centerIn: parent
width: Math.round(parent.width / 1.5)
height: Math.round(parent.height / 2)
radius: 25
}
}
And the python code that uses it:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import *
if __name__ == "__main__":
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load('example.qml')
label = engine.rootObjects()[0]
win = QMainWindow()
win.setCentralWidget(label)
win.show()
sys.exit(app.exec_())
But I get this error:
Traceback (most recent call last):
File "test2.py", line 16, in <module>
win.setCentralWidget(label)
TypeError: setCentralWidget(self, QWidget): argument 1 has unexpected type 'QObject'
If you want to use qml items in Qt Widgets then you must use a QQuickWidget (or QQuickView + QWidget::createWindowContainer()):
import os.path
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtQuickWidgets import QQuickWidget
CURRENT_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)))
if __name__ == "__main__":
app = QApplication(sys.argv)
filename = os.path.join(CURRENT_DIR, "example.qml")
qml_widget = QQuickWidget()
qml_widget.setResizeMode(QQuickWidget.SizeRootObjectToView)
qml_widget.setSource(QUrl.fromLocalFile(filename))
win = QMainWindow()
win.setCentralWidget(qml_widget)
win.show()
sys.exit(app.exec_())
you can have glow effect with :
import QtGraphicalEffects 1.12
also this is a label with glow effect :
Rectangle {
width: 50
clip: true
height: 20
color: "#592e2e2e"
layer.enabled: true
layer.effect: Glow {
samples: 14
color: "#592e2e2e"
transparentBorder: true
}
Text {
clip: true
anchors.fill: parent
wrapMode: Text.WrapAnywhere
horizontalAlignment: Text.AlignHCenter
}
}

How can I send parent to a new ApplicationWindow

I want to recover the position and the size of the master "main.qml". But I do not know how to declare the parent of the new window.
I have no problem if I open the window directly from the window main.qml in javascript but through python I do not see how.
I think I have to use "self.win" but how declare it ?
Thanks for yours responses.
test.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
import sys
class Main2(QObject):
def __init__(self, engine, what_send_for_send_the_parent):
QObject.__init__(self)
"""
How can I say to the new windows who is the parent ?
"""
context = engine.rootContext()
context.setContextProperty("py_Page2", self)
engine.load('test2.qml')
self.win = engine.rootObjects()[0]
class Main(QObject):
def __init__(self, engine):
QObject.__init__(self)
self.context = engine.rootContext()
self.property = self.context.setContextProperty("py_Page", self)
self.load = engine.load('test.qml')
self.win = engine.rootObjects()[0]
print("Context", self.context) # <PyQt5.QtQml.QQmlContext object at 0xb65e6f30>
print("Property", self.property)# None
print("Load", self.property) # None
print("Win", self.win) # <PyQt5.QtGui.QWindow object at 0xb65e6f80>
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
main = Main(engine)
main2 = Main2(engine, "???")
engine.quit.connect(app.quit)
sys.exit(app.exec_())
test.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
id: "main"
visible: true
width: 200; height: 240;
Text {text: qsTr("main")}
}
test2.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
id: "main2"
visible: true
width: 200; height: 240;
x: main.x
y: main.y
Text {text: qsTr("main2")}
}
I think I have found :
class Main2(QObject):
def __init__(self, engine, master):
QObject.__init__(self)
context = engine.rootContext()
context.setContextProperty("main_x", master.win.property("x"))
context.setContextProperty("main_y", master.win.property("y"))
engine.load('test2.qml')
...
main = Main(engine)
main2 = Main2(engine, main)
...
And in the file qml
ApplicationWindow {
id: "main2"
visible: true
width: 200; height: 240;
x: main_x + 20
y: main_y + 120
Text {text: qsTr("main2")}
}
I can recover the value like that. Is this correct? Is there a more conventional way?
Although the solution works in this case it can fail in more real cases where each QML can load many components since the QML load is asynchronous but your procedure is synchronous.
The solution is to create a QObject and export it to QML using setContextProperty() so that it is accessible from all the QMLs that are loaded through the QQmlApplicationEngine. That QObject must have a property that is a mirror of the property you want to obtain.
main.py
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QPoint, QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
class Manager(QObject):
positionChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._position = QPoint()
#pyqtProperty(QPoint, notify=positionChanged)
def position(self):
return self._position
#position.setter
def position(self, p):
if self._position != p:
self._position = p
self.positionChanged.emit()
if __name__ == "__main__":
import os
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
manager = Manager()
engine.rootContext().setContextProperty("manager", manager)
current_dir = os.path.dirname(os.path.realpath(__file__))
engine.load(QUrl.fromLocalFile(os.path.join("test.qml")))
engine.load(QUrl.fromLocalFile(os.path.join("test2.qml")))
engine.quit.connect(app.quit)
sys.exit(app.exec_())
test.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
id: root
visible: true
width: 200
height: 240
Text {
text: qsTr("main")
}
Component.onCompleted: manager.position = Qt.point(root.x, root.y)
}
test2.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 200
height: 240
x: manager.position.x
y: manager.position.x
Text {
text: qsTr("main2")
}
}

QML: Setting "source" property for image causes it to disappear

What I'm trying to do is update the source of an image with QML in PyQt5. When I use element.setProperty("source", "./testImage.png") to change the image I get the following error message.
QML Image: Protocol "" is unknown
Any idea on how to fix this?
I've looked at other ways to interact with QML elements and if possible I'd like to stick with changing the image through Python code rather then through just QML.
main.py
from PyQt5.QtWidgets import *
from PyQt5.QtQml import *
from PyQt5.QtCore import *
from PyQt5.QtQuick import *
from PyQt5 import *
import sys
import resource_rc
class MainWindow(QQmlApplicationEngine):
def __init__(self):
super().__init__()
self.load("main.qml")
self.rootContext().setContextProperty("MainWindow", self)
self.window = self.rootObjects()[0]
self.cardLeft = self.window.findChild(QObject, "cardLeft")
#pyqtSlot()
def changeImage(self):
self.cardLeft.setProperty("source", "./images/3_of_clubs.png")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
main.qml
import QtQml 2.11
import QtQuick 2.3
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.11
ApplicationWindow{
id: screen
width: 720
height: 490
visible: true
Material.theme: Material.Dark
Item {
id: cards
width: parent.width
height: parent.height - toolBar.height
anchors {
top: parent.top;
bottom: toolBar.top;
bottomMargin: 10
}
RowLayout {
anchors.fill: parent
spacing: 20
Image {
objectName: "cardLeft"
id: cardLeft
Layout.fillWidth: true
Layout.maximumHeight: 250
Layout.minimumHeight: parent.height
Layout.margins: 20
source: "./testImage.png"
fillMode: Image.PreserveAspectFit
}
}
}
Button {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.fillHeight: true
Layout.maximumHeight: 100
Layout.maximumWidth: 300
Layout.margins: 20
text: qsTr("Change Image")
highlighted: true
Material.accent: Material.color(Material.LightGreen)
onClicked: MainWindow.changeImage()
}
}
You have to pass a QUrl for it you must use QUrl::fromLocalFile():
import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
# import resource_rc
dir_path = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QtQml.QQmlApplicationEngine):
def __init__(self):
super().__init__()
self.load(QtCore.QUrl.fromLocalFile(os.path.join(dir_path, "main.qml")))
self.rootContext().setContextProperty("MainWindow", self)
if self.rootObjects():
self.window = self.rootObjects()[0]
self.cardLeft = self.window.findChild(QtCore.QObject, "cardLeft")
#QtCore.pyqtSlot()
def changeImage(self):
if self.cardLeft:
url = QtCore.QUrl.fromLocalFile(os.path.join(dir_path, "images/3_of_clubs.png"))
self.cardLeft.setProperty("source", url)
if __name__ == '__main__':
app = QtGui.QGuiApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

Categories

Resources