OOP Schwierigkeiten bei TKinter

Fragen zu Tkinter.
Antworten
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

Hallo allerseits,

ich bin noch relativ neu bei Python und komme bei einem Problem nicht weiter (logisch, sonst wäre ich ja nicht hier...)

Ich habe ein Programm geschrieben, dass Rezepte aus einer DB (sqlite3) ausliest und neue Rezepte einträgt. Das Programm habe ich im zweiten Anlauf dann auch so hinbekommen, dass ich die Logik komplett in Funktionen und Klassen gepackt habe und das Programm in der Kommandozeile alles macht was es soll (m.E. einigermaßen Objektorientiert). Der nächste Schritt ist jetzt die Programmierung eines GUI.
Deshalb jetzt meine Fragen:
- In vielen Beispielen sieht man, dass jedes neue Fenster eine neue Klasse bekommt. Das erschließt sich mir noch nicht so ganz, wieso das gemacht wird. Könnte ich nicht z.B. auch nur Funktionen verwenden? Oder erleichter das Organisieren in Klassen das "löschen" bzw. schließen eines Fensters? Dass ich es aus Gründen der Übersichtlichkeit nicht einfach untereinander code, wie ich das bei meinen aller ersten Versuchen noch gemacht habe erschließt sich mir mittlerweile. Die Frage ist offensichtlich noch etwas theoretisch, aber ich steige eben leider nicht ganz durch. Mein Argument wäre nämlich, dass ich jedes Fenster ja nur ein mal brauche und deshalb der Vorteil einer Klasse (jedes Objekt hat die gleichen Eigenschaften) ja total umsonst sind, da ich für 10 Fenster auch 10 Klassen brauche.
- Wenn ich einen Button erstelle, dann soll dieser ja eine Funktion aufrufen. In welcher Instanz erstelle ich denn diese Funktion? Innerhalb der Klasse? Dann kommt bei mir die Fehlermeldung, die Funktion sei nicht bekannt (siehe Code unten). Wenn ich die Funktion außerhalb der Klasse erstelle ist die Variable die ich abfrage nicht bekannt.
- Der Code beschreibt das zweite Fenster innerhalb des Programms (deshalb Toplevel). Vorher gibt es ein Fenster, das eine Auswahlmöglichkeit bietet, ob ein Rezept abgefragt oder ein neues eingetragen werden soll. Dort wird dann die Klasse "RezeptEingabe" aufgerufen und entsprechend der vorliegende Code ausgeführt. Aktuell ist es noch so, dass ich dann das zweite Fenster öffne und das erste weiterhin offen bleibt, ich habe also 2 große Fenster auf dem Bildschirm. Das ist ja nicht wirklich schön. Wie könnte ich denn immer nur ein Fenster geöffnet haben? Ich hatte überlegt mit Frames zu arbeiten, diese dann zu entfernen. Ich hab mich da aber noch nicht genauer eingelesen. Ist das grundsätzlich ein Möglicher Ansatz oder welche Ideen habt ihr? Dann würde ich mich damit nochmal genauer auseinander setzen, wenn die jetzigen Probleme gelöst sind.

Angehängt der Code, ich danke euch schon mal für eure Hilfe und bleibt Gesund!!

Code: Alles auswählen

import tkinter as tk

####--- Wenn die Funktion von unten hier steht kommt der Fehler "name" sei nicht bekannt

class RezeptEingabe:

####----------An dieser Stelle kommt ein NameError: Funktion "abfragen" ist nicht bekannt--------#

    def abfragen():
    # Die Funktion soll die Variable übernehmen, die wird dann an den "Logikteil" übergeben und dann passiert Magie
        gerichtname = name.get()
        print(gerichtname)

    def __init__(self):
    
        #Erzeugen des Fensters
        fenster = tk.Toplevel()
        fenster.geometry("400x400")
        fenster.title("Rezepteingabe")
        #Dialog, abfragen wie das Gericht heißt
        text3 = tk.Label(fenster, text = "Name des Gerichts?")
        text3.grid(row = 2, column = 0, sticky = "w")
        name = tk.Entry(fenster, bg = "white")
        name.grid(row = 2, column =1, sticky = "e")
        
        #Erstellen zweier Knöpfe, hier wird die Funktion aufgerufen.
        knopfWeiter = tk.Button(fenster, text = "Okay", command = abfragen)
        knopfWeiter.grid(row = 3, column = 1)
        knopfAbbrechen = tk.Button(fenster, text = "Abbrechen", bg = "red")
        knopfAbbrechen.grid(row = 3, column = 0)
