Related
I made a QtQuick Window Gui Application for Python 3.8 on Windows. The last thing I cant figure out is how to display Python print() in the Gui Text Area. What i want is, that wherever in my Python code a print statement is and gets executed during runtime, i want to output it into the TextArea in my Gui app
I read the following post, but failed to implemet it, different errors occured and am more confused then before:
the closest and most usefull was this one:
How to capture output of Python's interpreter and show in a Text widget?
and some others:
Python/PyQt/Qt Threading: How do I print stdout/stderr right away?
How to Redirect a Python Console output to a QTextBox
How can I flush the output of the print function?
How do I direct console output to a pyqt5 plainTextEdit widget with Python?
Python Printing StdOut As It Received
working Sample Code to send a string from Python into QML TextArea
main.py
import os
from pathlib import Path
import sys
from vantage import daily
# load GUI libs
from PySide2.QtGui import QGuiApplication
from PySide2.QtCore import QSettings, QObject, Signal, Slot
from PySide2.QtQml import QQmlApplicationEngine
# load app
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
self.timer = QTimer()
self.timer.setInterval(100)
self.timer.timeout.connect(self.writer)
self.timer.start()
# console output write function
def writer(self):
towrite = 'i am writing'
self.textwritten.emit(str(towrite))
# create an instance of the Python object (Backend class)
back_end = Backend()
# give data back to QML
engine.rootObjects()[0].setProperty('writen', back_end)
# close app
sys.exit(app.exec_())
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
color: "#2f2f2f"
title: qsTr("alpha")
/*print out console text*/
property string texted: "Console Text"
property QtObject writen
ScrollView {
id: scrollViewCon
x: 58
y: 306
width: 507
height: 100
ScrollBar.vertical.verticalPadding: 4
ScrollBar.vertical.minimumSize: 0.4
ScrollBar.vertical.contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 100
radius: width / 2
color: control.pressed ? "#81e889" : "#f9930b"
}
TextArea {
font.family: control.font
font.pointSize: 8
color:"#f9930b"
wrapMode: TextEdit.Wrap
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: textField
placeholderTextColor : "#f9930b"
opacity: 1
text: texted
placeholderText: texted //qsTr("Console")
readOnly: true
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
}
Connections {
target: writen
function onTextwritten(msg) {
texted = msg;
}
}
}
i think what needs to happen is that everytime sys.stdout is called by print() it emits a signal with itself?
leaving main.qml as is and only changing main.py
main.py
...
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
sys.stdout = self.writer(str(sys.stdout))
def writer(self, message):
#towrite = 'i am writing'
self.textwritten.emit(message)
...
The print function writes over sys.stdout so the solution is to assign some QObject that has a write method that emits a signal. For this you can use contextlib.redirect_stdout:
import os
import sys
from contextlib import redirect_stdout
from functools import cached_property
from pathlib import Path
from PySide2.QtCore import (
QCoreApplication,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
Signal,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class RedirectHelper(QObject):
stream_changed = Signal(str, name="streamChanged", arguments=["stream"])
def write(self, message):
self.stream_changed.emit(message)
class TimerTest(QObject):
#cached_property
def timer(self):
return QTimer(interval=1000, timeout=self.handle_timeout)
def handle_timeout(self):
print(QDateTime.currentDateTime().toString())
def start(self):
self.timer.start()
def main():
ret = 0
redirect_helper = RedirectHelper()
with redirect_stdout(redirect_helper):
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("redirectHelper", redirect_helper)
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)
timer_test = TimerTest()
timer_test.start()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
Flickable {
id: flickable
flickableDirection: Flickable.VerticalFlick
anchors.fill: parent
TextArea.flickable: TextArea {
id: textArea
anchors.fill: parent
readOnly: true
font.pointSize: 8
color: "#f9930b"
wrapMode: TextEdit.Wrap
placeholderTextColor: "#f9930b"
opacity: 1
placeholderText: qsTr("Console")
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
ScrollBar.vertical: ScrollBar {
}
}
Connections {
function onStreamChanged(stream) {
textArea.insert(textArea.length, stream);
}
target: redirectHelper
}
}
Is there a way for me to treat QML components as objects, and initialize them in Python? For instance, here's a simplified QML for a box:
I want to be able to replicate what a constructor method can do in Java. I want to be able to customize the text on each box through the Python script and also, at the same time, create multiple box instances that are separate from each other.
import QtQuick 2.0
import QtQuick.Controls 2.0
Item {
id: boxItem
width: 800
height: 118
Rectangle {
id: boxRect
height: 118
color: "#55f555"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
Text {
id: textItem
width: 463
height: 43
color: "#000000"
text: qsTr("Header Text")
anchors.left: parent.left
anchors.top: parent.top
font.pixelSize: 38
verticalAlignment: Text.AlignVCenter
font.family: "Roboto"
textFormat: Text.AutoText
anchors.leftMargin: 20
anchors.topMargin: 20
}
}
}
This is my current Python script modified from Qt's template version:
import os
import sys
from pathlib import Path
import PySide6.QtQml
from PySide6.QtQuick import QQuickView
from PySide6.QtCore import Qt, QUrl
from PySide6.QtGui import QGuiApplication
if __name__ == '__main__':
#Set up the application window
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
#Load the QML file
qml_file = Path(__file__).parent / "Main.qml"
view.setSource(QUrl.fromLocalFile(os.fspath(qml_file.resolve())))
#Show the window
if view.status() == QQuickView.Error:
sys.exit(-1)
view.show()
#execute and cleanup
app.exec()
del view
Quick clarification: I am working with custom built QML components, not trying to edit pre-existing ones made by QT.
Applying the concepts of another technology (programming language, library, framework) are often a bad approach to use some other technology. Each technology has its own methodology and good practices to implement any requirement.
In the case of QML the other languages such as C++, python, etc. are generally considered to implement business logic (something like a backend) and QML takes care of the view and the operation. In this case, it is recommended to create a QObject that provides other QObjects that create, modify, etc. the data and this can be reflected in the view. In this particular case, for the requirement, it is enough to use a model and a custom qml Item.
Box.qml
import QtQuick 2.0
import QtQuick.Controls 2.0
Item {
id: boxItem
width: 800
height: 118
property alias text: textItem.text
Rectangle {
id: boxRect
height: 118
color: "#55f555"
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
Text {
id: textItem
width: 463
height: 43
color: "#000000"
anchors.left: parent.left
anchors.top: parent.top
font.pixelSize: 38
verticalAlignment: Text.AlignVCenter
font.family: "Roboto"
textFormat: Text.AutoText
anchors.leftMargin: 20
anchors.topMargin: 20
}
}
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 2.0
Item {
id: root
ScrollView
{
anchors.fill: parent
Column{
Repeater{
model: manager.model
Box{
text: model.display
}
}
}
}
}
main.py
import os
import sys
from pathlib import Path
from PySide6.QtCore import Property, QObject, Qt, QUrl
from PySide6.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide6.QtQuick import QQuickView
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
#Property(QObject, constant=True)
def model(self):
return self._model
if __name__ == "__main__":
# Set up the application window
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
manager = Manager()
view.rootContext().setContextProperty("manager", manager)
qml_file = Path(__file__).parent / "main.qml"
view.setSource(QUrl.fromLocalFile(os.fspath(qml_file.resolve())))
if view.status() == QQuickView.Error:
sys.exit(-1)
view.resize(640, 480)
view.show()
for i in range(20):
item = QStandardItem(f"item-{i}")
manager.model.appendRow(item)
app.exec()
I have this code,
qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.0
ApplicationWindow {
property var theme: String("#ffffff")
property var focusColor: String('transparent')
id: applicationWindow
visible: false
width: 600
height:600
Image {
id: image_bug
anchors.fill: parent
source: "im.png"
}
Rectangle {
width: 100; height: 600
color: "green"
Text {
id: helloText
text: "Hello world!"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pointSize: 10; font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: { effectSource.width = 1200; effectSource.height = 1200;}
}
}
ShaderEffectSource {
id: effectSource
sourceItem: image_bug
anchors.centerIn: image_bug
width: 300
height: 300
sourceRect: Qt.rect(x,y, width, height)
}
FastBlur{
id: blur
anchors.fill: effectSource
source: effectSource
radius: 100
}
}
PyQT5 or Pyside2
import sys
import os # +++
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
QRect, QSize, QUrl, Qt)
'''
from PySide2.QtCore import Qt, QUrl
from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
QRect, QSize, QUrl, Qt)
'''
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
class GUI_MainWindow(QMainWindow):
def __init__(self, widget, parent=None):
QMainWindow.__init__(self, parent)
self.setWindowTitle('GUI_MainWindow')
self.resize(600, 600)
self.widget = widget
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
self.widget_test_2 = QLabel("<h1>Hello World !</h1>", alignment=Qt.AlignCenter)
self.widget_test_2.setObjectName(u"widget_test_2")
self.widget_test_2.setGeometry(QRect(180, 40, 151, 181))
self.widget_test_2.raise_()
layout = QVBoxLayout(centralWidget)
layout.addWidget(self.widget_test_2)
layout.addWidget(self.widget, stretch=1)#blur box
if __name__ == "__main__":
myApp = QApplication(sys.argv)
file = os.path.join(DIR_PATH, "qml_window.qml")
url = QUrl.fromLocalFile(file)
engine = QQmlApplicationEngine()
context = engine.rootContext()
context.setContextProperty("main", engine)
engine.load(url)
if not engine.rootObjects():
sys.exit(-1)
widget = QWidget.createWindowContainer(engine.rootObjects()[0])
window = GUI_MainWindow(widget)
window.show()
sys.exit(myApp.exec_())
and I want ShaderEffectSource to blur everything behind it,
even widgets created by PyQt5 or PySide2.
While moving or staying in place
Everything that is behind the widget should be blurry.
I already tried to use the QGraphicsBlurEffect effect for this, but this did not give me the desired results.
I hope FastBlur can do it.
if there are any other options then let me know
Can i do it?
I'm updating this answer as the question has been partially cleared in the comments, but I will leave the original answer at the end, as it might still be useful.
Besides that, the concept at the base remains the same: a graphics effect is applied to an object, and it modifies that object look, not how underlying objects appear. If you want to apply that effect to multiple objects, they have to be children of a common parent, and the effect has to be set for that parent, but everything that is below that parent (and it's not its child) will only be partially affected by the result of the effect.
Imagine a blur effect as a filter applied to a real life photograph that is partially transparent: while the image in the photograph is blurred, what you can see behind it will not be blurred.
Subclass the graphics effect
QGraphicsEffects don't provide the ability to limit the extent of their processing, as they usually modify the whole "bounding rect" of the object they are set for.
In order to achieve that, subclassing is necessary and the draw() method has to be overridden, as it is the one responsible for the actual drawing.
I'm going to presume that the whole interface is going to be affected by the effect in some way: even if some objects are "outside" the rectangle of the effect, they are still part of the same parent, so this is what we're going to do:
create a main widget that acts as container for the full interface
add a main layout for the main interface (the one normally shown)
create a sub widget that contains the main interface, set a layout for it and add anything you need to that layout
set the subclassed graphics effect to the sub widget
create a widget for the menu, that has the main widget as parent, so it will not be part of the main layout; it will have its own layout with its buttons, labels, etc.
add a system that changes the graphics effect according to the geometry of the menu, and whenever that changes, the effect will be applied to that geometry only
class BlurEffect(QtWidgets.QGraphicsBlurEffect):
effectRect = None
def setEffectRect(self, rect):
self.effectRect = rect
self.update()
def draw(self, qp):
if self.effectRect is None or self.effectRect.isNull():
# no valid effect rect to be used, use the default implementation
super().draw(qp)
else:
qp.save()
# clip the drawing so that it's restricted to the effectRect
qp.setClipRect(self.effectRect)
# call the default implementation, which will draw the effect
super().draw(qp)
# get the full region that should be painted
fullRegion = QtGui.QRegion(qp.viewport())
# and subtract the effect rectangle
fullRegion -= QtGui.QRegion(self.effectRect)
qp.setClipRegion(fullRegion)
# draw the *source*, which has no effect applied
self.drawSource(qp)
qp.restore()
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
background = QtGui.QPixmap('background.png')
# apply a background to this widget, note that this only serves for the
# graphics effect to know what's outside the boundaries
p = self.palette()
p.setBrush(p.Window, QtGui.QBrush(background))
self.setPalette(p)
self.resize(background.size())
# this layout is only for the child "sub" widget
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.setContentsMargins(0, 0, 0, 0)
# the "sub" widget, that contains the main interface
self.subWidget = QtWidgets.QWidget()
mainLayout.addWidget(self.subWidget)
# set the background for the subwidget; note that we can't use setPalette()
# because palette and fonts are inherited by children; using ".QWidget"
# we ensure that the background is only applied to the subwidget
self.subWidget.setStyleSheet('''
.QWidget {
background-image: url(background.png);
}
''')
# some random widgets
subLayout = QtWidgets.QGridLayout(self.subWidget)
for row in range(3):
for col in range(3):
btn = QtWidgets.QPushButton()
subLayout.addWidget(btn, row, col)
btn.setText('Open menu')
btn.setFocus()
btn.clicked.connect(self.openMenu)
# create an instance of our effect subclass, and apply it to the subwidget
self.effect = BlurEffect()
self.subWidget.setGraphicsEffect(self.effect)
self.effect.setEnabled(False)
self.effect.setBlurRadius(10)
# create the menu container, that *HAS* to have this main widget as parent
self.topMenu = QtWidgets.QWidget(self)
self.topMenu.setVisible(False)
self.topMenu.setFixedWidth(200)
# move the menu outside the window left margin
self.topMenu.move(-self.topMenu.width(), 0)
menuLayout = QtWidgets.QVBoxLayout(self.topMenu)
menuLayout.addSpacing(20)
for b in range(4):
btn = QtWidgets.QPushButton('Button {}'.format(b + 1))
menuLayout.addWidget(btn)
menuLayout.addSpacing(10)
closeButton = QtWidgets.QPushButton('Close menu')
menuLayout.addWidget(closeButton)
closeButton.clicked.connect(self.closeMenu)
# a stretch to ensure that the items are always aligned on top
menuLayout.addStretch(1)
# an animation that will move the menu laterally
self.menuAnimation = QtCore.QVariantAnimation()
self.menuAnimation.setDuration(500)
self.menuAnimation.setEasingCurve(QtCore.QEasingCurve.OutQuart)
self.menuAnimation.setStartValue(-self.topMenu.width())
self.menuAnimation.setEndValue(0)
self.menuAnimation.valueChanged.connect(self.resizeMenu)
self.menuAnimation.finished.connect(self.animationFinished)
# a simple transparent widget that is used to hide the menu when
# clicking outside it; the event filter is to capture click events
# it may receive
self.clickGrabber = QtWidgets.QWidget(self)
self.clickGrabber.installEventFilter(self)
self.clickGrabber.setVisible(False)
def resizeMenu(self, value):
# move the menu and set its geometry to the effect
self.topMenu.move(value, 0)
self.effect.setEffectRect(self.topMenu.geometry())
def openMenu(self):
if self.topMenu.x() >= 0:
# the menu is already visible
return
# ensure that the menu starts hidden (that is, with its right border
# aligned to the left of the main widget)
self.topMenu.move(-self.topMenu.width(), 0)
self.topMenu.setVisible(True)
self.topMenu.setFocus()
# enable the effect, set the forward direction for the animation, and
# start it; it's important to set the effect rectangle here too, otherwise
# some flickering might show at the beginning
self.effect.setEffectRect(self.topMenu.geometry())
self.effect.setEnabled(True)
self.menuAnimation.setDirection(QtCore.QVariantAnimation.Forward)
self.menuAnimation.start()
# "show" the grabber (it's invisible, but it's there) and resize it
# to cover the whole window area
self.clickGrabber.setGeometry(self.rect())
self.clickGrabber.setVisible(True)
# ensure that it is stacked under the menu and above everything else
self.clickGrabber.stackUnder(self.topMenu)
def closeMenu(self):
# in case that the menu has changed its size, set again the "start" value
# to its negative width, then set the animation direction to backwards
# and start it
self.menuAnimation.setStartValue(-self.topMenu.width())
self.menuAnimation.setDirection(QtCore.QVariantAnimation.Backward)
self.menuAnimation.start()
# hide the click grabber
self.clickGrabber.setVisible(False)
def animationFinished(self):
# if the animation has ended and the direction was backwards it means that
# the menu has been closed, hide it and disable the effect
if self.menuAnimation.direction() == QtCore.QVariantAnimation.Backward:
self.topMenu.hide()
self.effect.setEnabled(False)
def focusNextPrevChild(self, next):
if self.topMenu.isVisible():
# a small hack to prevent tab giving focus to widgets when the
# menu is visible
return False
return super().focusNextPrevChild(next)
def eventFilter(self, source, event):
if source == self.clickGrabber and event.type() == QtCore.QEvent.MouseButtonPress:
# the grabber has been clicked, close the menu
self.closeMenu()
return super().eventFilter(source, event)
def resizeEvent(self, event):
super().resizeEvent(event)
# always set the menu height to that of the window
self.topMenu.setFixedHeight(self.height())
# resize the grabber to the window rectangle, even if it's invisible
self.clickGrabber.setGeometry(self.rect())
if self.topMenu.isVisible():
# resize the effect rectangle
self.effect.setEffectRect(self.topMenu.geometry())
Previous answer
Since you want to apply the effect to the underlying objects, I believe that the solution is to use a "container" to embed them, and then apply the blur effect to that. The same concept would be applied with QGraphicsBlurWidget too.
ApplicationWindow {
property var theme: String("#ffffff")
property var focusColor: String('transparent')
id: applicationWindow
visible: false
width: 600
height:600
Rectangle {
id: container
anchors.fill: parent
Image {
id: image_bug
anchors.fill: parent
source: "im.png"
}
Rectangle {
width: 100; height: 600
color: "green"
Text {
id: helloText
text: "Hello world!"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pointSize: 10; font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: { effectSource.width = 1200; effectSource.height = 1200;}
}
}
}
ShaderEffectSource {
id: effectSource
sourceItem: container
anchors.centerIn: image_bug
width: 300
height: 300
sourceRect: Qt.rect(x,y, width, height)
}
FastBlur{
id: blur
anchors.fill: effectSource
source: effectSource
radius: 100
}
}
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
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 }
}
}
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.
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.