In einer *.py Datei auf eine *.ui Datei zugreifen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Sonntag 28. Juni 2020, 16:12

Hallo,

ich mache gerade erste Versuche mit dem Qt-Creator. Mit dem Designer habe ich ein Fenster mit verschiedenen WIdgets erstellt. Habe auch jedem Widgets einen "sinnvollen" Namen gegeben.
Qt-Creator erstellt daraus eine *.ui Datei und hat mir auch eine main.py-Datei erstellt.
Wenn ich main.py ausführe wird mein erstelltes Fenset ordentlich angezeigt.

Meine Frage ist, wie ich nun aber in main.py auf die WIdgets zugreifen kann? Ich muss denen ja noch Funktionen und Inhalte hinzufügen.

Eine Combobox heißt zm Beispiel "Auswahlmenü", aber wenn ich darauf zugreifen will mit "self.Auswahlmenü(...)"

Bekomme ich die Fehlermeldung:
AttributeError: 'widgets' object has no attribute 'Auswahlmenü'

Ich komme hier nicht ganz weiter.

Habe auch schon die *.ui in eine *.py Datei umwandeln lassen, aber der Code der dabei rauskommt ist mehr als unübersichtlich. Würde gerne das Layout von der Funktion trennen. Vorallem ist es auch angenehmer bei nachträglichen Änderungen.

Danke schon einmal für eure Zeit.

Viele Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 6366
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Sonntag 28. Juni 2020, 18:16

@Saturn89: Wie sieht denn Deine `main.py` aus?
long long ago; /* in a galaxy far far away */
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Sonntag 28. Juni 2020, 19:21

Code: Alles auswählen

#! usr/bin/env Python3
# This Python file uses the following encoding: utf-8

import sys
import os
import pandas as pd

from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile
from PySide2.QtUiTools import QUiLoader


class widgets(QWidget):
    def __init__(self, parent=None):
        super(widgets, self).__init__()
        self.load_ui()

    def load_ui(self):
        loader = QUiLoader()
        path = os.path.join(os.path.dirname(__file__), "form.ui")
        ui_file = QFile(path)
        ui_file.open(QFile.ReadOnly)
        loader.load(ui_file, self)
        ui_file.close()


if __name__ == "__main__":
    app = QApplication([])
    widget = widgets()
    widget.show()
    sys.exit(app.exec_())
So sieht der aus, der wurde von Qt-Creator erstellt.

Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 6366
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Sonntag 28. Juni 2020, 19:46

@Saturn89: Da fehlt jetzt aber die Zeile die einen Fehler verursacht und ich bin mir ziemlich sicher das der Qt-Designer da keinen Import für `pandas` reingeschrieben hat.

Klassennamen schreibt man in Python in MixedCase, also `Widgets`, allerdings ist der Name nicht wirklich passend, denn es ist ja *ein* Widget. Und dann wäre der Name immer noch *sehr* generisch gewählt.

Ich würde aus `load_ui()` auch keine eigene Methode machen.
long long ago; /* in a galaxy far far away */
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Sonntag 28. Juni 2020, 21:41

Oh das mit pandas stimmt natürlich. Das war noch von Versuchen drin .
Die Methode wurde vom Qt-Creator erstell. Danke für den Hinweis mit MixedCase und der Namenswahl. Für den Versuch habe ich mir bei dem Namen nicht viel Mühe gegeben.

Ja die Zeile fehlt, da ich nur geraten habe wie es funktionieren könnte. Ich bin da absolut planlos. Es gehört aber schon in „def load_ui“? Also wenn wir es als Methode lassen. Bin aber an der Variante ohne Methode auch interessiert, vorallem wenn es besser wäre.
Wenn ja wie würde den eine Zeile aufgebaut sein?
Wie wenn ich mit tkinter auf ein Optionsmenü zB zugreife?

Ich hoffe ich drücke mich verständlich aus :D

Danke für dein Interesse und die Hilfe
Grüße
Sirius3
User
Beiträge: 12195
Registriert: Sonntag 21. Oktober 2012, 17:20

Montag 29. Juni 2020, 04:55

Eigentlich funktioniert es so, wie du es umschreibst. Daher ist es auch so wichtig, genau den Code zu sehen, der nicht funktioniert. Und die genaue Fehlermeldung dazu.
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Dienstag 30. Juni 2020, 06:45

