@Marvin75854: Also wie gesagt ist das mit den magischen Zahlen anhand denen entschieden wird was getan werden soll ziemlich unübersichtlich. Da würde ich mindestens mal `enum.Enum`-Objekte für verwenden, denen kann man einen beschreibenden Namen geben, den man dann im Code verwenden kann. Das kann auch helfen Fehler zu vermeiden oder zu finden, weil man bei Ausgaben dann keine nichtssagende Zahl mehr bekommt, und auch nicht aus versehen gleich Konstanten mit unterschiedlichen Bedeutungen aus versehen vergleichen kann. Und wenn man dann anhand von `Enum`-Werten unterschiedliches Verhalten über ``if``/``elif``-Kaskaden realisiert, könnte man an der Stelle schon mal schauen *wie* sich das Unterscheidet und ob man diese Unterscheidungen nicht als Daten ausdrücken kann, oder eventuell sogar als Datentypen mit Verhalten, also Methoden.
Grid als Layout ist ja okay, es ging mehr mehr darum dass das alles in *einem* Grid steckt. Wenn man da jetzt Änderungen vornehmen will, muss man sehr wahrscheinlich ziemlich viele `grid()`-Aufrufe anpassen.
Eine Klasse mit *einem* Eingabefeld ist wahrscheinlich nur in wenigen Fällen sinnvoll. Ein Widget in dem man beispielsweise einen Punkt eingeben kann der aus X- und Y-Koordinate besteht, hat ja schon mal mindestens zwei Eingabefelder, eins für jeden Bestandteil. Und dann noch zwei Label 'X' und 'Y', damit man weiss welches Eingabefeld für welche Koordinate gedacht ist. Und vielleicht noch ein Label das beschreibt was dieser Punkt bedeutet. Da sind dann schon fünf Widgets, plus ein `Frame` von dem man das ableiten kann, wo die Dinge drin stecken, und den man dann in der GUI platziert. Und wenn man das eine Zelle weiter rechts oder tiefer haben möchte, kann man diesen gesamten Verbund von fünf Widgets mit einem `grid()`-Aufruf wo anders hin setzen und muss nicht jedes Label und jedes Eingabefeld einzeln verschieben. Und man kann aussehalb der Klasse diesem Verbund einen Namen geben, statt mindestens den zweien die man sonst für die Eingabefelder benötigt um später an die Werte zu kommen.
In der `main()`-Funktion steht in der Regel nur so etwas, hier jetzt mal mit super-generischen Namen und unter der Annahme das die Programmlogik auch einen Zustand hat, und deshalb in einer Klasse zusammengefasst ist, was nicht zwingen der Fall sein muss, wenn man die Programmlogik auch zustandslos von der GUI aus verwenden kann:
Code: Alles auswählen
def main():
root = tk.Tk()
logic = ProgramLogic()
frame = MainFrame(root, logic)
frame.pack()
root.mainloop()
In der `MainFrame.__init__()` wird die GUI in dem Frame aufgebaut. Das heisst `MainFrame` hat die Widgets die man später noch braucht, um Werte abzufragen oder Ausgaben/Anzeigen zu machen, als Attribute. Wobei das nicht direkt die Grundwidgets sein müssen, also das was aus dem Tkinter-Modul kommt, sondern eben auch selbst geschriebene Widgets sein können, die aus mehreren Teilen bestehen.
Um an Zahlenwerte aus solchen selbst geschriebenen Widgets zu kommen, bietet man den gleichen Weg wie die vorhandenen Einzelwidgets: Man schreibt Methoden die diese Werte abfragen, und gegebenenfalls auch prüfen und umwandeln. Man möchte zum Beispiel nicht erst in der Programmlogik Zeichenketten in Zahlen umwandeln wenn man Zahlen benötigt. Das sollte schon im GUI-Teil passieren. Der ist auch für eine Rückmeldung an den Benutzer zuständig. Wenn der beispielsweise bei einem Punkt nicht beide Koordinaten eingibt, oder in eines der Felder einfach 'Hallo' eintippt, dann sollten diese Werte gar nicht erst in die Berechnungsfunktion kommen, sondern schon im GUI-Code zu einer Fehlermeldung führen, damit der Benutzer das korrigieren kann. Wenn man nett ist, setzt man dann auch gleich den Fokus auf das Eingabefeld mit dem ungültigen Wert und wählt den Inhalt aus.
Mal (völlig ungetestet) ein Beispiel für eine GUI die einen Start- und einen Endpunkt, und eine Anzahl von Schritten erfragt und dann eine Funktion damit aufruft:
Code: Alles auswählen
class MainFrame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.start_point_entry = PointEntry(self, 'Start')
self.start_point.grid(row=0, column=0)
self.end_point_entry = PointEntry(self, 'End')
self.end_point_entry.grid(row=0, column=1)
frame = tk.Frame(self)
tk.Label(frame, text='Steps:').pack(side=tk.LEFT)
self.steps_entry = tk.Entry(frame, textvariable=self.steps_var)
self.steps_entry.pack(side=tk.LEFT)
frame.grid(row=1, column=0)
tk.Button(self, text='Go', command=self.on_go).grid(row=1, column=1)
def on_go(self):
try:
steps = int(self.steps_entry.get())
except ValueError:
# TODO Hier ein Fehlerfenster öffnen.
self.steps_entry.set_focus()
self.steps_entry.select_range(0, tk.END)
else:
do_something_with_two_points_and_a_number_of_steps(
self.start_point_entry.get_point(),
self.end_point_entry.get_point(),
steps,
)
Wobei wie oben schon beschrieben `PointEntry` selbst aus bis zu fünf weiteren Widgets besteht, die hier aber gar nicht mehr auftauchen müssen, weil nur die Schnittstelle nach aussen, also hier die Klasse selbst und eine `get_point()`-Methode von Interesse sind. Ohne diese Klasse gäbe es in der `MainFrame.__init__()` 8 bis 10 mehr GUI-Objekte.
Wieso solltest Du bei etlichen Klassen nicht mehr durchsteigen? Der Witz ist doch gerade das man Details in Klassen kapselt die man dann gar nicht mehr wissen muss um das Programm an sich zu verstehen, die jetzt alle global im Modul rumliegen und die in Rückruffunktionen alle auf einen Schlag stehen. Zum Beispiel brauchst Du die Interna von einem `PointEntry` gar nicht kennen um die gezeigte `MainFrame`-Klasse zu verstehen. Ohne das `PointEntry` wäre aber die `__init__()` und die `on_go()` deutlich umfangreicher. Und das bei nur zwei Punkten. Du hast ja was von 22 Punkten in der GUI geschrieben.
Die Übersicht kommt in der GUI, genau wie sonst auch beim Programmieren dadurch, dass man ein Problem in Teilprobleme zerlegt, und die wieder in Teilprobleme, bis man Teillösungen hat, die sich leicht schreiben und verstehen lassen und zu grösseren Teillösungen zusammensetzen. Solange bis man das Gesamtproblem gelöst hat. Und auf höheren Ebenen braucht man dann die ganzen Variablen und Details auf tieferen Ebenen nicht sehen/verstehen.
Dein Beispiel kann so nicht funktionieren, denn man müsste das `Berechnung`-Objekt ja der GUI übergeben, damit darauf zugreifen kann und Methoden darauf aufrufen kann.
Und die leere `__init__()` und das Einführen neuer Attribute in den Methoden, und dann auch noch jeweils andere, deutet sehr darauf hin das da Funktionen die gar nicht auf diese Weise zusammengehören, in einer Klasse stecken.
Ich hoffe es ist nicht zu verwirrend, aber man sollte nicht so viele Klassen schreiben.
Wenn es eine Funktion ist, dann ist es eine Funktion. Es bringt keinen Vorteil die in eine Klasse zu zwängen. Was man vielleicht zu einem Objekt zusammenfassen kann sind Argumente die Du an Deine Funktion(en) in der Programmlogik übergibst und was die zurückgeben.