Emit python list in Signal to Qml ui - python

I am trying to communicate a python script to a qml ui using signal/slot and, while it works for some types (str) it seems to not be working when I try emitting a list:
Python:
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import time, sys
class PythonSignalEmitter(QObject):
getListTest = Signal(list)
#Slot()
def callList(self):
print("HELLO from python ")
test = ["a", "b", "c"]
self.getListTest.emit(test)
if __name__ == '__main__':
app = QGuiApplication([])
engine = QQmlApplicationEngine()
signal_emitter = PythonSignalEmitter()
engine.rootContext().setContextProperty("signalEmitter", signal_emitter)
engine.load("main.qml")
sys.exit(app.exec())
Fragment of the main.qml file:
Connections {
target: signalEmitter
function onSignal(res) {
console.log(`------------`);
console.log(`qml list is ${res}`);
console.log(`------------`);
}
}
The output of this just gives:
HELLO from python
So the app runs with no problem, and after a click on a specified component, the slot is called, but the log in the QLM side is not even printed, seems like the signal is not even emitted.

[EDIT]
You need to rename onSignal with onGetListTest. The rest is fine.
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Window
ApplicationWindow {
id: main
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Frame {
anchors.centerIn: parent
RowLayout {
Button {
text: qsTr("Quit")
onClicked: Qt.callLater(Qt.quit)
}
Button {
text: qsTr("Test")
onClicked: signalEmitter.callList()
}
}
}
Connections {
target: signalEmitter
function onGetListTest(res) {
console.log(`------------`);
console.log(`qml list is ${res}`);
console.log(`------------`);
}
}
}

Related

module `Qt.labs.qmlmodels` is not installed

I'm trying to run the example of item selection for a TableView, but it needs Qt.labs.qmlmodels. The problem is, that doesn't appear to be installed, and I can't find the correct package.
main.qml:
import Qt.labs.qmlmodels
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
visible: true
width: 1000
height: 700
title: "Test"
TableView {
id: tableView
anchors.fill: parent
clip: true
model: TableModel {
rows: [{
"name": "Harry"
}, {
"name": "Hedwig"
}]
TableModelColumn {
display: "name"
}
}
selectionModel: ItemSelectionModel {
model: tableView.model
}
delegate: Rectangle {
required property bool selected
implicitWidth: 100
implicitHeight: 30
color: selected ? "blue" : "lightgray"
Text {
text: display
}
}
}
}
main.py:
import sys
import signal
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine
# Make app respond to Ctrl-C
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit) # type: ignore
engine.load("main.qml")
sys.exit(app.exec())
Running main.py gives me this error:
QQmlApplicationEngine failed to load component
file:///home/user/test2/main.qml:1:1: module "Qt.labs.qmlmodels" is not installed
OS: Arch Linux
Qt version: 6.3.1-1
please read this:
qt-6.4-qml-python-missing-liberary-module-qt5compat-graphical-effects-is-not-installed
Just now, I try to change matplotlib-backends' version. I find that the pyqt6 folder doesn't have the Qt folder.
So I copy that in pyside6 lib to pyqt lib.
For me is:from D:\Python\Python39\Lib\site-packages\PySide6\qml\Qt to D:\Python\Python39\Lib\site-packages\PyQt6\Qt6\qml.
Please try this, hope it work.

The Shortcut in a PySide2 MenuItem prevents window from showing

I'm trying to create a very simple ApplicationWindow using PySide2 (Qt for Windows) and QML.
main.py
import sys
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QUrl
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == "__main__":
app = QApplication(sys.argv)
url = QUrl("mainWindow.qml")
engine = QQmlApplicationEngine()
engine.load(url)
sys.exit(app.exec_())
qml file
import QtQuick.Controls 2.4
ApplicationWindow {
id: mainWindow
visible: true
title: "MainWindow"
width: 640
height: 480
menuBar: MenuBar {
id: menuBar
Menu {
id: editMenu
title: "&Edit"
MenuItem {
id: copyItem
text: "Copy"
// This doesn't work:
// shortcut: "Ctrl+C"
// This doesn't work either:
// shortcut: StandardKey.Copy
}
}
}
}
As shown, the code runs and displays an ApplicationWindow with a MenuBar and the Menu. But if I outcomment either of the two shortcut variants the window isn't shown at all. I don't understand, why. My example follows the Qt documentation on MenuItems.
In QML there are 2 types of items: Qt Quick Controls 1 and Qt Quick Controls 2. Both groups have items with the same name but they differ in their properties, in your case MenuItem of Qt Quick Controls 2 does not have a shortcut property but instead Qt Quick Controls 1 if it has it so the solution is to change the import:
import QtQuick 2.11 // <---
import QtQuick.Controls 1.4 // <---
ApplicationWindow {
id: mainWindow
visible: true
title: "MainWindow"
width: 640
height: 480
menuBar: MenuBar {
id: menuBar
Menu {
id: editMenu
title: "&Edit"
MenuItem {
id: copyItem
text: "Copy"
shortcut: StandardKey.Copy
onTriggered: console.log("copy")
}
}
}
}