Guten Morgen und sorry für die späte Antwort.

Ersteinmal Danke für eure Hilfe, hier habe ich den Code mit meinen Versuchen:

Code: Alles auswählen

#! usr/bin/env Python3
# This Python file uses the following encoding: utf-8
import sys
import os
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile
from PySide2.QtUiTools import QUiLoader
class widgets(QWidget):
    def __init__(self, parent=None):
        super(widgets, self).__init__()
        self.load_ui()
    def load_ui(self):
        loader = QUiLoader()
        path = os.path.join(os.path.dirname(__file__), "form.ui")
        ui_file = QFile(path)
        ui_file.open(QFile.ReadOnly)
        loader.load(ui_file, self)
        ui_file.close()
        self.auswahl = list(["test", "test2"])
        self.choicebox_material = QComboBox(
            self, *self.auswahl
        )
if __name__ == "__main__":
    app = QApplication([])
    widget = widgets()
    widget.show()
    sys.exit(app.exec_())
Hier die Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/user/xxx/main.py", line 32, in <module>
    widget = widgets()
  File "/home/user/xxx/main.py", line 15, in __init__
    self.load_ui()
  File "/home/user/xxx/main.py", line 25, in load_ui
    self.choicebox_material = QComboBox(
NameError: name 'QComboBox' is not defined
07:25:54: /usr/bin/python exited with code 1
Mir ist klar, das "QComboBox" hier der Fehler ist, mit tkinter würde ich "tkinter.Optionsmenu" schreiben.
Mir ist aber nicht klar, wie ich es hier ansprechen muss. Die erstellte ComboBox habe ich im Designer "choicebox_material" genannt, wenn ich das an die Stelle von "QComboBox" schreibe bekomme ich die Meldunf "undefined name 'choicebox_material'.

Vielen Dank und freundliche Grüße

Edit: Die Verbesserung mit MixedCase werde ich bei meinem richitgen Code dann auf jeden Fall anwenden.
Sirius3
User
Beiträge: 12195
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 30. Juni 2020, 07:16

Würdest Du tatsächlich eine neue QComboBox erstellen wollen, müßtest Du sie aus QWidgets importieren, aber das willst Du ja gar nicht.
Du willst die vorhandene choicebox_material mit Daten füllen.
`super` benutzt man ohne Parameter, dafür fehlt der Parameter beim __init__-Aufruf. Dem Dateinamen definiert man am besten als Konstanten am Anfang der Datei. Der list-Aufruf ist bei einer Liste überflüssig.
Das was unter `if __name__...` steht, sollte in eine Funktion `main` wandern.

Code: Alles auswählen

#! usr/bin/env Python3
# This Python file uses the following encoding: utf-8
import sys
import os
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile
from PySide2.QtUiTools import QUiLoader

FORM_UI_FILENAME = os.path.join(os.path.dirname(__file__), "form.ui")

class Widgets(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        loader = QUiLoader()
        ui_file = QFile(FORM_UI_FILENAME )
        ui_file.open(QFile.ReadOnly)
        loader.load(ui_file, self)
        ui_file.close()
        self.choicebox_material.addItems(["test", "test2"])

def main():
    app = QApplication([])
    widget = Widgets()
    widget.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Dienstag 30. Juni 2020, 08:01

Danke für die schnelle Antwort.

Habe deinen Code gleich gegen meinen ersetzt, allerdings funktioniert das Ganze noch nicht.
Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/user/xxx/main.py", line 29, in <module>
    main()
  File "/home/user/xxx/main.py", line 23, in main
    widget = Widgets()
  File "/home/user/xxx/main.py", line 18, in __init__
    self.choicebox_material.addItems(["test", "test2"])
AttributeError: 'Widgets' object has no attribute 'choicebox_material'
08:46:17: /usr/bin/python exited with code 1
Die "choicebox_material" existiert aber, kann hier leider keine Bilder hochladen, hatte einen Screenshot gemacht.
Da du in deinem Code "Widgets" geschrieben hast, habe ich im "Designer" das "widgets" auch zu "Widgets" geändert. Das brachte aber keine Änderung.

Hast du noch eine Idee, wo ich einen Fehler gemacht haben könnte?

Danke und viele Grüße :)
Sirius3
User
Beiträge: 12195
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 30. Juni 2020, 09:02

PySide2 verhält sich hier anders als PyQt.
Der UI-Loader erzeugt ein Widget und das kann man nicht verhindern. Man kann also keine eigene Widget-Klasse definieren, sondern kann nur einen Wrapper schreiben.

Code: Alles auswählen

import sys
import os
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile
from PySide2.QtUiTools import QUiLoader

FORM_UI_FILENAME = os.path.join(os.path.dirname(__file__), "form.ui")

class Form:
    def __init__(self, parent=None):
        self.widget = QUiLoader().load(FORM_UI_FILENAME, parent)
        self.widget.choicebox_material.addItems(["test", "test2"])

def main():
    app = QApplication([])
    form = Form()
    form.widget.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Dienstag 30. Juni 2020, 09:38

Oh das hätte ich ja nie alleine hinbekommen, aber jetzt funktioniert es, vielen Dank dafür ! :)

