I try to use QQuickImageProvider send QImage to QML, everything work well in c++ Qt5.9.2,but I try similar code with PyQt5(5.9.2), QML just says error:ImageProvider supports Image type but has not implemented requestImage(),but in fact,I implemented the requestImage(),here my code:
main.py:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtQml import *
from PyQt5.QtQuick import *
class MyImageProvider(QQuickImageProvider):
def __init__(self):
super(MyImageProvider, self).__init__(QQuickImageProvider.Image)
def requestImage(self, p_str, size):
img = QImage(300, 300, QImage.Format_RGBA8888)
img.fill(Qt.red)
return img, img.size()
app = QGuiApplication([])
viewer = QQuickView()
viewer.engine().addImageProvider("myprovider", MyImageProvider())
viewer.setResizeMode(QQuickView.SizeRootObjectToView)
viewer.setSource(QUrl("example.qml"))
viewer.show()
app.exec()
example.qml:
import QtQuick 2.7
Item {
id: root
width: 800
height: 600
Image{
// width: 300
// height: 300
source: "image://myprovider/test.png"
}
}
Perhaps requestImage() has different parameters and return values in python and c++,I'm sure that's right in format. refer to some examples, http://nullege.com/codes/search/PyQt5.QtQuick.QQuickImageProvider, I don't know what's wrong with me.
According to what I'm reviewing the problem is QQuickView or QQmlEngine, these classes are obsolete.
I recommend you use QQmlApplicationEngine:
main.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtQml import *
from PyQt5.QtQuick import *
class MyImageProvider(QQuickImageProvider):
def __init__(self):
super(MyImageProvider, self).__init__(QQuickImageProvider.Image)
def requestImage(self, p_str, size):
img = QImage(300, 300, QImage.Format_RGBA8888)
img.fill(Qt.red)
return img, img.size()
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.addImageProvider("myprovider", MyImageProvider())
engine.load(QUrl.fromLocalFile("example.qml"))
if len(engine.rootObjects()) == -1:
sys.exit(-1)
sys.exit(app.exec_())
example.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window{
visible: true
width: 640
height: 480
Image{
anchors.fill : parent
source: "image://myprovider/test.png"
}
}
I had the very same problem. It seems it was because the provider instance got lost after passed onto addImageProvider(). I resolved this issue like following.
app = QGuiApplication([])
viewer = QQuickView()
provider = MyImageProvider() # keep this instance during your app running
viewer.engine().addImageProvider("myprovider", provider)
viewer.setResizeMode(QQuickView.SizeRootObjectToView)
viewer.setSource(QUrl("example.qml"))
viewer.show()
app.exec()
Related
I have a QQuickPaintedItem in PyQt5, but the "paint" method is never being called.
I see this related question: Paint method of QQuickPaintedItem not called
In that question, the reason that paint is never being called is because the custom object didn't have a defined width/height. That's not the issue in my case, and yet the QQuickPaintedItem is still never calling "Paint". Here is a minimal example:
import sys
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
from PyQt5.QtGui import QPainter, QColor, QBrush
from PyQt5.QtQuick import QQuickPaintedItem
class Board(QQuickPaintedItem):
def __init__(self, parent):
super().__init__()
def paint(self, painter: QPainter):
painter.setBrush(QBrush(QColor("red")))
painter.drawRect(0, 0, 50, 50,)
print("I'm being drawn")
def componentComplete(self) -> None:
print(f"Done! My size is {self.width()}, {self.height()}, and my coords are {self.x()}, {self.y()}")
print(self.isVisible())
self.update()
if __name__ == '__main__':
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
ctx = engine.rootContext()
qmlRegisterType(Board, 'Board', 1, 0, 'Board')
engine.load("app.qml")
app.exec()
App.qml
import QtQuick 2.6
import QtQuick.Controls 2.15
import Board 1.0
ApplicationWindow {
id: window
width: 800
height: 900
visible: true
Board {
id: board
anchors.centerIn: parent
x: 0
y: 0
width: 700
height: 700
}
}
Also using a timer to change the width/height dynamically does not make it redraw either, and the "update" method which is being called in "componentCompleted" should trigger a redraw, right?
I'm trying to display text message on screen, when barcode is detected via OpenCV, from within long running background thread, but nothing that I've tried worked so far.
I'm confused by slight differences between PySide and PyQt and most examples deal with user interactions.
Could someone give me a simple example if possible of how to achieve that?
Much appreciated.
import os, sys, cv2, threading, time
from multiprocessing import Process
from PySide2.QtCore import QObject, Signal, Property, QUrl, QTimer, QDateTime
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
class TextManager(QObject):
textChanged = Signal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._text = ""
def get_text(self):
return self._text
def set_text(self, value):
if self._text == value:
return
self._text = value
self.textChanged.emit()
class CamWorker(QObject):
def scanner(self):
camera = cv2.VideoCapture(0)
def decodeCam(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
barcodes = pyzbar.decode(gray)
if detected:
changeQMLProperty
return image
while True:
# Read current frame
try:
ret, frame = camera.read()
im=decodeCam(frame)
except ValueError:
print("error")
def run():
app = QtGui.QGuiApplication(sys.argv)
manager = TextManager()
directory = os.path.dirname(os.path.abspath(__file__))
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('qml/main.qml')
engine.rootContext().setContextProperty
if not engine.rootObjects():
sys.exit(-1)
engine.rootObjects()[0].setProperty('manager', manager)
worker = CamWorker()
threading.Thread(target=worker.scanner, daemon=True).start()
return app.exec_()
if __name__ == "__main__":
sys.exit(run())
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
id: testing
visible: true
width: 640
height: 480
Text{
anchors.fill: parent
text: manager.text
}
}
You can use QML types for getting video from camera here is a simple example:
File main.py
import sys
from pathlib import Path
from PySide6.QtCore import QObject, Slot, Signal, QRunnable, QThreadPool
from PySide6.QtGui import QGuiApplication, QImage
from PySide6.QtQml import QQmlApplicationEngine, QmlElement
import cv2
import numpy as np
from pyzbar import pyzbar
import random
QML_IMPORT_NAME = "com.myapp.components"
QML_IMPORT_MAJOR_VERSION = 1
class Worker(QRunnable):
def __init__(self, emiter: Signal, image: QImage):
super().__init__(None)
self.image = image
self.emiter = emiter
def qimage_to_array(self, image: QImage) -> np.ndarray:
"""Converts a QImage into an opencv MAT format"""
image = image.convertToFormat(QImage.Format.Format_RGBA8888)
width = image.width()
height = image.height()
ptr = image.constBits()
return np.array(ptr).reshape(height, width, 4)
def run(self):
arr = self.qimage_to_array(self.image)
gray = cv2.cvtColor(arr, cv2.COLOR_BGR2GRAY)
barcodes = pyzbar.decode(gray)
# logic here.
# returning now random boolean
self.emiter.emit(random.choice([True, False]))
#QmlElement
class Cv2Capture(QObject):
imageAnalayized = Signal(bool)
#Slot(int, QImage)
def receive(self,req_id, image: QImage):
worker = Worker(self.imageAnalayized, image)
QThreadPool.globalInstance().start(worker)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
qml_file = Path(__file__).parent / "main.qml"
engine.load(qml_file)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
File main.qml
import QtQuick
import QtQuick.Controls
import QtMultimedia
import com.myapp.components
import QtQuick.Controls.Material
ApplicationWindow {
id: mainFrame
Material.theme: Material.Dark
width: 640
height: 480
visible: true
title: qsTr("Cam Test")
Cv2Capture {
id: bridge
onImageAnalayized: function(res){console.log(res)}
}
Rectangle {id: rect
width: 640
height: 400
MediaDevices {
id: mediaDevices
}
CaptureSession {
imageCapture: ImageCapture {
id: capture
onImageCaptured: function(req_id, preview){bridge.receive(req_id, preview)}
}
camera: Camera {
id: camera
}
videoOutput: output
}
VideoOutput {
id: output
anchors.fill: parent
}
Button {
id: startCamButton
text: "Start Cam"
anchors.top: output.bottom
anchors.left: output.left
onClicked: {
camera.start()
camImage.opacity = 0
}
}
Button {
id: takePicButton
text: "take pic"
anchors.top: output.bottom
anchors.left: startCamButton.right
onClicked: {
capture.capture()
camImage.opacity = 1
}
}
Image {
id: camImage
anchors.fill: parent
source: capture.preview
}
}
}
This example uses PySide6 which has better support for QML than PySide2.
I'm confused by slight differences between PySide and PyQt
PyQt and PySide are both Python bindings of the C++ library called Qt.
PyQt - made by riverbanks.
PySide - by Qt company.
Explanation:
The QmlElement, as described here, decorator will register the class to be used in QML.
every Signal you create in that class would be available in QML
in the type instantiation e.g:
Cv2Capture {
id: bridge
onImageAnalayized: function(res){console.log(res)}
}
Where onImageAnalayized was imageAnalayized = Signal(bool) in Python.
slots are also available via calling them with the component id.
Trying out a really simple TreeView control for Qt in Python, but for some reason the GUI is just blank.
main.qml
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4 as OldControls
ApplicationWindow {
visible: true
title: qsTr("Simple Tree View")
OldControls.TreeView {
anchors.fill: parent
model: simpleModel
OldControls.TableViewColumn {
role: "display"
title: "Name"
width: 100
}
}
}
main.py
import sys
from os.path import abspath, dirname, join
from PySide2.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide2.QtQml import QQmlApplicationEngine
class SimpleTreeView(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setColumnCount(1)
root = self.invisibleRootItem()
group1 = QStandardItem("group1")
group1.setText("group1")
value1 = QStandardItem("value1")
value1.setText("value1")
group1.appendRow(value1)
root.appendRow(group1)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
qmlFile = join(dirname(__file__), 'main.qml')
engine.rootContext().setContextProperty("simpleModel", SimpleTreeView())
engine.load(abspath(qmlFile))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Output in Linux
The problem is that since the SimpleTreeView object is not assigned to a variable then it is destroyed, that can be verified using the destroyed signal.
class SimpleTreeView(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.destroyed.connect(lambda o : print("destroyed:", o))
# ...
Output:
destroyed: <PySide2.QtCore.QObject(0x56377cf050f0) at 0x7ffa20deac40>
The solution is to assign a variable to that object so that the life cycle is greater:
qmlFile = join(dirname(__file__), 'main.qml')
model = SimpleTreeView()
engine.rootContext().setContextProperty("simpleModel", model)
# ...
I'm new to QML, QtQuick and Python. I would like to display a list of files (full path) using QML. It seems like I should use a ListView and ListElements. The examples and tutorials I have found all use list data that is hard-coded and very simple. I do not understand how to go from those examples to something more realistic.
How do I use a Python string array from my backend to populate a list displayed by the QML UI?
The length of the string array is arbitrary. I want the list items to be clickable (like a QML url type, possibly). They will open the operating system's default application for that file/url type.
My backend code is similar to this:
import sys
from subprocess import Popen, PIPE
import getpass
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtCore import Qt, QCoreApplication, QObject, pyqtSlot
from PyQt5.QtQml import QQmlApplicationEngine
class Backend(QObject):
basepath = '/path/to/files'
list_files_cmd = "find " + basepath + " -type f -readable"
myfiles = Popen(list_files_cmd, shell=True, stdout=PIPE, stderr=PIPE)
output, err = myfiles.communicate()
# the output is a Byte literal like this: b'/path/to/file1.txt\n/path/to/file2.txt\n'. Transform into a regular string:
newstr = output.decode(encoding='UTF-8')
files_list = newstr.split('\n')
for file in files_list:
print(file)
if __name__ == '__main__':
backend = Backend()
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
engine = QQmlApplicationEngine('view.qml')
engine.rootContext().setContextProperty("backend", backend)
sys.exit(app.exec_())
Right now I am just printing the files_list string array from the backend to the console, but the goal is to use that string array to populate the QML list in the UI.
An example of the contents of files_list is:
['/path/to/files/xdgr/todo.txt', '/path/to/files/xdgr/a2hosting.txt', '/path/to/files/xdgr/paypal.txt', '/path/to/files/xdgr/toggle.txt', '/path/to/files/xdgr/from_kty.txt', '/path/to/files/xdgr/feed59.txt', '/path/to/files/one/sharing.txt', '/path/to/files/two/data.dbx', '']
(I will need to figure out how to deal with the null string at the end of that array.)
A rough outline of my QML (to the best of my current ability) is like this:
import QtQml.Models 2.2
import QtQuick.Window 2.2
import QtQuick 2.2
import QtQuick.Controls 1.3
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
TabView {
anchors.fill: parent
Tab {
title: "Files"
anchors.fill: parent
ListView {
id: mListViewId
anchors.fill: parent
model: mListModelId
delegate : delegateId
}
ListModel {
id: mListModelId
// I would like backend.files_list to provide the model data
}
}
}
Component.onCompleted: {
mListModelId.append(backend.files_list)
}
}
The most relevant questions I have found are these, but they did not resolve my issue:
qt - Dynamically create QML ListElement and content - Stack Overflow Dynamically create QML ListElement and content
qt - QML ListElement pass list of strings - Stack Overflow QML ListElement pass list of strings
You don't need to use a ListModel to populate a ListView since as the docs points out a model can be a list:
model : model
This property holds the model providing data for the list.
The model provides the set of data that is used to create the items in
the view. Models can be created directly in QML using ListModel,
XmlListModel or ObjectModel, or provided by C++ model classes. If a
C++ model class is used, it must be a subclass of QAbstractItemModel
or a simple list.
(emphasis mine)
I also recommend Data Models.
In this case, a list can be displayed through a pyqtProperty. On the other hand do not use subprocess.Popen() as it is blocking causing the GUI to freeze, instead use QProcess.
import os
import sys
from PyQt5.QtCore import (
pyqtProperty,
pyqtSignal,
pyqtSlot,
QCoreApplication,
QObject,
QProcess,
Qt,
QUrl,
)
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
class Backend(QObject):
filesChanged = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._files = []
self._process = QProcess(self)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
self._process.setProgram("find")
#pyqtProperty(list, notify=filesChanged)
def files(self):
return self._files
#pyqtSlot(str)
def findFiles(self, basepath):
self._files = []
self.filesChanged.emit()
self._process.setArguments([basepath, "-type", "f", "-readable"])
self._process.start()
def _on_readyReadStandardOutput(self):
new_files = self._process.readAllStandardOutput().data().decode().splitlines()
self._files.extend(new_files)
self.filesChanged.emit()
if __name__ == "__main__":
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
backend = Backend()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", backend)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "view.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
view.qml
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
TabView {
anchors.fill: parent
Tab {
title: "Files"
ListView {
id: mListViewId
clip: true
anchors.fill: parent
model: backend.files
delegate: Text{
text: model.modelData
}
ScrollBar.vertical: ScrollBar {}
}
}
}
Component.onCompleted: backend.findFiles("/path/to/files")
}
You can also use QStringListModel.
import os
import sys
from PyQt5.QtCore import (
pyqtProperty,
pyqtSignal,
pyqtSlot,
QCoreApplication,
QObject,
QProcess,
QStringListModel,
Qt,
QUrl,
)
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
class Backend(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStringListModel()
self._process = QProcess(self)
self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
self._process.setProgram("find")
#pyqtProperty(QObject, constant=True)
def model(self):
return self._model
#pyqtSlot(str)
def findFiles(self, basepath):
self._model.setStringList([])
self._process.setArguments([basepath, "-type", "f", "-readable"])
self._process.start()
def _on_readyReadStandardOutput(self):
new_files = self._process.readAllStandardOutput().data().decode().splitlines()
self._model.setStringList(self._model.stringList() + new_files)
if __name__ == "__main__":
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
backend = Backend()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", backend)
current_dir = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(current_dir, "view.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
view.qml
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
TabView {
anchors.fill: parent
Tab {
title: "Files"
ListView {
id: mListViewId
clip: true
anchors.fill: parent
model: backend.model
delegate: Text{
text: model.display
}
ScrollBar.vertical: ScrollBar {}
}
}
}
Component.onCompleted: backend.findFiles("/path/to/files")
}
I want to show a rectangle in Qml and I want to change the rectangle's properties(width, length) from my python code. In fact, there is a socket connection in the python code, through which the values of width and length are received from another computer. To put it simple: another user should be able to adjust this rectangle in real-time.
I know how to make a socket connection in my python file and using PyQt5, I can show the qml file from python.
However, I am in trouble to access the rectangle's parameters through my python code. How can I do that?
This is a simplified sample of my qml file:
import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
visible: true
width: Screen.width/2
height: Screen.height/2
Rectangle {
id: rectangle
x: 187
y: 92
width: 200
height: 200
color: "blue"
}
}
And here is what I have written in my .py file:
from PyQt5.QtQml import QQmlApplicationEngine, QQmlProperty
from PyQt5.QtQuick import QQuickWindow, QQuickView
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtWidgets import QApplication
import sys
def run():
myApp = QApplication(sys.argv)
myEngine = QQmlApplicationEngine()
myEngine.load('mainViewofHoomanApp.qml')
if not myEngine.rootObjects():
return -1
return myApp.exec_()
if __name__ == "__main__":
sys.exit(run())
There are several methods to modify a property of a QML element from python/C++, and each has its advantages and disadvantages.
1. Pulling References from QML
Obtain the QML object through findChildren through another object.
Modify or access the property with setProperty() or property(), respectively or with QQmlProperty.
main.qml (the qml is for the next 2 .py)
import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
visible: true
width: Screen.width/2
height: Screen.height/2
Rectangle {
id: rectangle
x: 187
y: 92
width: 200
height: 200
color: "blue"
objectName: "foo_object"
}
}
1.1 setProperty(), property().
import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial
def testing(r):
import random
w = r.property("width")
h = r.property("height")
print("width: {}, height: {}".format(w, h))
r.setProperty("width", random.randint(100, 400))
r.setProperty("height", random.randint(100, 400))
def run():
myApp = QtGui.QGuiApplication(sys.argv)
myEngine = QtQml.QQmlApplicationEngine()
directory = os.path.dirname(os.path.abspath(__file__))
myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))
if not myEngine.rootObjects():
return -1
r = myEngine.rootObjects()[0].findChild(QtCore.QObject, "foo_object")
timer = QtCore.QTimer(interval=500)
timer.timeout.connect(partial(testing, r))
timer.start()
return myApp.exec_()
if __name__ == "__main__":
sys.exit(run())
1.2 QQmlProperty.
import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial
def testing(r):
import random
w_prop = QtQml.QQmlProperty(r, "width")
h_prop = QtQml.QQmlProperty(r, "height")
print("width: {}, height: {}".format(w_prop.read(), w_prop.read()))
w_prop.write(random.randint(100, 400))
h_prop.write(random.randint(100, 400))
def run():
myApp = QtGui.QGuiApplication(sys.argv)
myEngine = QtQml.QQmlApplicationEngine()
directory = os.path.dirname(os.path.abspath(__file__))
myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))
if not myEngine.rootObjects():
return -1
r = myEngine.rootObjects()[0].findChild(QtCore.QObject, "foo_object")
timer = QtCore.QTimer(interval=500)
timer.timeout.connect(partial(testing, r))
timer.start()
return myApp.exec_()
if __name__ == "__main__":
sys.exit(run())
A disadvantage of this method is that if the relation of the object with the rootobject is complex(Sometimes objects that are in other QMLs are hard to access with findChild) the part of accessing the object becomes complicated and sometimes impossible so this method will fail. Another problem is that when using the objectName as the main search data there is a high dependency of the Python layer to the QML layer since if the objectName is modified in QML the logic in python would have to be modified. Another disadvantage is that by not managing the life cycle of the QML object it could be eliminated without Python knowing so it would access an incorrect reference causing the application to terminate unexpectedly.
2. Pushing References to QML
Create a QObject that has the same type of properties.
Export to QML using setContextProperty.
Make the binding between the properties of the QObject and the properties of the item.
main.qml
import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
visible: true
width: Screen.width/2
height: Screen.height/2
Rectangle {
id: rectangle
x: 187
y: 92
width: r_manager.width
height: r_manager.height
color: "blue"
}
}
main.py
import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial
class RectangleManager(QtCore.QObject):
widthChanged = QtCore.pyqtSignal(float)
heightChanged = QtCore.pyqtSignal(float)
def __init__(self, parent=None):
super(RectangleManager, self).__init__(parent)
self._width = 100
self._height = 100
#QtCore.pyqtProperty(float, notify=widthChanged)
def width(self):
return self._width
#width.setter
def width(self, w):
if self._width != w:
self._width = w
self.widthChanged.emit(w)
#QtCore.pyqtProperty(float, notify=heightChanged)
def height(self):
return self._height
#height.setter
def height(self, h):
if self._height != h:
self._height = h
self.heightChanged.emit(h)
def testing(r):
import random
print("width: {}, height: {}".format(r.width, r.height))
r.width = random.randint(100, 400)
r.height = random.randint(100, 400)
def run():
myApp = QtGui.QGuiApplication(sys.argv)
myEngine = QtQml.QQmlApplicationEngine()
manager = RectangleManager()
myEngine.rootContext().setContextProperty("r_manager", manager)
directory = os.path.dirname(os.path.abspath(__file__))
myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))
if not myEngine.rootObjects():
return -1
timer = QtCore.QTimer(interval=500)
timer.timeout.connect(partial(testing, manager))
timer.start()
return myApp.exec_()
if __name__ == "__main__":
sys.exit(run())
The disadvantage is that you have to write some more code. The advantage is that the object is accessible by all the QML since it uses setContextProperty, another advantage is that if the QML object is deleted it does not generate problems since only the binding is eliminated. And finally, by not using the objectName, the dependency does not exist.
So I prefer to use the second method, for more information read Interacting with QML from C++.
Try some thing like below (Not tested, but will give you an idea).
create some objectname for rectangle as shown below:
Rectangle {
id: rectangle
x: 187
y: 92
width: 200
height: 200
color: "blue"
objectName: "myRect"
}
Interact with QML and find your child, then set the property.
#INTERACT WITH QML
engine = QQmlEngine()
component = QQmlComponent(engine)
component.loadUrl(QUrl('mainViewofHoomanApp.qml'))
object = component.create()
#FIND YOUR RECTANGLE AND SET WIDTH
child = object.findChild(QObject,"myRect")
child.setProperty("width", 500)
This is what I do in PySide6 (6.2.4 at the point of testing this):
If I have a custom property defined in QML like this:
import pyobjects
CustomPyObject {
id: iTheMighty
property var myCustomProperty: "is awesome"
Component.onCompleted: iTheMighty.subscribe_to_property_changes()
}
I define my python object like this:
QML_IMPORT_NAME = "pyobjects"
QML_IMPORT_MAJOR_VERSION = 1
#QmlElement
class CustomPyObject(QQuickItem):
#Slot()
def subscribe_to_property_changes(self):
self.myCustomPropertyChanged.connect(
lambda: self.on_my_custom_property_changed(self.property("myCustomProperty"))
)
# or
self.myCustomPropertyChanged.connect(
lambda: self.on_my_custom_property_changed(QQmlProperty(self, "myCustomProperty").read())
)
def on_my_custom_property_changed(self, new_value):
print("Got new value", new_value)
This way I get notified whenever a Qml property changes. Subscribing in the constructor of CustomPyObject is not possible as the custom property will only be ready after the object was created. Hence, the Component.onCompleted trigger.