Frage zur GUI-Erstellung und Steuerung

Fragen zu Tkinter.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Melewo:
Melewo hat geschrieben:@Alfons Mittelmeyer: Das ist Deine Meinung, meine ist es nicht. Mit Auslagern und Importieren sollte man sich meiner Meinung nach erst beschäftigen, wenn man bereits weiß, was man da macht.
Es ist doch ganz klar was man in einem Menü macht. Man drückt dort auf einen Command Button, oder kreuzt eine Checkbox an oder wählt einen Radiobutton aus und davon muß dann irgend etwas Kenntnis bekommen. Und das Menü braucht auch nicht wissen wer. Im Auto kannst Du auch auf das Gaspedal drücken, ohne Bescheid zu wissen, wie es mit der Einspritzung im Motor genau verbunden ist.

Und was kann man für ein Menü in einem größeren Programm rechnen? Bei mir hat das Menü 500 Zeilen, wobei die Hilfe Seiten noch nicht dabei sind. Das sind dann nochmals 25 Toplevel Fenster, für jedes ein py File.
BlackJack

@Alfons Mittelmeyer: Der OP hat aber kein 500 Zeilen-Menü und selbst wenn man das hat, würde man auch nicht das Menü als ganzes auslagern, sondern eher anfangen das sinnvoll nach Funktionalität zu verteilen. Und bevor man selber einen `EventBroker` implementiert, würde ich ja eher auf ein anderes, moderneres GUI-Rahmenwerk setzen, wo es bereits Abstraktionen von ”Aktionen” gibt, die dann mit Menüs und Werkzeugleisten verwendet/verbunden werden können.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Der OP hat aber kein 500 Zeilen-Menü und selbst wenn man das hat, würde man auch nicht das Menü als ganzes auslagern, sondern eher anfangen das sinnvoll nach Funktionalität zu verteilen.
Am Anfang der Programmentwicklung stehen normalerweise die Anforderungen. Und dann kommen die Softwarearchitekten und gliedern das nach funktionellen Einheiten. Stefanxfg macht das also genau richtig.

Eine sinnvolle funktionelle Einheit ist sicher das Menü. Beim Menü darf man nicht nur die GUI Elemente rechnen, sondern auch dass diese eine Funktionalität haben. Menü GUI erweitert um Funktionalität ergibt etwa einen Faktor von fünf. Aus hundert Zeilen werden dann eben 500.

Hier mein Menü:

Code: Alles auswählen

import tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.geometry('600x183+0+0')
        self.mainmenu = Mainmenu(self)
        self['menu'] = self.mainmenu
        self.message = tk.Message(self,bg='white', text='Das soll der GuiDesigner sein.\n\nIm Original ist er allderdings in einem Toplevel Window,\ndamit man das Mainwindow designen kann.\n\nDas Menü hier aber ist original!', width=600)
        self.message.pack(expand=1)

class Mainmenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.Menu = Menu_1(self,name='menu_1',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu,label='File')
        self.Menu_1 = Menu1(self,name='menu1',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu_1,label='Special')
        self.menu = Menu_2(self,name='menu_2',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.menu,label='Help')
        self.add_command(background='green', columnbreak=1, label='Create ON')
        self.add_command()
        self.add_command(background='green', label='Config ON')
        self.add_command()
        self.add_command(background='green', label='Layout ON')
        self.add_command()
        self.add_command(background='green', label='Mouse ON')
        self.add_command(label='Hide')

class Menu_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Backup')
        self.add_command(label='Save')
        self.add_command(label='Load & Edit')
        self.add_command(label='Load & Run')
        self.Menu = Menu_3(self,name='menu_3',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu,label='Split & Join')
        self.add_separator()
        self.Menu_1 = Menu1_1(self,name='menu1_1',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu_1,label='Export (tk)')
        self.Menu_2 = Menu2(self,name='menu2',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu_2,label='Save Access')
        self.add_separator()
        self.add_command(label='Quit')