Allerdings sind mir noch weitere Fragen aufgekommen, hätte gerne das vor der Auswahl eine Bezeichnung in der ComboBox steht, also zum Beispiel "Material auswählen". Das soll dort stehen wenn noch nichts ausgewählt wurde.
Mit tkinter habe ich dafür eine tkinter.StrinkVar definiert und das beim erstellen des Optionsmenü mit in die Klammer geschrieben. Geht das hier auch so ähnlich?

Das gleiche hätte ich auch gerne in Eingabefelder, in der ich eine Texteingabe vom Benutzer erwarte. Allerdings sollte das nicht so sein, dass dort steht "Hier Text eingeben" und dass der Benutzer den Inhalt vor seiner Eingabe erst löschen muss, sondern das mit dem anklicken der vordefinierte Inhalt ("Hier Text eingeben") verschwindet.

Achso und dann noch zur Code-Erstellung: Ich habe mehrere Widgets, definiere ich die alle in "def__init__"? Vorallem muss Python erst noch eine Datei auslesen, aus der die Inhalte für die Widgets später kommen. Würde das gerne übersichtlich aufbauen.

Hoffe ich habe mich verständlich ausgedrückt :D

Grüße
Sirius3
User
Beiträge: 12195
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 30. Juni 2020, 09:59

Du kannst ja selbst im Designer die Optionen anschauen. QLineEdit kennt placeholderText, QComboBox nicht.

Was Du Widgets nennst, ist ja normalerweise ein Fenster. Wenn Du mehrere Fenster hast, kannst Du die auch alle in einer Klasse erzeugen, oder Du machst mehrere davon, je nachdem wie Deine Abhängigkeiten sind.
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Dienstag 30. Juni 2020, 10:11

In den Optionen der QComboBox hatte ich geschaut und dort nichts gefunden, aber laut deiner Aussage ist das dann nicht möglich?

Mit Widgets meinte ich Objekte wie "ComboBox," "Eingabefelder" und soweiter.

Also ich habe eine Fenster, das beinhaltet vier "QComboBoxen" und der Inhalt dafür wird aus einer extra Datei ausgelesen. Gehört das Auslesen und das befüllen der ComboBoxen alles in "def__init__"?
Wenn ich dafür eine extra Methode machen soll, muss ich dann dabei etwas beachten?

Danke und Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 6366
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dienstag 30. Juni 2020, 10:25

@Saturn89: Üblicherweise versucht man die Programmlogik von der GUI zu trennen, also die Programmlogik eigenständig zu haben und die GUI dann da drauf zu setzen. In der Regel in dem man der GUI die Programmlogik als Argument übergibt. Das auslesen der Datei würde dann eher *vor* dem Aufruf der `__init__()` passieren, das befüllen der Komboboxen dann in der `__init__()` weil das ja zur Initialisierung der GUI gehört.
long long ago; /* in a galaxy far far away */
Saturn89
User
Beiträge: 12
Registriert: Montag 27. April 2020, 20:29

Dienstag 30. Juni 2020, 10:33

Okay vielen Danke für die Information.

Dann werde ich mich mal darum kümmern. :D

Grüße
Antworten