Zugriff auf die Widgets meiner .ui (Pyside6)

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hallo,

ich Versuche Zugriff auf meine Widgets zu bekommen, nur leider ohne Erfolg.
Ich müsste diese doch über den QUiLoader() ansprechen können?
ich habe die .ui direkt eingefügt. Nur leider bekomme ich eine Fehlermeldung bei Ansprache meiner Widgets.

Cannot find reference 'arbeitspreis_netto' in 'QWidget | QWidget'

Woran könnte dies liegen? QWidget ist ja laut QT Doc:

The QWidget class is the base class of all user interface objects.
https://doc.qt.io/qtforpython-6/PySide6 ... ts.QWidget

Und dann habe ich noch das Problem, dass die clicked() Funktion nicht gefunden wird.

Unresolved attribute reference 'clicked' for class 'object'

Hier der Quelltext:

Code: Alles auswählen

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
# Weiß nicht ob außer QApplication die anderen noetig sind war nur ein Versuch

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()
MFG Master_Shredder
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Da zwei Zeilen davor auf spin_box_eingabe zugegriffen wird, liegt es offensichtlich nicht an der Ansprache deiner Widgets, sondern daran, dass tatsächlich kein Widget den Namen `arbeitspreis_netto` hat.
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Der Fehler ist bei jedem Aufruf "self.window.*" in berechne()

Cannot find reference 'BEZEICHNER' in 'QWidget | QWidget'
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

Hängt das nicht davon ab was

Code: Alles auswählen

Abrechnung.eingabe_zaehlerstand(eingegebener_zaehlerstand)
zurückliefert? ach, ich sehe gerade das diese Namen auch bei self.windows. benutzt werden. Kommen die Fehler denn von

Code: Alles auswählen

self.window.arbeitspreis_netto.setText
oder von

Code: Alles auswählen

f"{abrechnung.arbeitspreis_netto:.2f} €"
?
Beides mit `arbeitspreis_netto`
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

@Master_Shredder: Kannst du bitte einmal den Code zeigen, den du tatsächlich ausführst und die dazugehörige Fehlermeldung. Denn das passt ja im ersten Post nicht und es ist einfacher beides in einem Post zu haben.
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

@sparrow Sorry, mir ist gerade ein Fehler aufgefallen!


Mal noch eine allgemeine Frage an @all. Wie würdet ihr aus der Sicht von Python QT bzw. pyside einfügen? So wie ich, direkt über den "Loader", oder mit UIC konvertiert in Pythoncode, File?
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Also, ich muss mich etwas berichtigen.
Ich bekomme Warnungen die lauten:

Unresolved attribute reference 'clicked' for class 'object'
Cannot find reference 'spin_box_eingabe' in 'QWidget | QWidget' (SpinBox für Wert Eingabe)
Cannot find reference 'arbeitspreis_netto' in 'QWidget | QWidget'
...
Und so weiter für jedes Label Widget in berechne()

Mir ist gerade mal aufgefallen dass das Programm selbst funtioniert.

Es scheint also nicht richtig realisiert. Wie könnte man dies ändern?

Meine IDE ist PyCharm

Code: Alles auswählen

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()
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: 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.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Hi, @__blackjack__, danke für die Antwort.
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.
Also jemand aus den C++ Bereich der mit Qt arbeitet meinte. Dass das direkte laden mittels Loader:

"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."

Ich habe mich gefragt ob es aus der Sicht von Python sinnvoller wäre es so zu tun. Aber jetzt aus Bequemlichkeit würde **ich** es nicht tun. Da habe ich lieber meine Warnungen weg. So gut kenne ich mich nicht aus, dass ich dies selbst bestimmen könnte.

Aber danke.
Benutzeravatar
sparrow
User
Beiträge: 4525
Registriert: Freitag 17. April 2009, 10:28

