diff --git a/tk_first_steps/feet_classes_mvc.py b/tk_first_steps/feet_classes_mvc.py new file mode 100644 index 0000000..23d98f9 --- /dev/null +++ b/tk_first_steps/feet_classes_mvc.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 + +from tkinter import * +from tkinter import ttk + + +# -------------------------- +# Validator +# -------------------------- +class EntryValidator: + def __init__(self, app): + self.app = app + + def check_entry(self, what: str = ""): + """ Validiert, ob Eingabe leer oder eine Fließkommazahl ist. + + :param what: str eingegebene Zeichenkette + :return: boolean + """ + self.app.controller.switch_status(1) + if what == "": + # Nichts eingegeben + return True + else: + try: + # DEBUG + print(what) + what = what.replace(',', '.') + float(what) + return True + except ValueError: + return False + + +# -------------------------- +# Model +# -------------------------- +class FeetToMeterData: + feet: float + meter: float + + def __init__(self): + self.feet = 0.0 + self.meter = 0.0 + + def get_meter(self): + return self.meter + + def get_feet(self): + return self.feet + + def set_feet(self, newfeet: float = 0.0): + self.feet = newfeet + self.calculate_meter() + return True + + def set_meter(self, newmeter: float = 0.0): + self.meter = newmeter + return True + + def calculate_meter(self): + m_value = 0.3048 * self.feet + m_value = round(m_value, 2) + self.set_meter(m_value) + return True + + def print_data(self): + print("{} ft = {} m".format(self.feet, self.meter)) + + +# -------------------------- +# Controller +# -------------------------- +class FeetToMeterController: + def __init__(self, app): + self.app = app + self.status = 1 # 1 - neue Eingabe, 2 - Eingabe verarbeitet + + def switch_status(self, new_status: int = None): + """ Verändert den globalen Status + + :param new_status: neuer Statuswert + :return: None + """ + if not new_status: + if self.status == 1: + self.status = 2 + else: + self.status = 1 + else: + self.status = new_status + + self.app.update_statusbar(self.status) + + def calculate(self, *args): + """ Berechnet aus der eingegebenen Länge in feet die Länge in Metern. + + :param args: + :return: None + """ + try: + stringvalue = self.app.feet.get() + stringvalue = stringvalue.replace(",", ".") + value = float(stringvalue) + self.app.data.set_feet(value) + m_value = self.app.data.get_meter() + self.app.meters.set(m_value) + # Berechnung abgeschlossen. + self.app.data.print_data() + self.switch_status(2) + except ValueError: + pass + return True + + +# -------------------------- +# View / Main Application +# -------------------------- +class FeetToMeter(Tk): + def __init__(self): + super().__init__() + self.controller = FeetToMeterController(self) + self.validator = EntryValidator(self) + self.data = FeetToMeterData() + + self.title("Feet to Meters") + + # Event Handler (Validator) registrieren + self.check_entry_wrapper = self.register(self.validator.check_entry) + + self.s = ttk.Style() + self.create_styles() + self.create_widgets() + self.bind_events() + + def create_styles(self): + """ Erstellen der speziellen Stil-Konfigurationen. + + :return: + """ + self.s.configure('aFrame.TFrame', background="yellow") + self.s.configure('bFrame.TFrame', background="green") + self.s.configure('cFrame.TFrame', background="red") + return True + + def create_widgets(self): + """ Erstellen der Fenster-Bestandteile. + + :return: + """ + # Rahmen im Hauptfenster (aus ttk für Farbanpassung) + mainframe = ttk.Frame(self, padding="3 3 12 12") + mainframe.grid(column=0, row=0, sticky=N + W + S + E) + + # mainframe darf sich ausdehnen. + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + + # Eingabefeld für Länge in feet + self.feet = StringVar() + feet_entry = ttk.Entry(mainframe, width=7, textvariable=self.feet, + validatecommand=(self.check_entry_wrapper, '%P'), + validate='key') + feet_entry.grid(column=2, row=1, sticky=W + E) + + # Einheit-Label für Eingabefeld der Länge in feet + ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=W) + + # Label für Ausgabefeld der Länge in Metern + ttk.Label(mainframe, text="ist äquivalent zu").grid(column=1, row=2, sticky=E) + + # Einheit-Label für Ausgabefeld der Länge in Metern + ttk.Label(mainframe, text="Meter").grid(column=3, row=2, sticky=W) + + # Ausgabefeld für Länge in Metern + self.meters = StringVar() + meters_entry = ttk.Entry(mainframe, width=7, textvariable=self.meters) + meters_entry.grid(column=2, row=2, sticky=(W, E)) + meters_entry.configure(state='readonly') # keine Eingabe, aber selektierbar + + # Button für Berechnung + + button_calc = ttk.Button(mainframe, text="Berechne", command=self.controller.calculate) + button_calc.grid(column=3, row=3, sticky=W) + + # schönere Abstände + for child in mainframe.winfo_children(): + child.grid_configure(padx=5, pady=5) + + # Statusbar + + self.frame_statusbar = ttk.Frame(self) + self.frame_statusbar['relief'] = 'sunken' + self.frame_statusbar['height'] = 12 + self.frame_statusbar['style'] = 'aFrame.TFrame' + self.frame_statusbar.grid_propagate(0) # Feste Größe an Grid-Packer weitergeben + self.frame_statusbar.grid(column=0, row=1, sticky=W + S + E) + + # Setze Fokus in Eingabefeld + feet_entry.focus() + + def bind_events(self): + """ Ereignisse an Kommandos binden. + + :return: + """ + self.bind("", self.controller.calculate) + self.bind("", self.controller.calculate) + self.bind("", self.close) + + def update_statusbar(self, status: int = 1): + """ Passt die Farbe der Statuszeile an den Status an. + + :return: None + """ + if status == 1: + self.frame_statusbar['style'] = 'aFrame.TFrame' + elif status == 2: + self.frame_statusbar['style'] = 'bFrame.TFrame' + else: + self.frame_statusbar['style'] = 'cFrame.TFrame' + + def close(self, event=None): + self.destroy() + + +# -------------------------- +# Starting Point +# -------------------------- +if __name__ == "__main__": + app = FeetToMeter() + app.mainloop()