PySide2 QML - How to reference QML file within another QML file as component?

I got 3 Files in the same folder:
form.py
from __future__ import print_function
import sys, os
from PySide2.QtCore import QFile, QObject, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQuick import QQuickView
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
qmlFile = os.path.join(os.path.dirname(__file__), 'Main.qml')
view.setSource(QUrl.fromLocalFile(os.path.abspath(qmlFile)))
if view.status() == QQuickView.Error:
sys.exit(-1)
view.show()
app.exec_()
del view
Main.qml
import QtQuick 2.10
Item {
width: 200
height: 200
ListModel
{
id: myModel
ListElement { type: "Dog"; age: 8 }
ListElement { type: "Cat"; age: 5 }
}
ListView {
anchors.fill: parent
model: myModel
delegate: MyDelegate
}
}
MyDelegate.qml
import QtQuick 2.10
Component {
id: myDelegate
Text { text: type + ", " + age }
}
Running the form.py should give me a window with a ListView and two elements in it. This code is taken from an official tutorial and changed so I can see how referencing of QML files within other QML files works. All I get is a white window, so I guess the delegate does not get loaded by the Main.qml.
The first letter of MyDelegate.qml is uppercase so the Main.qml should automatically load the delegate. I expect this behaviour because it is a solution from the question: Include another QML file from a QML file
and in many tutorials, including some for PyQt5, I saw them referencing other QML files like that.
If I copy the Component {...} part into the Main.qml and change the delegate reference in ListView {...} from MyDelegate to myDelegate, it works.
This problem appears with any QML node I want to outsource into a separate file.
Doesn't PySide2 support this feature, or do I have to do some magic in the form.py to let the Main.qml know that there is another QML file to load?
I installed the current wheel with
pip install --index-url=http://download.qt.io/snapshots/ci/pyside/5.11/latest/ pyside2 --trusted-host download.qt.io
from the official wiki on a Windows 10 64bit machine with Python 3.6 installed.
Thanks to derM and eyllanesc who pointed out that there is a typo in the reference of MyDelegate.qml. The reference should be with braces delegate: MyDelegate {}.
The correct form.py is now:
import QtQuick 2.10
Item {
width: 200
height: 200
ListModel{
id: myModel
ListElement { type: "Dog"; age: 8 }
ListElement { type: "Cat"; age: 5 }
}
ListView {
anchors.fill: parent
model: myModel
delegate: MyDelegate {}
}
}
It even works with myModel outsourced in a MyModel.qml and referenced as model: MyModel {}

Injecting Properties into QML-Component