Die Vorteile, die in meinen Augen übewiegen, hat __blackjack__ bereits genannt. Quelltext aus den uic-Dateien zu generieren ist ein unnötiger und somit fehleranfälliger Zwischenschritt.
An die Warnungen solltest du dich gewöhnen. Das haben dynamische Sprachen nun einmal so an sich. Und diese "Warnungen" kommen ja auch nicht aus Python sondern aus der IDE, die du verwendest.
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Master_Shredder: In C++ ist das anders weil man ja sowieso einen mehrstufigen Übersetzungsprozess hat. Wenn man dem Qt-Präprozessor und C++ sowieso schon zwingend zum Übersetzen des Quelltextes braucht, stört das auch nicht weiter wenn man da noch einen „*.ui → *.cpp“-Schritt integriert. Derjenige der die Anwendung aus dem Quelltext laufen lassen will braucht dafür all die Werkzeuge.

Bei Python passiert normalerweise alles dynamisch zur Laufzeit. Da ist ein zusätzlicher statischer Übersetzungsschritt ungewöhnlich und umständlich. Und man steht dann auch vor der schon erwähnten Frage, wie man das mit den generierten Quelltexten und der Versionsverwaltung handhabt. Generiertes und/oder Redundantes gehört dort eigentlich nicht hinein, also wie bei C++ eigentlich nur die *.ui-Dateien. Das bedeutet dann aber, dass jeder der das dann benutzen will das Entwicklungswerkzeug zum Übersetzen von *.ui nach *.py braucht, und das in einem extra Schritt vor dem ersten ausführen des Programms verwenden muss. Das ist in Python ein Fremdkörper. Eben ungewöhnlich und umständlich.

Ich kann das mit dem „und wo man dann nicht mehr viel verändert“ auch nicht ganz nachvollziehen. Denn auch in C++ eignen sich dynamisch geladene *.ui-Dateien ja gerade für GUIs wo man an der GUI noch was ändert, weil man das oft tun kann, ohne das Programm neu übersetzen zu müssen, solange keine Wigdets hinzukommen oder wegfallen von denen das Programm wissen muss.

Gerade mal auf dem Desktoprechner geschaut: 1.377 *.ui-Dateien vorhanden. So ungewöhnlich scheint mir das also nicht zu sein. Wobei Glade, das Gtk-Gegenstück zum Qt-Designer, auch XML-Dateien mit dieser Dateiendung verwendet.

Edit: Man kann in Python natürlich auch das gleiche machen was man in C++ an der Stelle machen muss: „casten“. Das meint Dein C++-Programmierer vielleicht auch mit umständlich. Ist in Python ja eigentlich nicht notwendig wenn man nicht mit Typannotationen arbeitet.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Master_Shredder
User
Beiträge: 40
Registriert: Freitag 2. Februar 2024, 18:25

Bei Python passiert normalerweise alles dynamisch zur Laufzeit. Da ist ein zusätzlicher statischer Übersetzungsschritt ungewöhnlich und umständlich.
Ok. Das klingt für mich schon wieder Plausibler.
Man kann in Python natürlich auch das gleiche machen was man in C++ an der Stelle machen muss: „casten“. Das meint Dein C++-Programmierer vielleicht auch mit umständlich. Ist in Python ja eigentlich nicht notwendig wenn man nicht mit Typannotationen arbeitet.
Also gut, Typ annotaion steht bei mir schon auf der To Do Liste. Ich beschäftige mich nebenbei etwas mit clean code, und habe mir Typ annotation schon angesehen.

Verstehe ich es dann richtig, dass das direkten einbinden für mich dann nicht mehr in Frage kommt?
Benutzeravatar
Dennis89
User
Beiträge: 1516
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

laut Wikipedia versteht man unter "clean code" unter anderem, sauberen und verständlichen Code. Da in Python Typ-Annotationen dem Interpreter egal sind, kann das eher zu verwirrendem Code führen.
Ich hätte bei folgendem Code Fragezeichen im Kopf:

Code: Alles auswählen

#!/usr/bin/env python

def do_something(value: int) -> float:
    return str(value * 2)


def main():
    print(do_something(3.14))


if __name__ == '__main__':
    main()


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hat die Typprüfung aber auch:

Code: Alles auswählen

$ mypy --check-untyped-defs forum32.py 
forum32.py:5: error: Incompatible return value type (got "str", expected "float")  [return-value]
forum32.py:9: error: Argument 1 to "do_something" has incompatible type "float"; expected "int"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Antworten