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
}
}
Related
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.
i using ffmpeg in my qt-python app and i redirecting the STDOUT of subprocess to a textarea in qml file , the problem is when redirects without append works fine ,but when appending text into textarea it making gui freezing with high ram usage, how to append stream text without lag, please
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Flickable {
id: scrollView
x: 13
y: 38
width: 647
height: 168
contentWidth: text1.width; contentHeight: text1.height
visible: true
flickableDirection: Flickable.VerticalFlick
clip: true
ScrollBar.vertical: ScrollBar{}
ScrollBar.horizontal: ScrollBar{}
TextArea {
id: text1
opacity: 1
visible: true
color: "#000000"
font.pixelSize: 16
verticalAlignment: Text.AlignVCenter
font.preferShaping: false
font.kerning: false
font.styleName: "Courier New"
font.weight: Font.Medium
font.bold: true
readOnly: true
clip: true
font.family: "Courier New"
}
}
Connections {
target: con
function onSetTx(filer){
text1.append(filer)
}
}
}
main.py
import os
from pathlib import Path
import sys
from time import sleep
import subprocess
import re
from threading import Thread
from PySide2.QtCore import QObject, Slot, Signal
from PySide2.QtGui import QGuiApplication, QIcon
from PySide2.QtQml import QQmlApplicationEngine
class Bridge(QObject):
setTx = Signal(str)
def __init__(self):
QObject.__init__(self)
Thread(target=self.download).start()
def download(self):
k = subprocess.Popen( f'"{Path(__file__).parent / "ffmpeg"}" "Rest of arg here', shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, encoding='latin-1')
while k.poll() is None:
std = k.stdout.readline()
empty = re.compile('^$')
if empty.match(std):
pass
else:
self.setTx.emit(std)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
bridge = Bridge()
context = engine.rootContext()
context.setContextProperty("con", bridge)
qmlFile = Path(__file__).parent / 'main.qml'
engine.load(os.fspath(qmlFile.resolve()))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
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.
I try to get the root object after window having completed, but I get a error:
QmlObj = self.engine.rootObjects()[0]
Error: list index out of range
The strange thing is that it works when I try to call foo.init_window() after the MouseArea having clicked.
Here is my python code:
main.py
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, QUrl, Slot
import sys
import win32gui
flag = False
class Foo(QObject):
def __init__(self):
super().__init__()
self.engine = QQmlApplicationEngine()
#Slot()
def init_window(self):
global flag
if not flag:
QmlObj = self.engine.rootObjects()[0]
desk = win32gui.FindWindow("Progman", "Program Manager")
print(desk)
sndWnd = win32gui.FindWindowEx(desk, 0, "SHELLDLL_DefView", None)
print(sndWnd)
targetWnd = win32gui.FindWindowEx(sndWnd,
0, "SysListView32", "FolderView")
print(targetWnd)
win32gui.SetParent((int)(QmlObj.winId()), targetWnd)
flag = True
if __name__ == "__main__":
app = QApplication(sys.argv)
foo = Foo()
foo.engine.rootContext().setContextProperty("foo", foo)
foo.engine.load(QUrl("main.qml"))
# win = foo.engine.rootObjects()[0]
# win.show()
if not foo.engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Here is the .qml file:
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
width: 200
height: 100
visible: true
//flags: Qt.FramelessWindowHint
//flags: Qt.WindowStaysOnBottomHint
//flags: Qt.WindowMinMaxButtonsHint
Rectangle {
anchors.fill: parent
color: "red"
Component.onCompleted: foo.init_window()
MouseArea {
anchors.fill: parent
onClicked: foo.init_window()
}
Text {
anchors.centerIn: parent
text: "Hello, World!"
}
Button {
text: "Ok"
onClicked: {
console.log("OK Button clicked....")
}
}
}
}
The problem is that in Component.onCompleted the window(the rootObject) has finished building but the engine list has not been updated. The solution is to invoke init_window an instant later using Qt.callLater():
Component.onCompleted: Qt.callLater(foo.init_window)
I want to send dictionaries, containing data that I need to use to dynamically create qml objects, from a PySide2 class to a QML interface and since I need to do it in response to certain events, I need to use signals and slots.
Since I've just started to use QML and python I tried to create a simple project just to play around (as you can see from the code)
QML:
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.3
import QtQuick.Layouts 1.0
ApplicationWindow {
id: mainWindow
width:640
height: 480
title: qsTr("Simple ui")
visible: true
locale:locale
Rectangle {
id: appWindow
objectName: "splasso"
anchors.fill: parent
color: "yellow"
Material.accent: Material.DeepPurple
Material.primary: Material.Cyan
Component.onCompleted: function(){
TestVar.theSignal.connect(catchAnswer);
testList.append(stuff1);
testList.append(stuff2);
testList.append(stuff3);
testCombo.currentIndex = 0;
//Just a pointless test print
console.log(JSON.stringify(stuff1));
}
function catchAnswer(answer){
console.log(JSON.stringify(answer));
}
ComboBox{
id: testCombo
anchors.centerIn: parent
width: parent.width
onCurrentIndexChanged: function(){
TestVar.catchInt(currentIndex);
}
model: ListModel{
id: testList
}
}
}
}
Python 3:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
from time import sleep
from PySide2.QtCore import Qt, QObject, Signal, Slot, Property, QThread
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
class Test1(QObject):
theSignal = Signal(dict)
def __init__(self):
QObject.__init__(self)
#Slot(int)
def catchInt(self,caught):
print("Caught: {0}".format(caught))
testDict = {"myAnswer":caught}
self.theSignal.emit(testDict)
if __name__ == "__main__":
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
stuff1 = {"text":"Trying"}
stuff2 = {"text":"To send"}
stuff3 = {"text":"Dict"}
ctx = engine.rootContext()
ctxVar = Test1()
ctx.setContextProperty("stuff1", stuff1)
ctx.setContextProperty("stuff2", stuff2)
ctx.setContextProperty("stuff3", stuff3)
ctx.setContextProperty("TestVar",ctxVar)
engine.load('main.qml')
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
The outuput I expected (launching the script with "python3 Test_dict_1.py") was:
Caught: 1
qml: {"myAnswer": 1}
Caught: 2
qml: {"myAnswer": 2}
Caught: 1
qml: {"myAnswer": 1}
...etc...
What I got instead was:
Caught: 1
qml: undefined.
Caught: 2
qml: undefined.
Caught: 1
qml: undefined.
...etc...
Can you tell me what am I doing wrong? Are there errors in the code or this thing cannot be done?
The signature you have to use in the signal is QVariant:
class Test1(QObject):
theSignal = Signal('QVariant')
#Slot(int)
def catchInt(self,caught):
print("Caught: {0}".format(caught))
testDict = {"myAnswer":caught}
self.theSignal.emit(testDict)