#!/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") # Textvariablen, um Inhalte der Eingabefelder zu kontrollieren self.feet = StringVar() self.meters = StringVar() # Event Handler (Validator) registrieren self.check_entry_wrapper = self.register(self.validator.check_entry) # Zugriff auf Stilinformationen self.s = ttk.Style() # Zugriff auf Statusleiste self.frame_statusbar = ttk.Frame(self) # Aufbau des Fensters 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 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 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 st = self.frame_statusbar st['relief'] = 'sunken' st['height'] = 12 st['style'] = 'aFrame.TFrame' st.grid_propagate(0) # Feste Größe an Grid-Packer weitergeben st.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()