How to have a button in QML execute a function laid out in Python (PySide6) file?
-
I've spent the last 5 hours trying to find an answer, to no avail, and I'm at my wit's end, so I'm taking a break. I'm hoping someone on here will be able to help point me in the right direction. There is also a TL;DR: at the bottom. :)
First off, to help with understanding what I'm dealing with, here is a link to my project on GitHub so that anyone can see all of its code:
https://github.com/ion-mironov/LED_control_panelMain.qml contains the code for the GUI, led_matrix.py has the code for the animations, and main,py runs handles everything.
Currently, in my QML file I have some
Images
that I have set up to behave as buttons. Here's an excerpt:Image { id: leftSignal source: 'images/left_' + leftSignal.buttonState + '.svg' Layout.fillWidth: true Layout.fillHeight: true fillMode: Image.PreserveAspectFit property string buttonState: "off" states: [ State { name: 'off' }, State { name: 'on' } ] MouseArea { anchors.fill: parent onClicked: { rightSignal.buttonState = 'off'; brakeLights.buttonState = 'off'; parkingLights.buttonState = 'off'; leftSignal.buttonState = (leftSignal.buttonState === 'on') ? 'off' : 'on'; } } }
They toggle on and off just fine. What I am wanting to accomplish now is to have each of them execute their respective LED animations when they're clicked/tapped on.
I'm currently stuck on how to accomplish this, as searching for "how to execute Python function with QML button" (and variations of those search parameters) nets some scattered responses, and most of them don't actually come close to what I'm wanting to achieve. Also, the Qt documentation has not really been helpful in this case, especially to a novice such as myself.
TL;DR: I made custom buttons in QML and I want to trigger different LED animations when I select any of them, but can't find the right answer.
-
@Bob64 Yes, that's actually what I've been attempting all last night, but a lot of what I found online wasn't helpful or related to what I was wanting. Hence the reason why I'm asking on the forum; I genuinely do not know what I should do. I couldn't even find proper code examples for my problem. Most of what I found was either seriously outdated or only focusing on how to do one thing in either QML or Python but not utilizing both.
I'm sure it's something really simple and easily overlooked, but I have not figured it out, which is annoying and a bit frustrating.
@I-Mironov does this help?
import sys from pathlib import Path from PySide6.QtCore import QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine class ControlPanel(QObject): def __init__(self): super().__init__() @Slot(str) def do_it(self, id): print(f"do_it called with {id}!") if __name__ == '__main__': app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() controlPanel = ControlPanel() engine.rootContext().setContextProperty("controlPanel", controlPanel) qml_file = Path(__file__).parent / 'main.qml' engine.load(qml_file) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
main.qml
import QtQuick 2.0 import QtQuick.Controls 2.1 import QtQuick.Window 2.1 ApplicationWindow { height: 400; width: 800 visible: true Button { anchors.centerIn: parent height: 100; width: 200 text: "Click Here" onClicked: { controlPanel.do_it("CLICKED!") } } }
-
I've spent the last 5 hours trying to find an answer, to no avail, and I'm at my wit's end, so I'm taking a break. I'm hoping someone on here will be able to help point me in the right direction. There is also a TL;DR: at the bottom. :)
First off, to help with understanding what I'm dealing with, here is a link to my project on GitHub so that anyone can see all of its code:
https://github.com/ion-mironov/LED_control_panelMain.qml contains the code for the GUI, led_matrix.py has the code for the animations, and main,py runs handles everything.
Currently, in my QML file I have some
Images
that I have set up to behave as buttons. Here's an excerpt:Image { id: leftSignal source: 'images/left_' + leftSignal.buttonState + '.svg' Layout.fillWidth: true Layout.fillHeight: true fillMode: Image.PreserveAspectFit property string buttonState: "off" states: [ State { name: 'off' }, State { name: 'on' } ] MouseArea { anchors.fill: parent onClicked: { rightSignal.buttonState = 'off'; brakeLights.buttonState = 'off'; parkingLights.buttonState = 'off'; leftSignal.buttonState = (leftSignal.buttonState === 'on') ? 'off' : 'on'; } } }
They toggle on and off just fine. What I am wanting to accomplish now is to have each of them execute their respective LED animations when they're clicked/tapped on.
I'm currently stuck on how to accomplish this, as searching for "how to execute Python function with QML button" (and variations of those search parameters) nets some scattered responses, and most of them don't actually come close to what I'm wanting to achieve. Also, the Qt documentation has not really been helpful in this case, especially to a novice such as myself.
TL;DR: I made custom buttons in QML and I want to trigger different LED animations when I select any of them, but can't find the right answer.
@I-Mironov I haven't looked at your project in detail and also I am a C++/QML rather than Python/QML user, but is the issue just that you want to call the
runAnimation
method when a button is pressed?In that case you should be able to call
controlPanel.runAnimation(...)
(with appropriate arguments) from your button clicked handler.Edit: note that this works because you have exposed
controlPanel
as a context property to QML, and I assume (by analogy with what I know about C++) that decoratingrunAnimation
with the@Slot
decorator is sufficient to make it callable from QML. -
@I-Mironov I haven't looked at your project in detail and also I am a C++/QML rather than Python/QML user, but is the issue just that you want to call the
runAnimation
method when a button is pressed?In that case you should be able to call
controlPanel.runAnimation(...)
(with appropriate arguments) from your button clicked handler.Edit: note that this works because you have exposed
controlPanel
as a context property to QML, and I assume (by analogy with what I know about C++) that decoratingrunAnimation
with the@Slot
decorator is sufficient to make it callable from QML.@Bob64 Crap, I forgot that main,py had some additional functions and statements in them, of which I wasn't sure if some of them were wrong and need to be edited.
I was having issues with the
runAnimation
being callable in my Main.qml file; I was getting errors about "Unqualified access" or "Unused import" for it (I'm using VS Code). I read that you cannot import Python files into QML, and that's where I was starting to hit a wall of figuring out how to get everything to work. -
@Bob64 Crap, I forgot that main,py had some additional functions and statements in them, of which I wasn't sure if some of them were wrong and need to be edited.
I was having issues with the
runAnimation
being callable in my Main.qml file; I was getting errors about "Unqualified access" or "Unused import" for it (I'm using VS Code). I read that you cannot import Python files into QML, and that's where I was starting to hit a wall of figuring out how to get everything to work.@I-Mironov it is correct that you can't import Python files into QML, but you should not need to do that. What you are doing looks like it is roughly along the right lines. The idea is that you are passing something callable into the QML layer.
I think you should simplify what you are trying to do down to the most basic thing. You want to invoke a method that you define in your Python code when you click a button defined in QML code. If I were you I would make a new simple project just focusing on this. This is the sort of thing I did a lot when I was first learning QML (and sometimes still do) - I made lots of tiny projects to figure out how to do the individual things I wanted to do and once I was comfortable with them I brought them into my "real" project.
-
@I-Mironov it is correct that you can't import Python files into QML, but you should not need to do that. What you are doing looks like it is roughly along the right lines. The idea is that you are passing something callable into the QML layer.
I think you should simplify what you are trying to do down to the most basic thing. You want to invoke a method that you define in your Python code when you click a button defined in QML code. If I were you I would make a new simple project just focusing on this. This is the sort of thing I did a lot when I was first learning QML (and sometimes still do) - I made lots of tiny projects to figure out how to do the individual things I wanted to do and once I was comfortable with them I brought them into my "real" project.
@Bob64 Yes, that's actually what I've been attempting all last night, but a lot of what I found online wasn't helpful or related to what I was wanting. Hence the reason why I'm asking on the forum; I genuinely do not know what I should do. I couldn't even find proper code examples for my problem. Most of what I found was either seriously outdated or only focusing on how to do one thing in either QML or Python but not utilizing both.
I'm sure it's something really simple and easily overlooked, but I have not figured it out, which is annoying and a bit frustrating.
-
@Bob64 Yes, that's actually what I've been attempting all last night, but a lot of what I found online wasn't helpful or related to what I was wanting. Hence the reason why I'm asking on the forum; I genuinely do not know what I should do. I couldn't even find proper code examples for my problem. Most of what I found was either seriously outdated or only focusing on how to do one thing in either QML or Python but not utilizing both.
I'm sure it's something really simple and easily overlooked, but I have not figured it out, which is annoying and a bit frustrating.
@I-Mironov does this help?
import sys from pathlib import Path from PySide6.QtCore import QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine class ControlPanel(QObject): def __init__(self): super().__init__() @Slot(str) def do_it(self, id): print(f"do_it called with {id}!") if __name__ == '__main__': app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() controlPanel = ControlPanel() engine.rootContext().setContextProperty("controlPanel", controlPanel) qml_file = Path(__file__).parent / 'main.qml' engine.load(qml_file) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
main.qml
import QtQuick 2.0 import QtQuick.Controls 2.1 import QtQuick.Window 2.1 ApplicationWindow { height: 400; width: 800 visible: true Button { anchors.centerIn: parent height: 100; width: 200 text: "Click Here" onClicked: { controlPanel.do_it("CLICKED!") } } }
-
@I-Mironov does this help?
import sys from pathlib import Path from PySide6.QtCore import QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine class ControlPanel(QObject): def __init__(self): super().__init__() @Slot(str) def do_it(self, id): print(f"do_it called with {id}!") if __name__ == '__main__': app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() controlPanel = ControlPanel() engine.rootContext().setContextProperty("controlPanel", controlPanel) qml_file = Path(__file__).parent / 'main.qml' engine.load(qml_file) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
main.qml
import QtQuick 2.0 import QtQuick.Controls 2.1 import QtQuick.Window 2.1 ApplicationWindow { height: 400; width: 800 visible: true Button { anchors.centerIn: parent height: 100; width: 200 text: "Click Here" onClicked: { controlPanel.do_it("CLICKED!") } } }
@Bob64 I will give that a shot right now and report back. In the meantime, I figured I'd show a screenshot of what I encounter when I try using the
runAnimation
decorator:Now, this question came into my head: should I try testing this on my Raspberry Pi?
I'm building this project on my desktop but it's my Pi that would be the main source for handling the LED animations, since
rpi_ws281x
can't really be handled by Windows. -
@I-Mironov does this help?
import sys from pathlib import Path from PySide6.QtCore import QObject, Slot from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine class ControlPanel(QObject): def __init__(self): super().__init__() @Slot(str) def do_it(self, id): print(f"do_it called with {id}!") if __name__ == '__main__': app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() controlPanel = ControlPanel() engine.rootContext().setContextProperty("controlPanel", controlPanel) qml_file = Path(__file__).parent / 'main.qml' engine.load(qml_file) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
main.qml
import QtQuick 2.0 import QtQuick.Controls 2.1 import QtQuick.Window 2.1 ApplicationWindow { height: 400; width: 800 visible: true Button { anchors.centerIn: parent height: 100; width: 200 text: "Click Here" onClicked: { controlPanel.do_it("CLICKED!") } } }
@Bob64 said in How to have a button in QML execute a function laid out in Python (PySide6) file?:
@I-Mironov does this help?
Son. Of. A. Bi--...
You have got to be kidding me. I honestly thought that because that "Unqualified access" error showed, that it would not work at all. But, I went and tested your code and it was still able to work. I genuinely, GENUINELY thought it wouldn't just because of that error. So, I never bothered to actually test it on my Pi.
-
Okay, so now I know that I can ignore things like "Unqualified access" when it comes to something like this.
But, now I'm facing a new problem: I cannot get the LEDs to do anything. I've tested them with a simple script to ensure they do work, and they do. So, there's something going on with my project's code somewhere that is causing them to not work correctly. I've included
print
statements which do show up, just not the lights.I think I'll be better off making a simple QML and Python setup to test a basic light animation and see what's going on.
-