.
Viel zu lernen, ich noch habe.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Mit den Klassen musst Du Dich nochmal genauer auseinandersetzen. Dieses `self` ist dazu da, auf das Exemplar der Klasse zugreifen zu können. Das nutzt Du gar nicht.
Bei GUIs hast Du ja Eingabefelder und Knöpfe. Und um von den Knopfenaktionen zu den Eingaben zu kommen, dafür braucht man `self`.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Deine Ueberlegung zur Rolle von Klassen, wenn man doch pro Klasse nur ein Exemplar benoetigt, hat durchaus Berechtigung. Es gibt nicht umsonst ein Entwurfsmuster das sich "Singleton" nennt, bei dem man sicherstellt, dass von einer Klasse nur eine Instanz existieren kann. Trotzdem ist die Nutzung einer Klasse angezeigt, denn die Frage danach, wieviele Exemplare existieren, ist ja nur ein Aspekt. Ein anderer ist die Kapselung und Koppelung von Zustand und Code. Und DEN braucht du. Denn die Alternative waere sonst, dass deine 10 Fenster sich einen unueberschaubaren Klumpatsch an globalem Zustand irgendwie teilen. Da verlierst du den Ueberblick und verhedderst dich. Darum also auch da: Klassen.

Zur Frage warum "abfragen" nicht funktioniert hat Sirius3 ja schon was gesagt.

Zum Layout: das hier ist eine moegliche Loesung: https://stackoverflow.com/questions/754 ... in-tkinter
seb-korn
User
Beiträge: 8
Registriert: Mittwoch 25. März 2020, 23:15

Alles klar, vielen Dank euch zwei für die schnellen Antworten! Ich hatte auch schon das Gefühl, dass ich mit den Klassen noch nicht ganz durchgestiegen bin. Jetzt werde ich mich da nochmal einlesen. Die Begründung von deets ergibt Sinn und der Beitrag zum Layout scheint mir auch sinnvoll, den werde ich noch genau durcharbeiten. Falls nochmal Fragen auftauchen melde ich mich hier im Forum! :)
Viel zu lernen, ich noch habe.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@seb-korn: Die Frage ist nicht nur wie oft man das Fenster braucht, sondern das man bei GUIs in der Regel ”Funktionen“ braucht die einen gemeinsamen Zustand haben den man sich über Aufrufe hinweg merken muss. Beispiel: zwei Eingabefelder, ein Label für die Ausgabe, und ein Button der Zahlwerte aus den Eingabefeldern addiert und das Ergebnis im Label anzeigt. Dem Button muss man ja jetzt per `command` eine Funtkion mitgeben die keine Argumente erwartet, aber diese Funktion muss auf die beiden Eingabefelder und das Label zugreifen können, braucht die also als Argumente. Das kann man in so einem einfachen Beispiel noch mit `functools.partial()` lösen, aber je komplexer es wird und um so mehr Werte man bei solchen Aufrufen durch die Gegend reichen muss, kommt man irgendwann nicht mehr darum herum das alles zu einem Objekt zusammen zu fassen.

Schau Dir mal beispielsweise den Code in diesem Beitrag an, was da `_run()` am Ende alles an Argumenten mitgegeben werden muss:
viewtopic.php?f=31&t=48016#p363124

Das Beispiel mit dem addieren mit Funktionen:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial


def do_calculation(first_operand_entry, second_operand_entry, result_label):
    first_operand = float(first_operand_entry.get())
    second_operand = float(second_operand_entry.get())
    result_label["text"] = first_operand + second_operand


def main():
    root = tk.Tk()

    first_operand_entry = tk.Entry(root)
    first_operand_entry.pack()
    second_operand_entry = tk.Entry(root)
    second_operand_entry.pack()
    button = tk.Button(root, text="Addiere")
    button.pack()
    result_label = tk.Label(root)
    result_label.pack()

    button["command"] = partial(
        do_calculation, first_operand_entry, second_operand_entry, result_label
    )

    root.mainloop()


if __name__ == "__main__":
    main()
Und mit einer Klasse:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk


class AddUI(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self)
        self.first_operand_entry = tk.Entry(master)
        self.first_operand_entry.pack()
        self.second_operand_entry = tk.Entry(master)
        self.second_operand_entry.pack()
        tk.Button(master, text="Addiere", command=self.do_calculation).pack()
        self.result_label = tk.Label(master)
        self.result_label.pack()

    def do_calculation(self):
        first_operand = float(self.first_operand_entry.get())
        second_operand = float(self.second_operand_entry.get())
        self.result_label["text"] = first_operand + second_operand


def main():
    root = tk.Tk()

    add_ui = AddUI(root)
    add_ui.pack()

    root.mainloop()


if __name__ == "__main__":
    main()
Neben dem Umstand das man hier die Bestandteile der GUI in einem Objekt zusammengefasst hat und das bei Methoden automatisch als erstes Argument übergeben wird, braucht man dem Button nicht mehr an einen Namen binden, und man kann von anderen Widgets wie `Frame` erben. So kann man den Inhalt des Fensters wahlweise in ein Hauptfenster (`Tk`) stecken oder in ein weiteres Fenster (`Toplevel`) falls man schon ein Hauptfenster hat. Und man kann diesen `Frame` auch mit anderen `Frame`\s kombinieren wenn man noch andere Sachen in einem Fenster, oder vielleicht auch erst einmal nur einem anderen `Frame` anzeigen möchte.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten