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?
Related
I've been trying to make for example 10 different elements for example buttons and I could do that using the repeater but I've been having trouble with setting the text for each new element.
I'm getting the texts I want to set from a list in Python and I sent them to qml through QStringListModel. The text got to qml from the list as I wanted, but somehow the repeater set's the text of all the elements as the last string in the list given from Python.
I will provide the code later for extra explanation but for now I want to see if someone have idea how I can do it...(the thing is that the code is in another device and I'm waiting for it). Basically what I'm trying to do is, let's say for example, I have a list in python a_list = {buttonName, buttonAge, buttonHeight} and I want to make multiple buttons in qml as the size of the list (in this case 3) and change each text of the buttons i made in the repeater as the strings in the list).
this is main.py
import sys
from PySide2.QtCore import QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from foo import FooController
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
fooController = FooController()
engine.rootContext().setContextProperty("fooController", fooController)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
This is foo.py
from PySide2.QtCore import QObject, Property, Slot
class x:
def __init__(self, name, age):
self.name = name
self.age = age
class FooController(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.__text = "Foo"
self.__name = s1.name
self.__age = s1.age
#Property(str)
def text(self):
return self.__name
#Slot()
def clickListener(self):
print(self.__name)
This is foo.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
Button {
text: fooController.text
onClicked: fooController.clickListener()
}
and here is the qml window that contains the repeater
import QtQuick 2.0
import "../components"
//import QtQuick.Timeline 1.0
import QtQuick.Controls 2.15
import QtQuick 2.15
import QtQuick.Window 2.15
import QtGraphicalEffects 1.15
import QtQuick.Layouts 1.15
import "../main.py"
window{
width: 1500
height: 920
minimumWidth: 1100
minimumHeight: 650
visible: true
color: "#00000000"
id: mainWindow
title: qsTr("--")
Rectangle{
id: rectangle
anchors.fill: parent
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
radius: 10
color: "#4642b6"
Flickable {
id: flickable
contentHeight: gridLayoutBottom.height
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: 96
anchors.rightMargin: 8
anchors.leftMargin: 8
anchors.bottomMargin: 8
clip: true
ListModel {
id: imageModel
ListElement { _id: "tile0" }
}
Repeater {
model: imageModel
delegate: CustomMenuType{
ListView{
model: s
delegate: Text {
text: model.display
font.family: "Segoe UI"
color: "#ffffff"
}
}
//text: ListView.delegate.Text.text
font.pointSize: 9
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
}
}
}
Disclaimer: The code you provide is useless since it does not show any attempt to solve your problem, besides that there are undefined elements that I will not take as a basis for my answer, and it is a shame because you always learn more by correcting errors.
If you want to handle the information that the Repeater uses from Python then you must use a model. The Repeater supports 3 types of models:
A number,
A list or
An object that inherits from QAbstractItemModel.
In this case the first does not provide important information since it only indicates the number of elements so it will not show it since it is a trivial example.
In the case of the list the logic is to create a Q_PROPERTY of type "QVariantList" (in PyQt5 list is enough) and that has an associated signal that is emitted every time the list changes so that the view is notified and updates the painting.
import os.path
import sys
from PySide2.QtCore import Property, QObject, QDateTime, QTimer, QUrl, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
class Controller(QObject):
modelChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._model = ["Text1", "Text2", "Text3"]
#Property("QVariantList", notify=modelChanged)
def model(self):
return self._model
def update_model(self, l):
self._model = l[:]
self.modelChanged.emit()
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
l = [dt.addSecs(i).toString() for i in range(3)]
controller.update_model(l)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.modelData
}
}
}
}
In the case of QAbstractItemModel the logic is to create a QProperty of type QObject and make it constant since the model itself does not change but the information it manages. And on the QML side, the property must be accessed using the associated role, for example in the case of QStringListModel the role is "display":
import os.path
import sys
from PySide2.QtCore import Property, QDateTime, QObject, QStringListModel, QTimer, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
class Controller(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStringListModel()
self.model.setStringList(["Text1", "Text2", "Text3"])
def get_model(self):
return self._model
model = Property(QObject, fget=get_model, constant=True)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
l = [dt.addSecs(i).toString() for i in range(3)]
controller.model.setStringList(l)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.display
}
}
}
}
You can also create a custom model but you have to declare a role associated with the data, a trivial example is to use the QStandardItemModel class.
import os.path
import sys
from PySide2.QtCore import (
Property,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
)
from PySide2.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
TEXT_ROLE = Qt.UserRole + 1000
DATA_ROLE = TEXT_ROLE + 1
class Controller(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self.model.setItemRoleNames({TEXT_ROLE: b"text", DATA_ROLE: b"data"})
for i in range(3):
item = QStandardItem()
item.setData("Text{}".format(i), TEXT_ROLE)
item.setData(i, DATA_ROLE)
self.model.appendRow(item)
def get_model(self):
return self._model
model = Property(QObject, fget=get_model, constant=True)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
for i in range(controller.model.rowCount()):
item = controller.model.item(i)
item.setData(dt.addSecs(i).toString(), TEXT_ROLE)
item.setData(dt.addSecs(i), DATA_ROLE)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.text
onClicked: console.log(model.data)
}
}
}
}
You could also create a class that inherits from QAbstractListModel and overrides the data, setData, roleNames, rowCount methods.
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.
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()