class Menu_3(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Help')
        self.add_separator()
        self.add_command(label='Save  (part)')
        self.add_command(label='Load & Edit (part)')
        self.add_command(label='Load & Run (part)')

class Menu1_1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Help')
        self.add_separator()
        self.add_command(label='with names')
        self.add_command(label='without names')

class Menu2(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Widget Depth')
        self.add_command(label='Container Depth')

class Menu1(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Toproot')
        self.add_command(label='Refresh')
        self.add_separator()
        self.Menu = Menu_4(self,name='menu_4',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.Menu,label='Expert Options')

class Menu_4(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Help')
        self.add_separator()
        self.add_command(label='Code')

class Menu_2(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Introduction')
        self.add_command(label='Config Options')
        self.add_command(label='Tuple Entries')
        self.add_command(label='Menu Entries')
        self.add_command(label='Backup')
        self.add_command(label='Save & Load')
        self.add_command(label='Export (tk)')
        self.add_command(label='Save Access')
        self.add_separator()
        self.menu = Menu_5(self,name='menu_5',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.menu,label='Programming')
        self.menu_1 = Menu1_2(self,name='menu1_2',activebackground='#7bfeff', bg='white', relief='solid', tearoff=0)
        self.add_cascade(menu=self.menu_1,label='Code in Scripts')
        self.add_command(label='Examples')

class Menu_5(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Load Scripts')
        self.add_command(label='Access Widgets')
        self.add_command(label='Access Toplevel')

class Menu1_2(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.add_command(label='Functions')
        self.add_command(label='Namespace')
        self.add_command(label='Imports')
        self.add_command(label='DynTkImports')
        self.add_command(label='Relative Access')
        self.add_command(label='Root Access')
        self.add_command(label='Place for Code')
        self.add_command(label='DynAccess')

Application().mainloop()
Ich finde es sehr praktisch, wenn man so etwas auslagert. Denn man braucht für Änderungen nicht in einem großen Script zu suchen, sondern editiert einfach den betreffenden File. Ich verstehe nicht, was Dir daran nicht gefällt, wenn man so ein Menü einfach in einen anderen File kopiert und dann importiert :?:

Gut das Menü hätte nicht dann nicht größer werden brauchen, wenn man Funktionen wie File Laden, nicht im Menü selbst behandelt, sondern auch auslagert.

Auch sollte ich natürlich nicht alle Menüs menu nennen und dann eindeutige Namen für die Klassen generieren lassen.
BlackJack

@Alfons Mittelmeyer: Ein Programm sinnvoll aufteilen ist ja auch gut, nur ist das Menü halt keine wirklich sinnvolle Linie um einen Schnitt zu machen. Und die Funktionalität dann dazu zu packen ebenfalls nicht. Denn damit hätte so ein Modul eine superenge Bindung an das restliche Programm so das es einfach nur ein willkürlicher Codeblob ist, der in eine andere Datei kopiert und ”inkludiert” wird.

Ich finde auch nicht das man mit so einer Aufteilung anfangen sollte. Das kann man, wie von anderer Stelle schon angemerkt wurde, machen wenn das Modul tatsächlich zu gross wird. *Dann* steht aber als erstes mal die Trennung von GUI und Geschäftslogik in zwei (oder mehr) Module an erster Stelle. Und wenn man die GUI auf mehrere Module aufteilt, dann auch eher nicht das gesamte Menü in ein Modul, sondern eher das Menü selbst nach Funktionalitätsgruppen in verschiedene Module aufgeteilt die in sich geschlossen sind. Beispielsweise ein Modul das alle GUI-Komponenten enthält die für die Hilfe verwendet werden, inklusive dem Menüteil dafür. Oder Dateioperationen. Oder alle Menüeinträge die sich auf eine bestimmte Anzeige beziehen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Blackjack: Sorry am Anfang jeder professionellen Programmentwicklung, stehen die Anforderunge und daraus dann resultierend die Softwareachitektur statt irgendwie anfangen und dann ein Refactoring.

Wie so ein Refactoring dann in manchen Fällen aussieht, kannst Du Dir doch vorstellen: Alles wegwerfen und den Code neu schreiben. Und danach wieder dasselbe Problem.

Das muss doch nicht sein!

Außerdem, dass ich Funktionalität nicht vom Menü getrennt habe, ist keine enge Bindung an das übrige Programm. Mit dem GuiDesigner bearbeitet man Widgets und dabei spielt es überhaupt keine Rolle, wo die herkommen, und ob man die über das Menü dazulädt oder sonstwie oder mit dem übrigen Programm selbst definiert.

Was habe ich gemacht bei meinem Beispiel? Meinen GuiDesigser genommen und dann:

- ein Menü namens 'mainmenu' angelegt und dieses aktiviert
- dann in dieses 'mainmenu' gegangen und über das Menü mit 'File' -> 'Split & Join' -> 'Load & Edit (part)' den File 'guidesigner/Topmenu' hereingeladen und hatte nun das Menü.
- dann zurück in die Root gegangen und dort das tk.Message Widget angelegt, gepackt, den Text editiert, den Hintergrund auf weiß gesetzt und die width gesetzt
- ja das Mainwidow hatte ich mit der Maus auf eine gefällige Größe gesetzt und mittels Enter Taste die Geometrie bestätigt
- und dann hatte ich das wieder über das Menü mit 'File' -> 'Export (tk)' -> 'without names' exportiert und ohne Änderung hier gepostet.

Ob man einen neuen GuiInhalt oder Teile davon lädt stört den Rest gar nicht, genausowenig wie man über das Menü einer Textverarbeitung eine Datei öffnet und die danach bearbeitet wird. Wichtig wäre dann nur, dafür zu sorgen, dass dann der Cursor am Anfang steht.

Wenn ich etwas hinzulade, ändert sich mein Cursor - das ist das ausgewählte Widget - nicht, wenn ich den Gui Inhhalt neu lade, setze ich ihn auf den Anfang - also auf die Root. Das ist die ganze Bindung mit dem Rest.
BlackJack

@Alfons Mittelmeyer: Weder eine professionelle Softwareentwicklung noch die besonderheiten Deines GUI-Designers haben etwas mit dem Programm des Fragestellers zu tun.

Refactoring muss sein wenn man sich in ein neues Thema einarbeitet. Und auch in jedem grösseren und längerfristigen Programm, denn weder die Anforderungen, noch die Rahmenbedingungen, noch die APIs die man verwendet, bleiben auf Dauer gleich.

Bei einer professionellen Softwareentwicklung machen das auch Profis. Also zum Beispiel die Anforderungsanalyse und die Planung. Wobei auch Profis agil an so etwas herangehen können. Insbesondere wenn man in ein neues Rahmenwerk oder eine neue Technik einsteigt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Weder eine professionelle Softwareentwicklung noch die besonderheiten Deines GUI-Designers haben etwas mit dem Programm des Fragestellers zu tun.
Das eine ja, nämlich dass am Anfang Überlegungen zur Programmarchitektur stehen sollen. Das andere nicht, das bezog sich auf Deine Behauptung, dass Funktionalität im Menü eine enge Bindung an den Rest wären. Außerdem würde es Sinn machen, einen Filedialog zu öffnen und danach entweder abbrechen oder die entsprechende Funktion mit dem ausgewählten File aufzurufen, nochmals auszufaktorieren? Oh, ich habe es sogar gemacht und jeweils etwa 20 Zeilen Code in ein gesondertes Modul gepackt.
BlackJack hat geschrieben:Refactoring muss sein wenn man sich in ein neues Thema einarbeitet. Und auch in jedem grösseren und längerfristigen Programm, denn weder die Anforderungen, noch die Rahmenbedingungen, noch die APIs die man verwendet, bleiben auf Dauer gleich.
Ich habe auch nichts gegen Refactoring gesagt. Nur wenn es bereits am Anfang an Überlegungen zu der geeigneten Architektur mangelt, geht vieles schief.
BlackJack hat geschrieben:Bei einer professionellen Softwareentwicklung machen das auch Profis. Also zum Beispiel die Anforderungsanalyse und die Planung. Wobei auch Profis agil an so etwas herangehen können. Insbesondere wenn man in ein neues Rahmenwerk oder eine neue Technik einsteigt.
Das mit dem Rahmenwerk ist ein gutes Beispiel. Es ist ein Problem, wenn ein solches Rahmenwerk erst gar nicht da ist. 50 Tests mit zwanzigtausend Zeilen Code. 3/4 vom Code beschäftigten sich mit der Wertübergabe von Bedienelementen an Testparameter. Manchmal ging das Zurücksetzen auf Defaultwerte und manchmal nicht. Ich hatte dann ein Rahmenwerk implementiert. Dem Testrahmen übergab man die Referenz auf die Bedienelemente und auf die Testparameter und 75% vom Code waren hinfällig und alles hatte volle Funktionaliät.
Antworten