Wie kann ich Widgets ansprechen(pyside6)?
-
Hallo,
ich habe ein Problem. Ich habe die .ui Datei direkt eingefügt, und jetzt kann ich meine Widgets nicht anspechen.
Im ganzen möchte ich durch ein click() Signal, den inhalt einer SpinBox abrufen und in eine Methode geben.
Diese soll dann mit ermittelten Ergebnissen Labels setten.Hier der Quellcode:
import sys from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication from PySide6.QtCore import QFile, QIODevice from Abrechnung import Abrechnung class Verbrauchsrechner: """Main Klasse hier startet das Programm""" def berechne(self): eingegebener_zaehlerstand = self.ui.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) self.ui.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.ui.arbeitspreis_brutto.setText(f"{abrechnung.arbeitspreis_brutto:.2f} €") self.ui.grundpreis_netto.setText(f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €") self.ui.grundpreis_brutto.setText(f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €") self.ui.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.ui.gesamtpreis_brutto.setText(f"{abrechnung.gesamtbetrag_brutto:.2f} €") self.ui.abschlag_bis_jetzt.setText(f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €") self.ui.abschlag_ende_des_jahres.setText(f"{abrechnung.abschlag_ende_des_jahres:.2f} €") self.ui.gp_abz_abschlag_bj.setText(f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €") self.ui.gp_abz_abschlag_ej.setText(f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}") def main(): app = QApplication(sys.argv) ui_file_name = "GUI/verbrauchsrechner_gui.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() window = loader.load(ui_file) ui_file.close() if not window: print(loader.errorString()) sys.exit(-1) window.show() sys.exit(app.exec()) if __name__ == '__main__': main()
MFG Master_Shredder
-
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Ja das ist einer der "empfohlenen Wege", wie man den QtDesigner nutzt. Ob man es so macht, ist ja jedem selbst überlassen.
Ja so lasse ich es auch jetzt. Das sieht gut aus. Bevor ich mich auf Irrwege mache und am Ende nichts davon verstehe.
Denke zum Lernen und Herumexperimentieren mit Qt und QtDesigner ist das die beste und einfachste Lösung.
So muss man sich keine Gedanken machen, und hat innerhalb der Klasse überall vollen Zugriff auf die UI mittels self.ui.<objekt_name>.
Alles Weitere kommt dann mit der Zeit :)Ja denke ich auch. Dies ist wunderbar :).
OK. Dann dankeschön für die Hilfe.
-
Hi @Master_Shredder, willkommen im Forum,
vorab, ich bin nicht der große PySide/PyQt-Nutzer, bevorzuge eher C++, aber hier trotzdem ein paar Dinge, die mir aufgefallen sind:
Im ganzen möchte ich durch ein click() Signal, den inhalt einer SpinBox abrufen und in eine Methode geben.
Diese soll dann mit ermittelten Ergebnissen Labels setten.Ein Click worauf? Ein
clicked()
Signal muss ja irgendwoher kommen. Was genau soll geklickt werden? Sehe bei dir nämlich keinen Button.Ich habe die .ui Datei direkt eingefügt, und jetzt kann ich meine Widgets nicht anspechen.
Ja weil es kein
self.ui
mehr gibt.
Wenn ich mich nicht irre, wirdself.ui
auch in Qt für Python durchsetupUi
initialisiert (ebenso in C++, nur halt als::UI
Member im Header statt "self").window = loader.load(ui_file)
Der
QUiLoader
gibt einQWidget
zurück, welches aus der geladenen*.ui
Datei erstellt wurde.
Damit kannst du dann weiter arbeiten, die Sub-Widgets herausladen und dann die Signale verbinden.z.B. so:
button = self.window.findChild(QtWidgets.QPushButton, "button_object_name") button.clicked.connect(self.berechne)
Ich würde übrigens das Laden der UI File in den Konstruktor deiner
Verbrauchsrechner
-Klasse schieben und nicht unbedingt in der Main machen. So kannst du dann auch direkt das geladene Widgetwindow
als Klassenvariable nutzen und hast dann mitself.window
Zugriff darauf (siehe oben wie in meinem Code)...Keine Garantie dass das funktioniert... aber so würde man einen Button click mit deiner
Berechne
Funktion verbinden.
(Vielleicht findet sich hier noch ein PySide Experte, der mich möglicherweise korrigiert oder dir noch mehr Tipps geben kann)Aber was spricht denn dagegen, die
*.ui
Datei selbst zu einer*.py
Klasse zu kompilieren und dann so zu nutzen wie bisher?
Noch besser wäre natürlich die Widgets selbst zu coden, dann spart man sich den Designer und die ganzenui
Files komplett :)
Aber am Anfang muss man sich erstmal damit anfreunden :) -
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Hi @Master_Shredder, willkommen im Forum,
Hi, Dankeschön
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Ein Click worauf? Ein clicked() Signal muss ja irgendwoher kommen. Was genau soll geklickt werden? Sehe bei dir nämlich keinen Button.
Ja ein Button.
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Der QUiLoader gibt ein QWidget zurück, welches aus der geladenen *.ui Datei erstellt wurde.
Damit kannst du dann weiter arbeiten, die Sub-Widgets herausladen und dann die Signale verbinden.Ah ja so etwas habe ich mir schon gedacht, hatte auch schon mal ein Beispiel gesehen.
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Ich würde übrigens das Laden der UI File in den Konstruktor deiner Verbrauchsrechner-Klasse schieben und nicht unbedingt in der Main machen. So kannst du dann auch direkt das geladene Widget window als Klassenvariable nutzen und hast dann mit self.window Zugriff darauf (siehe oben wie in meinem Code)...
Ja dies halte ich auch für eine gute Idee, habe es auch so umgesetzt.
Jetzt habe ich nur das Problem, dass ich diese Fehlermeldungen hier bekomme:
Unresolved attribute reference 'clicked' for class 'object'
Unresolved attribute reference 'berechne' for class 'Verbrauchsrechner'import sys from PySide6 import QtWidgets from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication from PySide6.QtCore import QFile, QIODevice from Abrechnung import Abrechnung class Verbrauchsrechner: """Main Klasse hier startet das Programm""" def __init__(self): ui_file_name = "GUI/verbrauchsrechner_gui.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() window = loader.load(ui_file) ui_file.close() if not window: print(loader.errorString()) sys.exit(-1) window.show() # Slot eingerichtet button = window.findChild(QtWidgets.QPushButton, "button_OK") button.clicked.connect(self.berechne) def berechne(self): eingegebener_zaehlerstand = self.window.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) self.window.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.window.arbeitspreis_brutto.setText(f"{abrechnung.arbeitspreis_brutto:.2f} €") self.window.grundpreis_netto.setText(f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €") self.window.grundpreis_brutto.setText(f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €") self.window.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.window.gesamtpreis_brutto.setText(f"{abrechnung.gesamtbetrag_brutto:.2f} €") self.window.abschlag_bis_jetzt.setText(f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €") self.window.abschlag_ende_des_jahres.setText(f"{abrechnung.abschlag_ende_des_jahres:.2f} €") self.window.gp_abz_abschlag_bj.setText(f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €") self.window.gp_abz_abschlag_ej.setText(f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}") def main(): app = QApplication(sys.argv) sys.exit(app.exec()) if __name__ == '__main__': main()
-
@Master_Shredder said in Wie kann ich Widgets ansprechen(pyside6)?:
from PySide6.QtWidgets import QApplication
Ändere die Zeile zu:
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
Du musst natürlich die Klassen aus den Modulen, die du explizit nutzen willst, auch einbinden
(QSpinBox
und alle anderenQWidgets
, die du im weiteren Verlauf nutzt, auch)Unresolved attribute reference 'berechne' for class 'Verbrauchsrechner'
Entweder ist die Meldung mit der obigen Änderung dann auch weg oder er findet deine
berechne
Methode nicht.
Könnte sein, dass sie falsch eingerückt ist und er sie nicht innerhalb der Klasse findet, oder so.Edit:
Vor
window
undbutton
fehlt dasself
damit es keine lokale Variable bleibt und duself.window
in derberechne
Methode aufrufen kannst. -
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Könnte sein, dass sie falsch eingerückt ist und er sie nicht innerhalb der Klasse findet, oder so.
Die Einrücken dürfte eh falsch sein. Da sie ja nicht in der Klasse liegt. Ich habe dies korrigiert.
berechne() wird nun gefunden. Dann habe ich "self" vor die initialisierung von "window" und "button" gesetzt.
Jetzt werden wie widgets in berechne() nicht mehr gefunden.
Cannot find reference 'spin_box_eingabe' in 'QWidget | QWidget'
Cannot find reference 'arbeitspreis_netto' in 'QWidget | QWidget'
Cannot find reference 'arbeitspreis_brutto' in 'QWidget | QWidget'
...Diese müssten doch jetzt bei "window" aufgerufen werden.?
Dann habe ich noch die Widgets einzeln, in die Import Anweisung eingefügt.
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QSpinBox, QDialog
Doch diese werden leider nicht benutzt.
Unused import statement 'QPushButton'
Unused import statement 'QLabel'
...Dabei ist mir noch aufgefallen, dass mein Hauptfenster ein QDialog und kein QMainWindow ist.
clicked() wird leider immer noch nicht gefunden.
Unresolved attribute reference 'clicked' for class 'object'import sys from PySide6 import QtWidgets from PySide6.QtCore import QFile, QIODevice from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QLabel, QSpinBox from Abrechnung import Abrechnung class Verbrauchsrechner: """Main Klasse hier startet das Programm""" def __init__(self): ui_file_name = "GUI/verbrauchsrechner_gui.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() self.window = loader.load(ui_file) ui_file.close() if not self.window: print(loader.errorString()) sys.exit(-1) self.window.show() # Slot eingerichtet self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK") self.button.clicked.connect(self.berechne) def berechne(self): eingegebener_zaehlerstand = self.window.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) self.window.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.window.arbeitspreis_brutto.setText( f"{abrechnung.arbeitspreis_brutto:.2f} €" ) self.window.grundpreis_netto.setText( f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €" ) self.window.grundpreis_brutto.setText( f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €" ) self.window.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.window.gesamtpreis_brutto.setText( f"{abrechnung.gesamtbetrag_brutto:.2f} €" ) self.window.abschlag_bis_jetzt.setText( f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €" ) self.window.abschlag_ende_des_jahres.setText( f"{abrechnung.abschlag_ende_des_jahres:.2f} €" ) self.window.gp_abz_abschlag_bj.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €" ) self.window.gp_abz_abschlag_ej.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}" ) def main(): app = QApplication(sys.argv) sys.exit(app.exec()) if __name__ == "__main__": main()
-
@Master_Shredder said in Wie kann ich Widgets ansprechen(pyside6)?:
berechne() wird nun gefunden. Dann habe ich "self" vor die initialisierung von "window" und "button" gesetzt.
Jetzt werden wie widgets in berechne() nicht mehr gefunden.
Cannot find reference 'spin_box_eingabe' in 'QWidget | QWidget'
Cannot find reference 'arbeitspreis_netto' in 'QWidget | QWidget'
Cannot find reference 'arbeitspreis_brutto' in 'QWidget | QWidget'
...Diese müssten doch jetzt bei "window" aufgerufen werden.?
Puh, im Prinzip schon.
Kann es leider nicht ausprobieren und testen, da ich aktuell kein PySide nutze bzw. installiert habe.Deine ganze Struktur der Python Klasse(n) scheint noch nicht so ganz optimal zu sein.
[ Edit:
Hier gilt dasselbe wie beim Button. Du kannst nicht mehr direkt auf die Elemente zugreifen, da duwindow
mit demQUiLoader
als ganzesQWidget
aus deiner UI Datei lädst.
D.h. du müsstest wieder für jedes Element einmal mitfindChild
denQObject
-Eltern-Kind-"Baum" nach dem Widget mit dem Objektnamen durchsuchen.
Da ich annehme dass das allesQLabel
sind, kannst du auch alle auf einmal als Liste abfragen ]self.label_list = self.window.findChildren(QtWidgets.QLabel)
Dann habe ich noch die Widgets einzeln, in die Import Anweisung eingefügt.
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QSpinBox, QDialog
Doch diese werden leider nicht benutzt.
Unused import statement 'QPushButton'
Unused import statement 'QLabel'
...Die unused Warnings kann man ignorieren. Die sollten weggehen sobald du die Widgets wirklich ansprichst (auf das Label oder den Button zugreifst).
Dabei ist mir noch aufgefallen, dass mein Hauptfenster ein QDialog und kein QMainWindow ist.
Also das Widget, was du im QtDesigner als GUI erstellt hast, ist ein
QDialog
und darin sind dann deine Layouts mit Textfeldern, Labels, der SpinBox und dem Button?!
Warum einQDialog
?!
Ist jetzt nicht "verboten", ist aber ungewöhnlich.
Wenn du keinQMainWindow
mit dem ganzen ToolBar- und MenuBar-Schnickschnack brauchst/willst, kannst du auch einfach einQWidget
als Basisklasse wählen...QDialog
undQMainWindow
sind im Grunde auch nichts anderes als etwas speziellere Widgets.clicked() wird leider immer noch nicht gefunden.
Unresolved attribute reference 'clicked' for class 'object'Funktioniert denn die Zeile?
self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK")
Ist der ObjectName korrekt und existiert in der
verbrauchsrechner_gui.ui
Datei einQPushButton
IN demQDialog
dort, derbutton_OK
heißt?Nur um sicherzugehen:
Ist das was oben steht das gesamte Programm? Oder gibt's eine andere Python Klasse mitQMainWindow
oder so?
(Ich sehe du importierst Funktionen aus einem ModulAbrechung
, aber das sind dann wohl nur Hilfsfunktionen zur Berechnung der Werte die an anzeigen willst?!)
Es fehlt ja auch in dermain()
sowas wiedef main(): app = QApplication(sys.argv) verbr_rechner = Verbrauchsrechner() sys.exit(app.exec()) if __name__ == "__main__": main()
Edit:
Das Einzige was mir sonst noch bzgl. der Connection einfällt, wäre deine
Verbrauchsrechner
Klasse zu einemQObject
zu machen...class Verbrauchsrechner(QObject): def __init__(self, parent=None): super().__init__(parent)
aber eigentlich sollte das keine Rolle spielen, da du ja die Klassenvariable
self.window
als eigenständiges Qt Widget (aktuell ja wohlQDialog
) nutzt. Daher braucht die Klasse drumherum nichts mit Qt zu tun haben und kann ja auch Nicht-Qt Logik und Code enthalten.
Und dein Button ist ja schließlich einQPushButton
(sollte, daher am besten checken, was dasfindChild
wirklich zurückgibt).Vielleicht übersehe ich irgendwas Offensichtliches - wie gesagt, bin eher im C++ Umfeld unterwegs.
-
Danke für die ausführliche Antwort.
Da ich annehme dass das alles QLabel sind, kannst du auch alle auf einmal als Liste abfragen
Ja, dies sieht schön Elegant aus. Aber wie greife ich denn dann auf die einzelnen Label zu? Mit Index [0]... wird dies sehr unübersichtlich, unsauber.
Also das Widget, was du im QtDesigner als GUI erstellt hast, ist ein QDialog und darin sind dann deine Layouts mit Textfeldern, Labels, der SpinBox und dem Button?!
Ja genau.
Warum ein QDialog?!
Ist jetzt nicht "verboten", ist aber ungewöhnlich.
Wenn du kein QMainWindow mit dem ganzen ToolBar- und MenuBar-Schnickschnack brauchst/willst, kannst du auch einfach ein QWidget als Basisklasse wählen... QDialog und QMainWindow sind im Grunde auch nichts anderes als etwas speziellere Widgets.Dies kam durch ein Tutorial. Als ich das Erste mal Pyside benutzt habe wurde dies so erklärt. Und da dies ein Fenster und ein Button enthielt, so wie ich es brauchte, habe ich es gerade behalten. Für Zukunft's sicher zu sein würde ich auch ein QMainWindow holen. Nur am Anfang wusste ich auch nicht, dass ich die Menübar einfach entfernen kann.
Wenn es jetzt nichts an der Syntax ändert, würde ich den QDialog jetzt auch noch so lassen.Funktioniert denn die Zeile?
self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK")
Ist der ObjectName korrekt und existiert in der verbrauchsrechner_gui.ui Datei ein QPushButton IN dem QDialog dort, der button_OK heißt?
Ob sie "funktioniert" ist schwer zu sagen. Da das Argument mit dem gesucht wird ein String ist funktioniert die Fehleranzeige von PyCharm nicht. Bzw. der Fehler hängt bei clicked().
Unresolved attribute reference 'clicked' for class 'object'
Aber bei den Versuchen in berechne() einmal um die SpinBox anzusprechen und einmal das Label zu setten bekomme ich Fehler.
Unresolved attribute reference 'spin_box_eingabe' for class 'object'
Unresolved attribute reference 'setText' for class 'object'Ja der ObjektName ist korrekt.
Ist das was oben steht das gesamte Programm? Oder gibt's eine andere Python Klasse mit QMainWindow oder so?
(Ich sehe du importierst Funktionen aus einem Modul Abrechung, aber das sind dann wohl nur Hilfsfunktionen zur Berechnung der Werte die an anzeigen willst?!)Ja genau. In Abrechnung sind nur Hilfsfunktionen zur Berechnung.
Es fehlt ja auch in der main() sowas wie
Ja da bin ich auch schon drauf gekommen. Habe ein Objekt der Klasse erstellt und darüber dann auch "window" anzeigen lassen.
Nun startet meine GUI auch!Wenn ich einen Wert zum berechnen angebe und den Button betätige erhalte ich diese Fehlermeldung:
Attribute Qt::AA_ShareOpenGLContexts must be set before QCoreApplication is created. Traceback (most recent call last): File "/home/masterblack/PycharmProjects/Verbrauchsrechner/Verbrauchsrechner.py", line 38, in berechne eingegebener_zaehlerstand = spin_box.spin_box_eingabe.value() ^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'PySide6.QtWidgets.QSpinBox' object has no attribute 'spin_box_eingabe' Process finished with exit code 0
Hier der neue Quelltext:
import sys from PySide6 import QtWidgets from PySide6.QtCore import QFile, QIODevice from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QLabel, QSpinBox, QWidget from Abrechnung import Abrechnung class Verbrauchsrechner: """Main Klasse hier startet das Programm""" def __init__(self): ui_file_name = "GUI/verbrauchsrechner_gui.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() self.window = loader.load(ui_file) ui_file.close() if not self.window: print(loader.errorString()) sys.exit(-1) # window.show() # Slot eingerichtet self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK") self.button.clicked.connect(self.berechne) # self.label_list = self.window.findChildren(QtWidgets.QLabel) def berechne(self): spin_box = self.window.findChild(QtWidgets.QSpinBox, "spin_box_eingabe") eingegebener_zaehlerstand = spin_box.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) arbeitspreis_netto = self.window.findChild(QtWidgets.QLabel, "arbeitspreis_netto") arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.window.arbeitspreis_brutto.setText( f"{abrechnung.arbeitspreis_brutto:.2f} €" ) self.window.grundpreis_netto.setText( f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €" ) self.window.grundpreis_brutto.setText( f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €" ) self.window.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.window.gesamtpreis_brutto.setText( f"{abrechnung.gesamtbetrag_brutto:.2f} €" ) self.window.abschlag_bis_jetzt.setText( f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €" ) self.window.abschlag_ende_des_jahres.setText( f"{abrechnung.abschlag_ende_des_jahres:.2f} €" ) self.window.gp_abz_abschlag_bj.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €" ) self.window.gp_abz_abschlag_ej.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}" ) def main(): app = QApplication(sys.argv) verbrauchsrechner = Verbrauchsrechner() verbrauchsrechner.window.show() sys.exit(app.exec()) if __name__ == "__main__": main()
Daher braucht die Klasse drumherum nichts mit Qt zu tun haben und kann ja auch Nicht-Qt Logik und Code enthalten.
Ja, so weit Ich dies beurteilen kann, denke ich auch so.Vielleicht übersehe ich irgendwas Offensichtliches - wie gesagt, bin eher im C++ Umfeld unterwegs.
Schon gut ; )
-
@Master_Shredder said in Wie kann ich Widgets ansprechen(pyside6)?:
spin_box = self.window.findChild(QtWidgets.QSpinBox, "spin_box_eingabe") eingegebener_zaehlerstand = spin_box.spin_box_eingabe.value()
Da
spin_box
ja schon deineQSpinBox
(spin_box_eingabe
) aus der UI Datei ist, muss die untere Zeile lauten:eingegebener_zaehlerstand = spin_box.value()
Da ich annehme dass das alles QLabel sind, kannst du auch alle auf einmal als Liste abfragen
Ja, dies sieht schön Elegant aus. Aber wie greife ich denn dann auf die einzelnen Label zu? Mit Index [0]... wird dies sehr unübersichtlich, unsauber.
Ja sonst bleibt nur alle Widgets, die du ändern willst einzeln abzufragen und dann in der lokalen Klasse weiterzumachen.
In Großen und Ganzen ist der Ansatz den du gewählt hast auch eher ungewöhnt bzw. etwas umständlich.
QUiLoader
wird eher für Widgets bzw. UI Files genutzt, die man "mal eben" zur Laufzeit nachladen will und wo man dann nicht mehr viel verändert. Mit dem Loader kann man ja auch einzelne Widgets bzw. Widget-Plugins aus einer Library dynamisch erstellen/laden.Bei Qt hast du eigentlich 3 Möglichkeiten deine GUI zu erstellen (und zu designen):
- Über den Designer mit .ui Datei, die direkt eingebunden wird
- bei Python muss diese zusätzlich und manuell mit dem UIC Tool in eine .py - Klasse konvertiert werden.
QtCreator mit C++ macht dies automatisiert und wandelt die .ui mituic
in einen C-Header um... Denke mit Python würde sich das auch irgendwie automatisieren lassen (muss ja nur, wenn du Änderungen an der UI-File vornehmen willst und irgendwann ist man ja auch fertig damit)
- bei Python muss diese zusätzlich und manuell mit dem UIC Tool in eine .py - Klasse konvertiert werden.
- mittels
QUiLoader
zur Laufzeit Widgets aus .ui Dateien laden - Ohne Designer die GUI "per Hand" erstellen (sowohl C++ als auch Python)
- Vorteil, man lernt schneller den Umgang mit Qt, ist leider aber gerade für Anfänger nicht so simple. Dafür hat man jedes Element direkt im Code vorliegen wo man es benötigt und muss eher selten mit
findChild(ren)
das konstruierte Widget durchsuchen.
- Vorteil, man lernt schneller den Umgang mit Qt, ist leider aber gerade für Anfänger nicht so simple. Dafür hat man jedes Element direkt im Code vorliegen wo man es benötigt und muss eher selten mit
Im Prinzip sind 1 und 3 die häufigsten Methoden, aber kann verstehen, dass als Python Nutzer es nervig ist, jedes Mal seine UI Datei selbst in eine py-Klasse umzuwandeln. Weshalb wohl viele Python-Beispiele zu
QUiLoader
existieren.
Im C++ Umfeld wird es eher seltener genutzt.Hab vorhin aus eigenem Interesse mal geschaut und Beispiele für den Einsatz vom
QUiLoader
in Python gefunden (etwas "dynamischer" als in deinem Fall).Probier mal deine Klasse
Verbrauchsrechner
an das hier anzupassen:Oder mach ein neues Projekt auf und kopier den Code von dort. Wenn er immer noch nicht funktioniert, scheint irgendwas mit deiner IDE oder der PySide Installation nicht zu stimmen.
Viel Erfolg :)
Edit:
Ob sie "funktioniert" ist schwer zu sagen. Da das Argument mit dem gesucht wird ein String ist funktioniert die Fehleranzeige von PyCharm nicht. Bzw. der Fehler hängt bei clicked().
Unresolved attribute reference 'clicked' for class 'object'
Könnte man beispielsweise so testen:
(dann daswindow.show()
in dermain()
wegkommentieren und schauen ob nur der Button aus der UI mit dem entsprechenden Text erscheint)self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK") self.button.show()
Zu der Klasse
Verbrauchsrechner
fällt mir noch ein, dass du ja im Prinzip die Member-Funktionberechne()
mit demclicked()
Signal des Buttons verbinden willst... Eine Signal & Slot Verbindung funktioniert nur zwischen zweiQObject
Klassen.
Was ich vorher gesagt hatte gilt nur für die Einrichtung der Verbindung... dies muss selbst keinQObject
sein, sondern mindestens Zugriff auf Sender und Empfänger haben.
Also muss deinVerbrauchsrechner
mindestens einQObject
werden, damit du den Button mit dem Slot in der Klasse verbinden kannst. Höhere Klassen sind nicht notwendig, da du die gesamte UI durch denQUiLoader
lädst.
Die KlasseForm
in dem verlinkten Beispiel ist ja auch einQObject
.Weiß nicht, wie gut deine Python-Kenntnisse insgesamt sind, aber dazu gäbe es noch Lambdas
z.B.
variable = 42 button.clicked.connect( lambda: self.meineButtonFunktion( variable, button ) ) # oder button.clicked.connect( lambda: print("Hello World") ) # print("Hello World") bzw. allgemein der Code der Lambda Funktion wird jedes Mal ausgeführt # wenn der Button geklickt wird, # ohne dass man eine separate Funktion/Slot benötigt
- Über den Designer mit .ui Datei, die direkt eingebunden wird
-
🤦Ich musste gerade feststellen, dass das Programm in der Konstellation mit findChild() beim Button läuft!
Das was ich angezeigt bekomme sind Warnungen. Also es ist nicht richtig aber auch nicht so falsch, dass es nicht läuft.
import sys from PySide6 import QtWidgets from PySide6.QtCore import QFile, QIODevice from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication from Abrechnung import Abrechnung class Verbrauchsrechner: """Main Klasse hier startet das Programm""" def __init__(self): ui_file_name = "GUI/verbrauchsrechner_gui.ui" ui_file = QFile(ui_file_name) if not ui_file.open(QIODevice.ReadOnly): print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() self.window = loader.load(ui_file) ui_file.close() if not self.window: print(loader.errorString()) sys.exit(-1) # window.show() # Slot eingerichtet self.button = self.window.findChild(QtWidgets.QPushButton, "button_OK") self.button.clicked.connect(self.berechne) def berechne(self): eingegebener_zaehlerstand = self.window.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) self.window.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.window.arbeitspreis_brutto.setText( f"{abrechnung.arbeitspreis_brutto:.2f} €" ) self.window.grundpreis_netto.setText( f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €" ) self.window.grundpreis_brutto.setText( f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €" ) self.window.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.window.gesamtpreis_brutto.setText( f"{abrechnung.gesamtbetrag_brutto:.2f} €" ) self.window.abschlag_bis_jetzt.setText( f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €" ) self.window.abschlag_ende_des_jahres.setText( f"{abrechnung.abschlag_ende_des_jahres:.2f} €" ) self.window.gp_abz_abschlag_bj.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €" ) self.window.gp_abz_abschlag_ej.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}" ) def main(): app = QApplication(sys.argv) verbrauchsrechner = Verbrauchsrechner() verbrauchsrechner.window.show() sys.exit(app.exec()) if __name__ == "__main__": main()
Vielleicht kam es bei dir zu Verwirrung 😅.
In Großen und Ganzen ist der Ansatz den du gewählt hast auch eher ungewöhnt bzw. etwas umständlich.
Na gut, den hatte ich eigentlich nur gewählt, weil mal jemand in einem Python Forum meinte. Man würde es so machen.
Ich habe auch mal im Python Forum gefragt, ob dies vielleicht aus der Python Sicht besser wäre. Nur leider habe ich da noch keine Antwort bekommen.Aber jetzt unter dem Gesichtspunkt, wie du es mir erklärt hast die drei Methoden.
Erstmal zu Nr. 3. Also ich habe Qt gewählt weil ich mir das GUI bauen erleichtern wollte. Habe vorher mit TKinder(Für Python GUI's) gebaut, auch rein Code. Und ja, da wirst du ja wahnsinnig wenn du mal etwas ändern willst. Deshalb Qt-Designer.Also wenn jetzt nichts besonderes aus dem Python Forum oder sonst kommt.
Dann würde ich es mit Methode Nr. 1 machen. Wirklich umständlich ist dies ja nicht. Eine Zeile auf Kommando.
Mir ist halt schon wichtig, dass es gemacht wird wie es gemacht werden sollte!Ich habe dies auch mal auf einem neuen Branch angewendet. Auch mit QMainWindow statt QDialog.
import sys from PySide6.QtCore import QFile from PySide6.QtWidgets import QApplication, QMainWindow from Abrechnung import Abrechnung from GUI.Verbrauchsrechner_gui import Ui_MainWindow class Verbrauchsrechner(QMainWindow): """Main Klasse hier startet das Programm""" def __init__(self): super(Verbrauchsrechner, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) # Slot eingerichtet self.ui.button_OK.clicked.connect(self.berechne) # Slot def berechne(self): eingegebener_zaehlerstand = self.ui.spin_box_eingabe.value() abrechnung = Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand) self.ui.arbeitspreis_netto.setText(f"{abrechnung.arbeitspreis_netto:.2f} €") self.ui.arbeitspreis_brutto.setText(f"{abrechnung.arbeitspreis_brutto:.2f} €") self.ui.grundpreis_netto.setText( f"{abrechnung.grundpreis_tagesgenau_netto:.2f} €" ) self.ui.grundpreis_brutto.setText( f"{abrechnung.grundpreis_tagesgenau_brutto:.2f} €" ) self.ui.gesamtpreis_netto.setText(f"{abrechnung.gesamtbetrag_netto:.2f} €") self.ui.gesamtpreis_brutto.setText(f"{abrechnung.gesamtbetrag_brutto:.2f} €") self.ui.abschlag_bis_jetzt.setText( f"{abrechnung.bisheriger_gezahlter_abschlag:.2f} €" ) self.ui.abschlag_ende_des_jahres.setText( f"{abrechnung.abschlag_ende_des_jahres:.2f} €" ) self.ui.gp_abz_abschlag_bj.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag:.2f} €" ) self.ui.gp_abz_abschlag_ej.setText( f"{abrechnung.aktuelle_kosten_abz_bisheriger_abschlag_ende_des_jahres:.2f}" ) def main(): # eintrag_einfuegen() app = QApplication(sys.argv) window = Verbrauchsrechner() window.show() sys.exit(app.exec()) if __name__ == "__main__": main()
Funktioniert, sichtlich, wunderbar.
-
Hi @Pl45m4 ,
ich habe eine Antwort aus dem Python Forum bekommen. Glücklicher weiße auch von dem der dies mal, mit direkt laden, erwähnte.
"Ich nehme die *.uic-Dateien, weil Quelltext generieren ein unnötiger Zwischenschritt ist, und sich dann auch das Problem nicht stellt generierte Dateien in der Versionsverwaltung zu haben oder nicht.
Gegen die Warnungen kannst Du nicht wirklich was machen, ausser sie komplett abschalten. Damit muss man bei dynamischen Programmiersprachen halt leben das es Grenzen bei der statischen Analyse von Quelltext gibt."
Ich würde es dann nach der von dir erwähnten Methode Nr. 1 machen. Da es für mich, aus der Sicht von Python, keinen Sinnvollen Grund gibt es anders zu machen. Und außerdem kenne ich mich auch nicht so gut aus, dass ich selbst entscheiden könnte ob die Warnungen zu ignorieren sind.
Ja, dies habe ich in dem Post obendrüber ja schon umgesetzt. Kann ich dies so lassen, oder gibt es verbesserung's Vorschläge?
-
@Master_Shredder said in Wie kann ich Widgets ansprechen(pyside6)?:
🤦Ich musste gerade feststellen, dass das Programm in der Konstellation mit findChild() beim Button läuft!
Haha ok, gut zu hören.
Vielleicht kam es bei dir zu Verwirrung 😅.
Ja weil ich mir nicht mehr sicher war wie es in Python funktioniert mit der Verbindung. Ob die Klasse, wo eine Funktion mit einem Signal verbunden ist, ein
QObject
sein muss oder nicht (in C++ kann man nurQObject
Klassen direkt verbinden).
Anscheinend muss es in PySide nicht so sein...Funktioniert, sichtlich, wunderbar.
Ja das ist einer der "empfohlenen Wege", wie man den QtDesigner nutzt. Ob man es so macht, ist ja jedem selbst überlassen.
@Master_Shredder said in Wie kann ich Widgets ansprechen(pyside6)?:
"Ich nehme die *.uic-Dateien, weil Quelltext generieren ein unnötiger Zwischenschritt ist, und sich dann auch das Problem nicht stellt generierte Dateien in der Versionsverwaltung zu haben oder nicht.
Gegen die Warnungen kannst Du nicht wirklich was machen, ausser sie komplett abschalten. Damit muss man bei dynamischen Programmiersprachen halt leben das es Grenzen bei der statischen Analyse von Quelltext gibt."
Die Verwendung vom Designer macht den Code bzw. Anwendung etwas schwerer zu debuggen und für Dritte zu verstehen.
Deswegen wird in professionellen QtWidget-Apps meist kein Designer genutzt (QML/QtDesign Studio ist nochmal eine andere Geschichte). Gerade weil man irgendwann sehr limitiert ist, in dem was man tun/umsetzen kann. Daher ist man dann zwangsläufig auf eine (unübersichtliche) Hybrid-Lösung angewiesen.
Aus dem Grund verzichten viele eher komplett auf den QtDesigner und die .ui Dateien und schreiben ihre GUI selbst. Resultiert dann zwar in mehr Code, ist aber übersichtlicher. Da sieht dann jeder Leser von dem Code direkt was er bekommt (zu erwarten hat) und muss nicht noch irgendwelche XML Blöcke oder automatisch generierte Dateien durchsuchen.Auch wenn du sagst, dass es dir aufwändig vorkommt, alles im Code per Hand zu schreiben, müsstest du ja bei dem
QUiLoader
auch Abstriche machen. Um dort auf die internen Widgets zuzugreifen, ist es dann nötig sie z.B. mitfindChild
imQObject
-Baum vom (durch den Loader erzeugten) Widget zu suchen.
Dazu dann dasuic
Tool (User Interface Compiler), auf das man auch verzichten kann, wenn keine.ui
im Spiel ist (Stichwort Codegenerierung).Betrifft das gesamte Qt Framework, egal in welcher Form und Sprache man es einsetzt.
In C++ durch den UICompiler von Qt:- wird
meinWidget.ui
zuui_meinWidget.h
Diesen Header kann man dann einbinden. Daraus eine Instanz der UI-Klasse erstellen und ist dann in der Lage die Widgets aus der UI - Datei mit
ui->objektname
zu adressieren.In Python entsprechend:
meinWidget.ui
zumeinWidget.py
Alles in "Qt für Python", egal ob PySide oder PyQt wurde von C++ adaptiert, da Qt ein C++ GUI Framework ist. PySide und PyQt sind Bindings, die später dazuentwickelt wurden.
Darum musste auch ein Tool zum Übersetzen der UI Dateien in etwas "Python-Nutzbares" existieren, auch wenn diese zusätzliche Codegenerierung nicht der Philosophie einer "dynamischen" und Interpreter-Sprache (wie Python) entspricht.Da deine UI jetzt nicht besonders komplex ist, wäre es sogar einfacher sie ohne jedliche UI oder den Designer zusammenzusetzen. So würde ich es machen, nach einigen Jahren Erfahrung mit Qt (mag am Anfang aber etwas schwieriger sein)
Der Designer ist verlockend weil es relativ schnell und einfach ist, aber dann muss man auch mit den Kehrseiten leben :)
Entweder die Code-Generierung durchUIC
oder, beimQUiLoader
etwas umständlich auf die Widgets zuzugreifen zu müssen.
Sonst bleibt nur der Weg "zu Fuß" ;-)Im Endeffekt ist alles Geschmackssache bzw. auch etwas abhängig vom Use-Case und Art der App, die man entwickelt (und für wen).
Zu den Warnungen kann ich leider nichts sagen... Möglicherweise kann man die Qt Syntax der IDE beibringen.
Hab dazu den Bugreport hier gefunden:Schon mal VS Code statt PyCharm versucht?
Ich würde es dann nach der von dir erwähnten Methode Nr. 1 machen. Da es für mich, aus der Sicht von Python, keinen Sinnvollen Grund gibt es anders zu machen. Und außerdem kenne ich mich auch nicht so gut aus, dass ich selbst entscheiden könnte ob die Warnungen zu ignorieren sind.
Ja, dies habe ich in dem Post obendrüber ja schon umgesetzt. Kann ich dies so lassen, oder gibt es verbesserung's Vorschläge?
Denke zum Lernen und Herumexperimentieren mit Qt und QtDesigner ist das die beste und einfachste Lösung.
So muss man sich keine Gedanken machen, und hat innerhalb der Klasse überall vollen Zugriff auf die UI mittelsself.ui.<objekt_name>
.
Alles Weitere kommt dann mit der Zeit :) - wird
-
@Pl45m4 said in Wie kann ich Widgets ansprechen(pyside6)?:
Ja das ist einer der "empfohlenen Wege", wie man den QtDesigner nutzt. Ob man es so macht, ist ja jedem selbst überlassen.
Ja so lasse ich es auch jetzt. Das sieht gut aus. Bevor ich mich auf Irrwege mache und am Ende nichts davon verstehe.
Denke zum Lernen und Herumexperimentieren mit Qt und QtDesigner ist das die beste und einfachste Lösung.
So muss man sich keine Gedanken machen, und hat innerhalb der Klasse überall vollen Zugriff auf die UI mittels self.ui.<objekt_name>.
Alles Weitere kommt dann mit der Zeit :)Ja denke ich auch. Dies ist wunderbar :).
OK. Dann dankeschön für die Hilfe.
-