I am fairly new to Qt/PyQt and currently struggling with some basic functionality. What I'm trying to do is, to dynamically load QML-Views (*.qml) files from python and replace specific content on the fly. For example a checkbox gets checked and part of my current view is replaced with another qml file. First I wanted to provide this logic via PyQt, but it seems a StackView is a better idea (multiple qml files in pyqt).
However, in this case I am not able to inject properties into my QML files. I am only able to inject a property into the rootContext. That however limits the usage of my QML-Views since I can only use one view (of the same type) at once. I would like to inject properties dynamically into QML-Views and make them only visible to this particular view. In this case I can use the same view more than once with more than one object in the back-end to catch the signals.
Here is my SimpleMainWindow.qml file (the main view:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
objectName : "WINDOW"
property ApplicationWindow appWindow : window
}
And here the file I try to load (TestViewButton.qml):
import QtQuick 2.9
import QtQuick.Controls 1.4
Button {
id: test
text: "test"
objectName : "Button"
onClicked: {
configurator.sum(1, 2)
configurator.info("TestOutput")
}
}
Python file loading QML-View (Component)
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine, QQmlComponent
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load("qml/SimpleMainWindow.qml")
engine.quit.connect(app.quit)
rootWindow = engine.rootObjects()[0].children()[0].parent()
# create component
component = QQmlComponent(engine)
component.loadUrl(QUrl("qml/TestViewButton.qml"))
configurator = SignalHandler()
component.setProperty("configurator", configurator)
itm = component.create()
itm.setParentItem(rootWindow.children()[0])
itm.setProperty("configurator", configurator)
app.exec_()
And the python object that I use to handle the signals from the view (SignalHandler.py):
from PyQt5.QtCore import QObject, pyqtSlot
class SignalHandler(QObject):
#pyqtSlot(int, int)
def sum(self, arg1, arg2):
print("Adding two numbers")
#pyqtSlot(str)
def info(self, arg1):
print("STR " + arg1)
The button loads fine (by the way, is there a better way to identify the parent I want to add my button to, wasn't having any look with findChild). What is not working is the component.setProperty.... part. If I set the property in the rootContext of the engine it works fine (the SignalHandler methods are called). Already checked similar topics (like Load a qml component from another qml file dynamically and access that component's qml type properties ...)
Is this possible, or am I getting something wrong here?
thanks
From what I understand, you want to load the configuration object only in TestViewButton.qml and it is not visible in SimpleMainWindow.qml.
To do this TestViewButton.qml must have its own QQmlContext when it is loaded and is not the rootContext().
To test my response and observe that behavior we will create a similar button that tries to use the configurator, if this is pressed it should throw an error noting that the property does not exist but if the button loaded is pressed by the QQmlComponent should do its job normally.
qml/SimpleMainWindow.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4
ApplicationWindow {
id: window
visible: true
width: 640
color: "red"
height: 480
title: qsTr("Hello World")
Button {
x: 100
y: 100
text: "ApplicationWindow"
onClicked: {
console.log("ApplicationWindow")
configurator.sum(1, 2)
configurator.info("TestOutput")
}
}
}
As I commented previously I added the component with a new context:
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
configurator = SignalHandler()
engine.load("qml/SimpleMainWindow.qml")
engine.quit.connect(app.quit)
rootWindow = engine.rootObjects()[0]
content_item = rootWindow.property("contentItem")
context = QQmlContext(engine)
component = QQmlComponent(engine)
component.loadUrl(QUrl("qml/TestViewButton.qml"))
itm = component.create(context)
context.setContextProperty("configurator", configurator)
itm.setProperty("parent", content_item)
sys.exit(app.exec_())
At the end we get the following output:
qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined
qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined
Where we observe the desired behavior. The complete example can be found in the following link.

multiple qml files in pyqt

I am using pyqt and I have the following directory structure:
root
----> apps/
----> ui/
I have a simple qml based application in the app folder as:
apps/testqt.py
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView, QQuickWindow
from PyQt5.QtQml import qmlRegisterType, QQmlApplicationEngine
import sys
app = QApplication(sys.argv)
engine = QQmlApplicationEngine('ui/window.qml')
topLevel = engine.rootObjects()[0]
win = QQuickWindow(topLevel)
win.show()
app.exec_()
ui/window.qml
The qml file defines the app window and uses a StackView as follows:
import QtQuick 2.0
import QtQuick.Window 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
ApplicationWindow {
id: rootWindow
objectName: "window"
visible: true
width: 800
height: 480
title: "Window"
Component.onCompleted: {
setX(Screen.width / 2 - width / 2);
setY(Screen.height / 2 - height / 2);
}
property Component loginView: LoginView {}
StackView {
id: stackView
anchors.fill: parent
Component.onCompleted:
{
stackView.push(loginView)
}
}
}
This uses the LoginView component which is defined as:
apps/LoginView.qml
import QtQuick 2.0
import QtQuick.Window 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.0
ControlView {
ColumnLayout {
anchors.centerIn: parent
spacing: 25
width: 200
TextField {
id: username_fld
placeholderText: qsTr("User name")
Layout.fillWidth: true
}
TextField {
id: password_fld
placeholderText: qsTr("Password")
Layout.fillWidth: true
echoMode: TextInput.Password
}
RowLayout {
Button {
id: login_button
text: "Log In"
Layout.fillWidth: true
}
Button {
id: cancel_button
text: "Cancel"
Layout.fillWidth: true
}
}
}
}
Now when I use qmlscene, the view loads just fine. However, running python results in the application hanging while trying to load the QQmlApplicationEngine. I have a feeling it has something to do with the qml paths perhaps, so I included import ../ui into the window.qml imports but that did not change anything.
I am using Python 2.7 with Qt 5.6 in an Anaconda environment. Qt was installed from here: https://anaconda.org/anaconda/pyqt
Similarly i use multiple qml files along with multiple python files. When i access application it loads perfectly two qml files successfully but when stacked through third qml page, the application crashes. It throws segmentation fault.
I use Stackview to push and pop through QML pages, during debugging i found that it works when push and pop are done for just for two pages.
I have came across if i use Swipeview too. What i guess is that the engine that was created can be loaded with two stack QML files.

Categories

Resources