I have a qml prototype object ChargeBar defined in ChargeBar.qml. In main project I instance it two times with different ids. I need a slot in Chargebar to send data to. Now I need to write id of target manually in Connections{target: id...} for every instance. Is there any possibility to connect to target automatically?
# This Python file uses the following encoding: utf-8
import os, sys, random
from pathlib import Path
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Signal, Slot
APPLICATIONDATA = os.path.join(os.getenv('APPDATA'), "DSController")
class LowLevelControler(QObject):
def __init__(self):
QObject.__init__(self)
nextNumber = Signal(int)
#Slot()
def start(self):
print("giveNumber")
self.nextNumber.emit(random.randint(0, 99))
if __name__ == "__main__":
SERIAL_LLC_RIGHT = "0672FF485550755187034646"
SERIAL_LLC_LEFT = "066AFF575256867067063324"
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
LLC = dict()
LLC["right"] = LowLevelControler()
LLC["left"] = LowLevelControler()
engine.rootContext().setContextProperty("llcRight", LLC["right"])
engine.rootContext().setContextProperty("llcLeft", LLC["left"])
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.py <-- entrance
import QtQuick 2.12
import QtQuick.Controls 2.2
import QtQuick.Window 2.12
Window {
id: window
width: 1080
height: 1920
//visibility: "FullScreen"
visible: true
ChargeBar{
id: drawerL
edge: Qt.LeftEdge
llc: llcLeft
}
ChargeBar{
id: drawerR
edge: Qt.RightEdge
llc: llcRight //llcRight=Instance of Python class
Component.onCompleted: llc.nextNumber.connect(reNextNumber)
Connections {
target: drawerR
// PROBLEM: I have to insert target id for every instance manually,
// so I cannot put this passage to ChargeBar.qml */
function onReNextNumber(number) {
print(number)
print("emitted")
}
}
}
Component.onCompleted: drawerR.open()
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
Drawer {
x:0
y:0
property var llc
signal reNextNumber(int number)
width: 0.66 * window.width
height: window.height
// I want to define Connections here
Button{
id: button
anchors.centerIn: parent
height: 320
width: 320
onClicked: {
llc.start()
}
}
}
ChargeBar.qml
You just have to use the llc object as a target:
ChargeBar.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
Drawer {
id: root
property var llc
signal reNextNumber(int number)
width: 0.66 * window.width
height: window.height
Button {
id: button
anchors.centerIn: parent
height: 320
width: 320
onClicked: llc? llc.start(): null
}
Connections {
target: llc
function onNextNumber(n) {
root.reNextNumber(n)
}
}
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 2.2
import QtQuick.Window 2.12
Window {
id: window
width: 1080
height: 1920
visible: true
ChargeBar {
id: drawerL
edge: Qt.LeftEdge
llc: llcLeft
}
ChargeBar {
id: drawerR
edge: Qt.RightEdge
llc: llcRight
// for testing
onReNextNumber: function(number){
console.log("Test", number)
}
}
Component.onCompleted: drawerR.open()
}
Related
I want to plot data in Qt charts (QML) from python. The x,y data are saved in array x = np.array([0, 6]) y = np.array([0, 250]) . I am desperate how to pass these data to Qt Charts with one step. I can do it (step by step) with Signal/Slot, where the Signal is with parameters (x,y).
My working (very slow) code:
Creating signal from python to qml:
class AppWindow(QObject):
# Signals from python to QML
sigPlotData = Signal(int, int, arguments=['x','y'])
and later () I generate and send data to chart like that:
...
for i in range(50):
self.app.sigPlotData.emit(i, random.randint(0,150))
...
In QML file I do this:
//connections from Python to QML via signals
Connections {
target: backend
function onSigPlotData(x,y){
lineSer.append(x, y);
}
}
ChartView {
id: chartView
title: "Line"
anchors.fill: parent
ValueAxis{
id: axisX
min: 0
max: maxX
}
ValueAxis{
id: axisY
min: 0
max: 150
}
LineSeries {
id: lineSer
name: "data"
axisX: axisX
axisY: axisY
}
}
Thank you very much for help.
you can have this :
in main.py :
import os
from pathlib import Path
import sys
from PyQt5.QtCore import QCoreApplication, Qt, QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
def main():
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
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.ConnectionType.QueuedConnection
)
engine.load(url)
sys.exit(app.exec())
if __name__ == "__main__":
main()
and in main.qml :
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtCharts 2.15
ApplicationWindow {
visible: true
width: 600
height: 300
property int timeStep: 0
ChartView {
id: chartView
anchors.fill: parent
ValueAxis {
id: axisX
min: 0
max: 400
}
LineSeries {
id: series1
axisX: axisX
name: "data"
}
}
Timer {
interval: 100
repeat: true
running: true
onTriggered: {
timeStep++;
var y = (1+Math.cos(timeStep/10.0))/2.0;
series1.append(timeStep, y);
}
}
}
Result is:
In QML you can create your function or other things like(for, if,..) with javascript syntax
and as you see I use series1.append(timeStep, y); means append function for adding data.
I am sort of stringing things together and it is slowly starting to make more sense. I get how to send signals etc. with a onClick.
Aim: GUI where depending on button clicked, it will read, and RFID card. This code will be kept and passed to database.
There are two buttons. One is open, the other is close. Either of these will open same RFID Python script to read the card. These scripts are all run in a stackView. Below is the card reader stackView Page. It loads perfectly fine from the buttons pressed on previous screen.
To navigate to the correct stackView, stackView.push(Qt.resolvedUrl("**GET URL FROM PYTHON**")).
So Depending on the button pressed on the previous page, it will load either Welcome QML or goodbye QML
Main QML
///For button OPEN
onClicked: {
backend.open() ///Here connect to the correct python def
stackView.push(Qt.resolvedUrl("ReadCard.qml")) ///Go to read card
}
/// For button CLOSE
onClicked: {
backend.close() ///Here connect to the correct python def
stackView.push(Qt.resolvedUrl("ReadCard.qml"))///Go to read card
}
Python: main.py
# Signal Set Data
readOpen = Signal(str)
scanOut = Signal(str)
#Slot()
def open(self):
# Read card code
# SQL Code
# Move to Welcome QML if the lock is opened.
self.readOpen.emit("Welcome.qml")
#Slot()
def close(self):
# Read card code
# SQL Code
# Move to Good-Bye QML when lock is closed.
self.scanOut.emit("GoodBye.qml")
print("This is where the code to clock out will go")
QML: reader.qml
import QtQuick 2.0
import QtQuick.Controls 2.15
import "../controls"
import QtQuick.Layouts 1.0
Item {
Rectangle {
id: rectangle
...Other properties...
Rectangle {
id: rectangleTop
...Other Properties...
}
Rectangle {
id: rectangleVisible
color: "#1d2128"
radius: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.top: rectangleTop.bottom
anchors.bottom: parent.bottom
anchors.bottomMargin: 40
anchors.rightMargin: 50
anchors.leftMargin: 50
anchors.topMargin: 10
//The timer just used to simulate card read as no card reader connected yet
Timer {
id: timer
}
function delay(delayTime,cb) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
Component.onCompleted: delay(1000, function() {
stackView.push(Qt.resolvedUrl("URL FROM PYTHON"))
})
Text {
id: text1
x: 148
y: 119
width: 288
height: 109
color: "#ffffff"
text: qsTr("Scan your card")
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 36
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Connections{
target: backend
}
}
So the big question is. How to get that URL from Python and feed it into the Qt.resolvedurl?
From what I understood is that the OP wants the StackView url to be handled from python, so a possible solution is to create a qproperty that places the url through a Connection:
import os
import random
import sys
from pathlib import Path
from PySide2.QtCore import Property, QCoreApplication, QObject, Qt, QUrl, Signal, Slot
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class Manager(QObject):
url_changed = Signal(name="urlChanged")
def __init__(self, parent=None):
super().__init__(parent)
self._url = QUrl()
#Property(QUrl, notify=url_changed)
def url(self):
return self._url
def _update_url(self, url):
if self.url == url:
return
self._url = url
self.url_changed.emit()
#Slot()
def open(self):
self._update_url(QUrl("Welcome.qml"))
#Slot()
def close(self):
self._update_url(QUrl("GoodBye.qml"))
def main():
app = QGuiApplication(sys.argv)
manager = Manager()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("manager", manager)
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)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Qt is awesome!!!")
RowLayout {
id: row_layout
width: parent.width
Button {
text: "Open"
Layout.fillWidth: true
onClicked: manager.open()
}
Button {
text: "Close"
Layout.fillWidth: true
onClicked: manager.close()
}
}
StackView {
id: stack_view
anchors.top: row_layout.bottom
anchors.bottom: parent.bottom
width: parent.width
}
Connections {
function onUrlChanged() {
stack_view.pop();
stack_view.push(Qt.resolvedUrl(manager.url));
}
target: manager
}
}
Welcome.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
Text {
text: "Welcome"
anchors.centerIn: parent
}
}
GoodBye.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
Text {
text: "GoodBye"
anchors.centerIn: parent
}
}
This question already has an answer here:
How can I send a python dictionary to a QML interface with a Signal?
(1 answer)
Closed 12 months ago.
First of all, I am aware that a similar question has been posted here on Stack Overflow. I have tried this solution, however, with no success. Here is a minimum verifiable example of my code:
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Signal, Slot
class Dictionary(QObject):
def __init__(self):
QObject.__init__(self)
self.dictionary = {1: "Word1", 2: "Word2"}
sendDict = Signal(dict)
#Slot(bool)
def s_dict(self, arg1):
self.sendDict.emit(self.dictionary)
if __name__ == "__main__":
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
dictionary = Dictionary()
engine.rootContext().setContextProperty("dictionary", dictionary)
engine.load("main.qml")
engine.quit.connect(app.quit)
sys.exit(app.exec_())
And here is the QML file:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
ApplicationWindow {
visible: true
width: 640
height: 240
title: qsTr("Minimum Verifiable Example")
color: "whitesmoke"
GridLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 9
columns: 4
rows: 4
rowSpacing: 10
columnSpacing: 10
Button {
height: 40
Layout.fillWidth: true
text: qsTr("Emit dictionary!")
Layout.columnSpan: 2
onClicked: {
dictionary.s_dict(true)
}
}
}
Connections {
target: dictionary
function onSendDict(arg1) {console.log(arg1)}
}
}
To put it simply, I want to print the elements of this dictionary with console.log, however all I get is this: qml: QVariant(PySide::PyObjectWrapper, ). I've tried indexing it as well, but to no avail (it returns undefined).
Thanks for your time!
Here's working code that addresses two issues:
The Signal needs to be spec'd with "QVariantMap" for pyside to marshall it into QML as an object correctly.
Object keys in Javascript are always strings so the marshaling appears to be skipping your integer keys. Use strings instead.
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, Signal, Slot
class Dictionary(QObject):
def __init__(self):
QObject.__init__(self)
self.dictionary = {"1": "Word1", "2": "Word2"}
sendDict = Signal("QVariantMap")
#Slot(bool)
def s_dict(self, arg1):
self.sendDict.emit(self.dictionary)
if __name__ == "__main__":
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
dictionary = Dictionary()
engine.rootContext().setContextProperty("dictionary", dictionary)
engine.load("main.qml")
engine.quit.connect(app.quit)
sys.exit(app.exec_())
and
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
ApplicationWindow {
visible: true
width: 640
height: 240
title: qsTr("Minimum Verifiable Example")
color: "whitesmoke"
GridLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 9
columns: 4
rows: 4
rowSpacing: 10
columnSpacing: 10
Button {
height: 40
Layout.fillWidth: true
text: qsTr("Emit dictionary!")
Layout.columnSpan: 2
onClicked: {
dictionary.s_dict(true)
}
}
}
Connections {
target: dictionary
function onSendDict(arg1) {
console.log(arg1);
console.log(JSON.stringify(arg1, null, 4));
}
}
}
Output:
qml: [object Object]
qml: {
"1": "Word1",
"2": "Word2"
}
You could also use JSON and encode your Python data structures into a string, pass the string to QML and use JSON.parse in Javascript to decode it. That might work better with more complicated data structures.
I have defined a button (actually it is a MouseArea) in QML inside an ApplicationWindow. I managed to connect to its clicked event from PyQt5. Now I am trying to show a save file dialog, but I get the error:
QWidget: Cannot create a QWidget without QApplication
My code looks like this:
from PyQt5.QtCore import QUrl, QObject # pylint: disable-msg=E0611
from PyQt5.QtGui import QGuiApplication, QIcon # pylint: disable-msg=E0611
from PyQt5.QtQml import QQmlApplicationEngine # pylint: disable-msg=E0611
from PyQt5.QtWidgets import QFileDialog # pylint: disable-msg=E0611
def openFile():
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
filename = QFileDialog.getOpenFileName(None,"QFileDialog.getOpenFileName()", "","All Files (*);;Python Files (*.py)", options=options)
print(filename)
def run():
app = QGuiApplication(sys.argv)
app.setWindowIcon(QIcon(resource_path("assets\\images\\icon.ico")))
engine = QQmlApplicationEngine()
engine.load(resource_path("qml\\Window.qml"))
engine.quit.connect(app.quit)
if not engine.rootObjects():
return -1
button = engine.rootObjects()[0].findChild(QObject, "openButton")
button.clicked.connect(openFile)
return app.exec_()
if __name__ == '__main__':
sys.exit(run())
I have also tried to pass the ApplicationWindow instead of None, but then I get a type error:
TypeError: getOpenFileName(parent: QWidget = None, caption: str = '', directory: str = '', filter: str = '', initialFilter: str = '', options: Union[QFileDialog.Options, QFileDialog.Option] = 0): argument 1 has unexpected type 'QWindow'
which does not make a lot of sense in my opinion, because QMainWindow inherits QWidget.
How can I show the dialog from my openFile() function?
EDIT: For the sake of completeness here is a stripped down version of my .qml file
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick 2.3
ApplicationWindow {
id: mainWindow
visible: true
width: 600
height: 400
Item {
anchors.top: titleBar.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
property bool isMainButtonFocused: false
objectName: "openButton"
signal clicked()
Label {
padding: 5
text: "<b><font color='#fefefe'>Hello World</font></b>"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.family: "Helvetica"
font.pointSize: 9
background: Rectangle {
color: mouseAreaOpenFolderButton.containsMouse ? "#777777" : "#333333"
border.width: isMainButtonFocused ? 2 : 1
border.color: "#ffffff"
radius: 5
}
MouseArea {
id: mouseAreaOpenFolderButton
anchors.fill: parent
hoverEnabled: true
onClicked: {
isMainButtonFocused = true
parent.parent.clicked()
}
}
}
}
}
The errors are very clear:
If you are going to use a QWidget like QFileDialog then you must create a QApplication.
If you are going to pass a parent a QWidget, as QFileDialog is, then that parent must be another QWidget but ApplicationWindow is not a QWidget but a QWindow causing that error.
Other additional but more important error is that you should not access the QML object from Python (or C++) since its life cycle is different so it could have problems (See this answer for example), instead create a QObject where implement the logic and expose it as an item (with qmlRegisterType) or a context property (via setContextProperty).
Considering the above then you should use FileDialog and expose a QObject as context property:
# ...
class Helper(QObject):
#pyqtSlot(QUrl)
def read_file(self, url):
filename = url.toLocalFile()
print(filename)
def run():
app = QGuiApplication(sys.argv)
app.setWindowIcon(QIcon(resource_path("assets\\images\\icon.ico")))
engine = QQmlApplicationEngine()
helper = Helper()
engine.rootContext().setContextProperty("helper", helper)
# ...
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQuick 2.3
import QtQuick.Dialogs 1.3
ApplicationWindow {
id: mainWindow
visible: true
width: 600
height: 400
Item {
anchors.top: titleBar.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
property bool isMainButtonFocused: false
Label {
padding: 5
text: "<b><font color='#fefefe'>Hello World</font></b>"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.family: "Helvetica"
font.pointSize: 9
background: Rectangle {
color: mouseAreaOpenFolderButton.containsMouse ? "#777777" : "#333333"
border.width: isMainButtonFocused ? 2 : 1
border.color: "#ffffff"
radius: 5
}
MouseArea {
id: mouseAreaOpenFolderButton
anchors.fill: parent
hoverEnabled: true
onClicked: {
// isMainButtonFocused = true
fileDialog.visible = true
}
}
}
}
FileDialog {
id: fileDialog
title: "Please choose a file"
selectedNameFilter: "All Files (*);;Python Files (*.py)"
onAccepted: {
helper.read_file(fileDialog.fileUrls[0])
}
}
}
I'm trying to access a FileDialog control from the python file that starts the QQmlApplication engine in order to retrieve the file path property. I have set up a signal in the .qml file, however I cannot access the file dialog by id in the python file to set up the slot. The findChild method in application.py returns None. Here is the code:
application.py
import sys
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, QQmlFileSelector
sys_argv = sys.argv
sys_argv += ['--style', 'material']
app = QGuiApplication(sys_argv)
window = QQmlApplicationEngine()
window.load("QML/main.qml")
fileDialog = window.findChild(QQmlFileSelector, "fileDialog")
print(fileDialog)
app.exec_()
Page1.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.2
Page {
width: 600
height: 400
header: Label {
text: qsTr("Prepare Data")
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Qt.application.font.pixelSize * 2
padding: 10
}
Button {
text: qsTr("Load data")
anchors.centerIn: parent
onClicked: fileDialog.visible = true
padding: 10
}
signal folderSelected()
FileDialog {
id: fileDialog
selectFolder: true
title: qsTr("Select the data directory")
folder: shortcuts.home
onAccepted: {
parent.folderSelected()
}
}
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
ApplicationWindow{
visible: true
title: qsTr("Main window")
width: 1000
height: 800
Material.theme: Material.Light
Material.accent: Material.Orange
SwipeView {
id: swipeView
anchors.fill: parent
Page1 {
}
Page2 {
}
Page3 {
}
}
}
In an old answer explain in the section Pushing References to QML how to update some python object from QML, that methodology is the one recommended by Qt and it is the one that should be used now. With your current method you need to establish an objectname that can be problematic in many cases.
So the solution is to create a QObject that we export to QML and update the qproperty, this will emit a signal that we connect to a slot where we can do the logic that we want. On the other hand FileDialog returns a url, so the property must be a QUrl:
main.qml
import os
import sys
from PySide2 import QtCore, QtGui, QtQml
class FileManager(QtCore.QObject):
file_url_Changed = QtCore.Signal(QtCore.QUrl)
def __init__(self, parent=None):
super(FileManager, self).__init__(parent)
self._file_url = QtCore.QUrl()
def get_file_url(self):
return self._file_url
def set_file_url(self, file_url):
if self._file_url != file_url:
self._file_url = file_url
self.file_url_Changed.emit(self._file_url)
file_url = QtCore.Property(QtCore.QUrl, fget=get_file_url, fset=set_file_url, notify=file_url_Changed)
#QtCore.Slot(QtCore.QUrl)
def on_file_url_changed(file_url):
print(file_url.toLocalFile())
if __name__ == '__main__':
sys.argv += ['--style', 'material']
app = QtGui.QGuiApplication(sys.argv)
file_manager = FileManager()
file_manager.file_url_Changed.connect(on_file_url_changed)
engine = QtQml.QQmlApplicationEngine()
engine.rootContext().setContextProperty("file_manager", file_manager)
file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "QML", "main.qml")
engine.load(QtCore.QUrl.fromLocalFile(file))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Page1.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.2
Page {
width: 600
height: 400
header: Label {
text: qsTr("Prepare Data")
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Qt.application.font.pixelSize * 2
padding: 10
}
Button {
text: qsTr("Load data")
anchors.centerIn: parent
onClicked: fileDialog.visible = true
padding: 10
}
FileDialog {
id: fileDialog
selectFolder: true
title: qsTr("Select the data directory")
folder: shortcuts.home
onAccepted: {
file_manager.file_url = fileDialog.fileUrl // <---
}
}
}