From b4eda0867468e6ed6e859b0ca5ed5fb58c4ff224 Mon Sep 17 00:00:00 2001 From: mistangl Date: Wed, 17 Jan 2024 12:03:58 +0100 Subject: [PATCH] bauen der widgets in eigene lib namens gui ausgelagert. Umbau von test_gui in richtung Applikation in CreoMigrateGui --- lib/CreoMigrateGui.py | 33 ++-- lib/controller.py | 21 +++ lib/gui.py | 354 ++++++++++++++++++++++++++++++++++++++++++ lib/manage_configs.py | 3 - lib/test_gui.py | 4 +- 5 files changed, 392 insertions(+), 23 deletions(-) create mode 100644 lib/gui.py diff --git a/lib/CreoMigrateGui.py b/lib/CreoMigrateGui.py index 83319fd..7e9c261 100644 --- a/lib/CreoMigrateGui.py +++ b/lib/CreoMigrateGui.py @@ -97,6 +97,12 @@ class AllFrames(): def get_controller(self): return self._controller + def callback(self, key): + controller = self.get_controller() + if controller: + controller.callback(key) + + def data_to_model(self, controller): data = self.get_data() controller.set_gui_dict(data) @@ -166,7 +172,7 @@ class RightFrameLabels(AllFrames): self.teilenummer_text = tk.StringVar() teilenummer_entry = ttk.Entry(frame, textvariable=self.teilenummer_text) teilenummer_entry.grid(column=col_left+1, row=self.rownum, sticky=tk.W, padx=5, pady=5) - teilenummer_entry.bind('<>', self.combo_callback) + teilenummer_entry.bind('<>', lambda event:self.callback("teilenummer")) self.rownum += 1 separator2 = ttk.Separator(frame, orient='horizontal') separator2.grid(column=col_left, columnspan=3, row=self.rownum, sticky=tk.EW, padx=5, pady=5) @@ -183,11 +189,6 @@ class RightFrameLabels(AllFrames): if 'teilenummer' in data: self.teilenummer_text.set(data["teilenummer"]) - def combo_callback(self, event): - """ handle the changed event of TK""" - # TODO falls nötig - a = 6 - pass class RightFramePDM(AllFrames): def __init__(self, frame, nrow=0): @@ -206,7 +207,7 @@ class RightFramePDM(AllFrames): pdmkategorie_combo = ttk.Combobox(frame, textvariable=self.pdm_category) pdmkategorie_combo['values'] = ["Baugruppe", "Einzelteil", "Kaufteil", "NichtSivas", "Normteil", "Schweissbaugruppe"] pdmkategorie_combo['state'] = 'readonly' - pdmkategorie_combo.bind('<>', self.combo_callback) # falls nötig + pdmkategorie_combo.bind('<>', lambda event:self.callback('pdm_kategorie_neu')) pdmkategorie_combo.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) self.rownum += 1 @@ -218,7 +219,7 @@ class RightFramePDM(AllFrames): neuaufbau_checkbutton = ttk.Checkbutton(frame, text='ja', variable=self.neuaufbau, - command=self.checkbox_callback, + command=lambda: self.callback("neuaufbau"), onvalue=True, offvalue=False) neuaufbau_checkbutton.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) @@ -231,7 +232,7 @@ class RightFramePDM(AllFrames): lebenszyklusstatus_combo = ttk.Combobox(frame, textvariable=self.lebenszyklus_status_neu) lebenszyklusstatus_combo['values'] = ["Serie", "Prototyp"] lebenszyklusstatus_combo['state'] = 'readonly' - lebenszyklusstatus_combo.bind('<>', self.combo_callback) + lebenszyklusstatus_combo.bind('<>', lambda event:self.callback('lebenszyklus_status_neu')) lebenszyklusstatus_combo.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) self.rownum += 1 @@ -242,7 +243,7 @@ class RightFramePDM(AllFrames): aushalbzeug_checkbuttonr = ttk.Checkbutton(frame, text='neu', variable=self.aus_halbzeug_neu, - command=self.checkbox_callback, + command=lambda: self.callback("aus_halbzeug_neu"), onvalue=True, offvalue=False) aushalbzeug_checkbuttonr.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) @@ -278,12 +279,6 @@ class RightFramePDM(AllFrames): if 'zusatzinfos_text_neu' in data: self.zusatzinfos_text.set(data["zusatzinfos_text_neu"]) - def combo_callback(self, event): - pass - - def checkbox_callback(self): - pass - class App(): @@ -310,7 +305,7 @@ class App(): self.lf1 = LeftFrameAsm(left_frame, 0) nrows = self.lf1.get_rows() - self.tv = TreeView(left_frame, nrows) + self.lf2 = TreeView(left_frame, nrows) self.rf0 = RightFrameLabels(right_frame, 0) nrows = self.rf0.get_rows() @@ -323,7 +318,7 @@ class App(): """ methode zum auslesen aller Daten der Oberfläche aus dem Modell """ self.lf1.data_from_model() - self.tv.data_from_model() + self.lf2.data_from_model() self.rf0.data_from_model() self.rf1.data_from_model() @@ -333,7 +328,7 @@ class App(): self._controller = controller self.lf1.set_controller(controller) - self.tv.set_controller(controller) + self.lf2.set_controller(controller) self.rf0.set_controller(controller) self.rf1.set_controller(controller) diff --git a/lib/controller.py b/lib/controller.py index 9ff673f..2b7fd4f 100644 --- a/lib/controller.py +++ b/lib/controller.py @@ -9,6 +9,13 @@ class Controller(): def __init__(self, model, view): self.set_model(model) self.set_view(view) + self.logic = None + + + def add_logic(self, logic): + self.logic = logic + def remove_logic(self): + self.logic = None def set_model(self, model): self.model = model @@ -63,5 +70,19 @@ class Controller(): def get_all_gui_data(self): return self.model.get_all_gui_data() + def callback(self, key): + if self.logic: + self.logic.callback(key) + +class GuiLogic(): + """ + Class for business logic. implementing the dependencies between different user inputs + """ + def __init__(self): + pass + def callback(self, key): + a = key + + if __name__ == "__main__": pass diff --git a/lib/gui.py b/lib/gui.py new file mode 100644 index 0000000..7e9c261 --- /dev/null +++ b/lib/gui.py @@ -0,0 +1,354 @@ +import tkinter as tk +from tkinter import ttk +from controller import Controller +from caditem import CadItem +#from tkinter import messagebox +#import logging + + +#logger = logging.getLogger(__name__) +class TreeView(): + def __init__(self, frame, nrow=0): + # a treeview + # hier Anzahl der sichtbaren Zeilen des Baums angeben: + self.tree = ttk.Treeview(frame, height=40) + # TODO hier anstatt root die Statistik rein schreiben. + self.tree.heading('#0', text='root', anchor=tk.W) + self.tree.grid(row=nrow, column=0, sticky=tk.NSEW) + + # add a scrollbar + scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.tree.yview) + self.tree.configure(yscroll=scrollbar.set) + scrollbar.grid(row=nrow, column=1, sticky='ns') + + #self.tree.bind('<>', self.item_selected) # Einfachklick + self.tree.bind('', self.item_selected) # Doppelklick: + + def set_controller(self, controller): + self.controller = controller + + def refresh(self, parent_children_mapping, red_ids): + self.clear() + self.create(parent_children_mapping, red_ids) + + def clear(self): + for c in self.tree.get_children(): + self.tree.delete(c) + + def create(self, parent_children_mapping, red_ids): + # adding data of first column after root + pool = set() + for parent,children in parent_children_mapping.items(): + if parent not in pool: + self.tree.insert('', tk.END, text=parent, iid=parent, open=True) + pool.add(parent) + cn = children + # sortiere die Zahlen im Baum immer aufsteigend + cn.sort(reverse=True) + for child_id in cn: + if child_id not in pool: + pool.add(child_id) + if child_id not in red_ids: rowtags = ('good') + else: rowtags = ('missing') + self.tree.insert('', tk.END, text=child_id, iid=child_id, tags=rowtags, open=True) + #self.tree.move(child_id, parent, 0) + + # einmal über alles gehen und alle keys an die richtige Stelle schieben + for parent,children in parent_children_mapping.items(): + for child_id in children: + self.tree.move(child_id, parent, 0) + + # https://www.askpython.com/python-modules/tkinter/tkinter-colors + #self.tree.tag_configure('good', background='LawnGreen') + self.tree.tag_configure('good', background='white') + self.tree.tag_configure('missing', background='red') + + def data_from_model(self): + parent_children_mapping = self.controller.get_parent_children() + red_ids = self.controller.get_red_ids() + self.refresh(parent_children_mapping, red_ids) + + def item_selected(self, event): + """callback of TK if an item in the tree has been selected + """ + cid = self.tree.selection()[0] + # trage die neue ID ins Modell ein + self.controller.set_gui_data('teilenummer', cid) + self.controller.refresh_gui() + + +class AllFrames(): + """ enthält alle gleichen Methoden für die Widgets in der rechte Spalte + """ + def __init__(self, nrow=0): + self.set_start_rwo(nrow) + self._controller = None + + def get_rows(self): + return self.rownum + + def set_start_rwo(self, nrow): + if nrow < 0: + raise Exception("this row can not be set") + self.rownum = nrow + + def set_controller(self, controller): + self._controller = controller + def get_controller(self): + return self._controller + + def callback(self, key): + controller = self.get_controller() + if controller: + controller.callback(key) + + + def data_to_model(self, controller): + data = self.get_data() + controller.set_gui_dict(data) + + def data_to_model(self, controller, key, value): + controller.set_gui_data(key, value) + +class LeftFrameAsm(AllFrames): + def __init__(self, frame, nrow=0): + AllFrames.__init__(self, nrow) + self.frame = frame + self.build_widgets() + + def build_widgets(self): + frame = self.frame + self.rownum = AllFrames.get_rows(self) + col_left = 0 + # Baugruppennummer + asm_label = ttk.Label(frame, text="Baugruppennummer:" ) + asm_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + self.asm_number = tk.StringVar() + asm_entry = ttk.Entry(frame, textvariable=self.asm_number, width=9) + asm_entry.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + def get_data(self): + return { + "asm_id": self.asm_number.get(), + } + + def data_from_model(self): + data = self.get_controller().get_all_gui_data() + if 'asm_id' in data: + self.asm_number.set(data["asm_id"]) + +class RightFrameLabels(AllFrames): + def __init__(self, frame, nrow=0): + AllFrames.__init__(self, nrow) + self.frame = frame + self.build_widgets() + + def build_widgets(self): + frame = self.frame + self.rownum = AllFrames.get_rows(self) + col_left = 0 + # Feld, SIVAS-Werte, Überschriebene Werte + feld_label = ttk.Label(frame, text="Feld" ) + feld_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + Sivas_label = ttk.Label(frame, text="SIVAS-Werte" ) + Sivas_label.grid(column=col_left+1, row=self.rownum, sticky=tk.W, padx=5, pady=5) + transwritten_label = ttk.Label(frame, text="Überschriebene Werte" ) + transwritten_label.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + + feld_label.configure(font=("Helvetica", 16)) + Sivas_label.configure(font=("Helvetica",16)) + transwritten_label.configure(font=("Helvetica",16)) + self.rownum += 1 + + separator = ttk.Separator(frame, orient='horizontal') + separator.grid(column=col_left, columnspan=3, row=self.rownum, sticky=tk.EW, padx=5, pady=5) + self.rownum += 1 + + # Teilenummer + teilenummer_label = ttk.Label(frame, text="Teilenummer:") + teilenummer_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.teilenummer_text = tk.StringVar() + teilenummer_entry = ttk.Entry(frame, textvariable=self.teilenummer_text) + teilenummer_entry.grid(column=col_left+1, row=self.rownum, sticky=tk.W, padx=5, pady=5) + teilenummer_entry.bind('<>', lambda event:self.callback("teilenummer")) + self.rownum += 1 + separator2 = ttk.Separator(frame, orient='horizontal') + separator2.grid(column=col_left, columnspan=3, row=self.rownum, sticky=tk.EW, padx=5, pady=5) + self.rownum += 1 + + + def get_data(self): + return { + "teilennummer": self.teilenummer_text.get(), + } + + def data_from_model(self): + data = self.get_controller().get_all_gui_data() + if 'teilenummer' in data: + self.teilenummer_text.set(data["teilenummer"]) + + +class RightFramePDM(AllFrames): + def __init__(self, frame, nrow=0): + AllFrames.__init__(self, nrow) + self.frame = frame + self.build_widgets() + + def build_widgets(self): + frame = self.frame + self.rownum = AllFrames.get_rows(self) + col_left = 0 + # pdmkategorie + pdmkategorie_label = ttk.Label(frame, text="PDM-Kategorie:" ) + pdmkategorie_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.pdm_category = tk.StringVar() + pdmkategorie_combo = ttk.Combobox(frame, textvariable=self.pdm_category) + pdmkategorie_combo['values'] = ["Baugruppe", "Einzelteil", "Kaufteil", "NichtSivas", "Normteil", "Schweissbaugruppe"] + pdmkategorie_combo['state'] = 'readonly' + pdmkategorie_combo.bind('<>', lambda event:self.callback('pdm_kategorie_neu')) + pdmkategorie_combo.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + # Neuaufbau + neuaufbau_label = ttk.Label(frame, text="Neuaufbau:") + neuaufbau_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + + self.neuaufbau = None + neuaufbau_checkbutton = ttk.Checkbutton(frame, + text='ja', + variable=self.neuaufbau, + command=lambda: self.callback("neuaufbau"), + onvalue=True, + offvalue=False) + neuaufbau_checkbutton.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + # Lebenszyklusstatus + lebenszyklusstatus_label = ttk.Label(frame, text="Lebenszyklusstatus:") + lebenszyklusstatus_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.lebenszyklus_status_neu = tk.StringVar() + lebenszyklusstatus_combo = ttk.Combobox(frame, textvariable=self.lebenszyklus_status_neu) + lebenszyklusstatus_combo['values'] = ["Serie", "Prototyp"] + lebenszyklusstatus_combo['state'] = 'readonly' + lebenszyklusstatus_combo.bind('<>', lambda event:self.callback('lebenszyklus_status_neu')) + lebenszyklusstatus_combo.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + # aus Halbzeug + halbzeug_label = ttk.Label(frame, text="aus Halbzeug:") + halbzeug_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.aus_halbzeug_neu = None + aushalbzeug_checkbuttonr = ttk.Checkbutton(frame, + text='neu', + variable=self.aus_halbzeug_neu, + command=lambda: self.callback("aus_halbzeug_neu"), + onvalue=True, + offvalue=False) + aushalbzeug_checkbuttonr.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + # Zusatzinformationen + zusatzinfos_label = ttk.Label(frame, text="Zusatzinformationen:") + zusatzinfos_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.zusatzinfos_text = tk.StringVar() + zusatzinfos_entry = ttk.Entry(frame, textvariable=self.zusatzinfos_text) + zusatzinfos_entry.grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=5, pady=5) + self.rownum += 1 + + def get_data(self): + return { + "pdm_kategorie_neu": self.pdm_category.get(), + "neuaufbau": self.neuaufbau, + "lebenszyklus_status_neu": self.lebenszyklus_status_neu.get(), + "aus_halbzeug_neu": self.aus_halbzeug_neu, + "zusatzinfos_txt_neu": self.zusatzinfos_text.get(), + } + + def data_from_model(self): + data = self.get_controller().get_all_gui_data() + if 'pdm_kategorie_neu' in data: + self.pdm_category.set(data["pdm_kategorie_neu"]) + if 'neuaufbau' in data: + self.neuaufbau = data["neuaufbau"] + if 'lebenszyklus_status_neu' in data: + self.lebenszyklus_status_neu.set(data["lebenszyklus_status_neu"]) + if 'aus_halbzeug_neu' in data: + self.aus_halbzeug_neu= data["aus_halbzeug_neu"] + if 'zusatzinfos_text_neu' in data: + self.zusatzinfos_text.set(data["zusatzinfos_text_neu"]) + + + +class App(): + def __init__(self, root): + """Standardgui mit Baum zur Anwahl der Teile des Assemblies""" + + self.root = root + + # configure the grid + # die erste Spalte mit Baum sollte unbeweglich bleiben bei Vergräßerung des Fensters. + # self.root.columnconfigure(0, weight=2) + self.root.columnconfigure(1, weight=1) + #self.root.geometry('800x800') + + # Create left and right frames + left_frame = ttk.Frame(self.root, width=200, height=800) + left_frame.grid(row=0, column=0, padx=10, pady=5) + + right_frame = ttk.Frame(self.root, width=650, height=800) + right_frame.grid(row=0, column=1, padx=10, pady=5) + + style = ttk.Style(self.root) + style.theme_use('classic') + + self.lf1 = LeftFrameAsm(left_frame, 0) + nrows = self.lf1.get_rows() + self.lf2 = TreeView(left_frame, nrows) + + self.rf0 = RightFrameLabels(right_frame, 0) + nrows = self.rf0.get_rows() + self.rf1 = RightFramePDM(right_frame, nrows) + nrows = self.rf1.get_rows() + #self.rf2 = RightFramePDM(right_frame, nrows) + # TODO usw. + + def data_from_model(self): + """ methode zum auslesen aller Daten der Oberfläche aus dem Modell + """ + self.lf1.data_from_model() + self.lf2.data_from_model() + + self.rf0.data_from_model() + self.rf1.data_from_model() + + def set_controller(self, controller): + # merken des Handels auf den Controller in jedem widget + self._controller = controller + + self.lf1.set_controller(controller) + self.lf2.set_controller(controller) + self.rf0.set_controller(controller) + self.rf1.set_controller(controller) + + def mainloop(self): + self.root.mainloop() + + +if __name__ == "__main__": + + """ using the Model-View-Controller (MVC) Pattern + - The MODEL is responsible for managing the data of the application. It + receives user input from the CONTROLLER + - The VIEW renders presentation of the model in a particular format. + - The CONTROLLER responds to the user input and performs interactions on the + data model objects. The CONTROLLER receives the input, optionally validates + it and then passes the input to the model. + + + <--fills gui -- --manipulates--> + --user input-> <--give model data-- + + + """ diff --git a/lib/manage_configs.py b/lib/manage_configs.py index 0fd70c1..e59f151 100644 --- a/lib/manage_configs.py +++ b/lib/manage_configs.py @@ -273,6 +273,3 @@ class PfadConfig(IniBasedConfig): """hole alle möglichen Bezeichner """ return super().get_section_options("Zielpfade") - -if __name__ == '__main__': - Kalkulationsart diff --git a/lib/test_gui.py b/lib/test_gui.py index 31f0d5c..944078a 100644 --- a/lib/test_gui.py +++ b/lib/test_gui.py @@ -1,7 +1,7 @@ import tkinter as tk from caditem import CadItem from CreoMigrateGui import App -from controller import Controller +from controller import Controller, GuiLogic json_str = """ { @@ -69,6 +69,8 @@ model.from_json(json_str) root = tk.Tk() app = App(root) controller = Controller(model, app) +logic = GuiLogic() +controller.add_logic(logic) # um ohne Observer pattern auch Rückmeldungen geben zu können app.set_controller(controller) model.set_controller(controller)