Frame leeren / Klassenübergreifend

Fragen zu Tkinter.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Ich Grüße Euch

Ich habe ein (funktionierendes) Programm geschrieben mit nur einer Klasse und leider etlichen Frames. Dieses ist nun sehr aufgeblasen. Bisher habe ich immer frame.forget benutzt, was wohl nicht so elegant ist. Daher habe ich mich entschlossen das Programm nun mit mehreren Klassen aufzuteilen, aber ich komme seit Tagen nicht weiter. :roll:

Ich habe mal ein kleines Beispiel geschrieben, um das Grundgerüst aufzuzeigen.

Folgende Fragen fürs Erste:

1. Ist das Codegerüst so o. k.? Oder sollte ich das anders angehen?
2. Wie kann ich den Bildschirm leeren, sodass man von Beispiel zu Beispiel kommt, ohne den vorherigen Text noch dort zu haben.

Danke im Voraus und ein angenehmes Wochenende :wink:

P. S.: Bei Bedarf kann ich auch das ganze Programm posten, aber das sind mehr als 420 Zeilen

Code: Alles auswählen

import tkinter as tk


class Gui(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        self.master.attributes('-zoomed', True)

        # classes
        one = ExampleClassOne()
        two = ExampleClassTwo()

        # menubar
        top = self.winfo_toplevel()
        self.menubar = tk.Menu(top)
        top['menu'] = self.menubar

        # submenu
        self.file = tk.Menu(master=self.menubar)
        self.example = tk.Menu(master=self.menubar)

        # submenu cascades
        self.menubar.add_cascade(label='Datei', menu=self.file)
        self.menubar.add_cascade(label='Beispiel', menu=self.example)

        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)

        # submenu example
        self.example.add_command(label='Beispiel 1', command=one.example_method_one)
        self.example.add_command(label='Beispiel 2', command=two.example_method_two)


class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        self.grid()

    def example_method_one(self):
        one = tk.Label(master=self.master,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)


class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        self.grid()

    def example_method_two(self):
        two = tk.Label(master=self.master,
                       text='Test 2\nTest 2\nTest 2')
        two.grid(row=0)


def main():
    root = tk.Tk()
    Gui(root).grid(sticky="nsew")
    root.grid_columnconfigure(0, weight=1)
    root.mainloop()


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
BlackJack

@Schwarzer Wolf: Also ich würde sagen das ist nicht o.k. denn die beiden `Example*`-Klassen werden von `Frame` abgeleitet, aber dann gar nicht als `Frame` verwendet. Die Methode fängt dann an auf dem Elternobjekt in der Widgethierarchie zu operieren, was man nicht machen sollte. Zumal die dort immer mehr und mehr Label erzeugen und die nie wieder entfernen.

Ausserdem sollten sich Widgets nicht selber anordnen. Das macht ja auch keines der bereits vorhandenen Widgets. Damit nimmt man dem Code der das Widget erstellt die Freiheit es so anzuordnen wie er möchte.

Erst einmal solltest Du klären ob Du tatsächlich selbst was programmieren möchtest was dann am Ende so ähnlich wie ein `ttk.Notebook` funktioniert. Man muss das Rad ja nicht neu erfinden.

Ansonsten würde ich die Frames mit den tatsächlichen Inhalten entweder austauschen, mit der entsprechenden `*forget()`-Methode, oder alle übereinander und expandierend in die gleiche Gridzelle stecken und dann das jeweils aktuelle in den Vordergrund holen. Also letztlich was `ttk.Notebook` auch machen wird. Dazu schreibt man sich dann am besten eine Klasse die so ähnlich wie `ttk.Notebook` oder beispielsweise `QStackedWidget` von Qt funktioniert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: ich weiß nicht, was Du mit Deinem Programm tun willst. Aber wenn Dein Bildschirm sich mit Frames füllt, dass Du nicht mehr damit zurechtkommst, könntest Du statt Frames vielleicht Toplevel Windows verwenden. Die kannst Du dann auch in den Vordergrund rücken und schließen kann man sie auch einfach per Mausklick.
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Danke für die schnellen Antworten. :D
Alfons Mittelmeyer hat geschrieben:@Schwarzer Wolf: ich weiß nicht, was Du mit Deinem Programm tun willst. Aber wenn Dein Bildschirm sich mit Frames füllt, dass Du nicht mehr damit zurechtkommst, könntest Du statt Frames vielleicht Toplevel Windows verwenden. Die kannst Du dann auch in den Vordergrund rücken und schließen kann man sie auch einfach per Mausklick.
Das macht leider keinen Sinn für dieses Programm. Mit Toplevel Windows meinst Du doch (ein) neu(e) Fenster erstellen, oder?

Es soll alles in einem Fenster laufen.
BlackJack hat geschrieben:@Schwarzer Wolf:
Erst einmal solltest Du klären ob Du tatsächlich selbst was programmieren möchtest was dann am Ende so ähnlich wie ein `ttk.Notebook` funktioniert. Man muss das Rad ja nicht neu erfinden.

Ansonsten würde ich die Frames mit den tatsächlichen Inhalten entweder austauschen, mit der entsprechenden `*forget()`-Methode, oder alle übereinander und expandierend in die gleiche Gridzelle stecken und dann das jeweils aktuelle in den Vordergrund holen. Also letztlich was `ttk.Notebook` auch machen wird. Dazu schreibt man sich dann am besten eine Klasse die so ähnlich wie `ttk.Notebook` oder beispielsweise `QStackedWidget` von Qt funktioniert.
Mit Qt kenne ich mich gar nicht aus bisher. Kannst Du mir ein Beispiel zeigen, wie ich so etwas in eine Gridzelle stecke und dann in den Vordergrund hole. Das habe ich hier im Forum schon irgendwo gelesen, aber es nicht verstanden. Im Theoretischen lernen bin ich nicht gerade der beste. :-)

ttk.Notebook habe ich mir gerade angesehen. Ist sehr interessant. Kann ich da auch Submenüs anlegen? Ich weiß, nicht ob ich ohne Submenüs hinkomme mit dem Platz. Momentan hat sie nur eine Berechnung und eben etliche Dinge, die in der Menürubrik "Über" sind. Also Lizenz und ähnliches. Es sollen mit der Zeit noch etliche Funktionen hinzukommen (Joggen, tägliche Nahrung, Kraftstandards etc..

Ich poste mal einen Teil des "großen" Codes. Mir kommt das mit den Frames eben irgendwie sinnlos vor und es nimmt so viel Platz weg. Falls daraus nicht ersichtlich, poste ich den ganzen Code. Ist ja eh GPL 3

Also der große Code läuft wie gesagt bisher ohne Fehler. Aber ich habe eben den Fehler gemacht, alles in eine Klasse zu packen und will das nun auf mehrere Klassen unterteilen, sodass ich später Dinge schnell auswechseln kann, bzw. das Menü als Grundgerüst für andere Programme nehmen kann.

Die Klasse heißt erst neuerdings GUI, da ich ja aufteilen will. Dass sie nicht nur GUI enthält, sondern eben "alles" ist mir klar. 8)

Code: Alles auswählen

class Gui(tkinter.Frame):
    def __init__(self, master=None):
        tkinter.Frame.__init__(self, master=master)

        self.strength_table = []

        self.menu_bar = tkinter.Menu(master=self.master)
        self.submenu_programm = tkinter.Menu(master=self.menu_bar)
        self.submenu_lifting = tkinter.Menu(master=self.menu_bar)
        self.submenu_about = tkinter.Menu(master=self.menu_bar)

        self.frame_programm = tkinter.Frame(master=self.master)
        self.lframe_licence = tkinter.LabelFrame(master=self.master)
        self.frame_one_rep_max = tkinter.Frame(master=self.master)
        self.frame_developer = tkinter.Frame(master=self.master)
        self.frame_propaganda = tkinter.Frame(master=self.master)

        self.weight_input = tkinter.Entry(master=self.frame_one_rep_max,
                                          width=5)
        self.repetitions_input = tkinter.Entry(master=self.frame_one_rep_max,
                                               width=5)

        self.one_output = tkinter.Label(master=self.frame_one_rep_max)
        self.five_output = tkinter.Label(master=self.frame_one_rep_max)
        self.ten_output = tkinter.Label(master=self.frame_one_rep_max)
        self.twelve_output = tkinter.Label(master=self.frame_one_rep_max)
        self.fifteen_output = tkinter.Label(master=self.frame_one_rep_max)

        self.logo = tkinter.PhotoImage(file='./grafics/schwarzerwolf_cc.gif')
        self.tux = tkinter.PhotoImage(file='./grafics/tux.gif')

        self.base_window()
        self.menu()
        self.base_program()
        self.program_gui()

    def base_window(self):
        title, version, release, copyleft = schwarzerwolf()

        self.master.attributes('-zoomed', True)
        self.master.configure(menu=self.menu_bar)
        self.master['bg'] = '#000000'
        self.master['pady'] = 30
        self.master.title('{title} [{version}]'.format(title=title,
                                                       version=version,
                                                       font='Monospace'))

    def base_program(self):
        self.frame_programm['bg'] = '#000000'
        self.frame_programm.pack()

    def base_one_rm(self):
        self.frame_one_rep_max['bg'] = '#000000'
        self.frame_one_rep_max.pack()

    def base_licence(self):
        self.lframe_licence['bg'] = '#000000'
        self.lframe_licence['fg'] = '#ffffff'
        self.lframe_licence['text'] = 'GPL Lizenz'
        self.lframe_licence['pady'] = 30
        self.lframe_licence['padx'] = 30
        self.lframe_licence.pack()

    def base_developer(self):
        self.frame_developer['bg'] = '#000000'
        self.frame_developer['pady'] = 30
        self.frame_developer['padx'] = 30
        self.frame_developer.pack()

    def base_clear(self):
        self.frame_programm.forget()
        self.lframe_licence.forget()
        self.frame_one_rep_max.forget()
        self.frame_developer.forget()
        self.frame_propaganda.forget()

    def base_propaganda(self):
        self.frame_propaganda['bg'] = '#000000'
        self.frame_propaganda.pack()

    def menu(self):
        self.menu_bar['fg'] = '#ffffff'
        self.menu_bar['bg'] = '#000000'
        self.menu_bar['bd'] = 2
        self.menu_bar['font'] = ('Monospace', 10)

        self.menu_bar.add_cascade(label='Programm',
                                  menu=self.submenu_programm)
        self.menu_bar.add_cascade(label='Krafsport',
                                  menu=self.submenu_lifting)
        self.menu_bar.add_cascade(label='Über',
                                  menu=self.submenu_about)

        self.menu_program()
        self.menu_lifting()
        self.menu_about()

    def menu_program(self):
        self.submenu_programm.add_command(label='Beenden',
                                          command=self.master.quit,
                                          font=('Monospace', 10))

    def menu_lifting(self):
        self.submenu_lifting.add_command(label='1RM',
                                         command=self.one_rep_max_gui,
                                         font=('Monospace', 10))

    def menu_about(self):
        self.submenu_about.add_command(label='Lizenz',
                                       command=self.gui_licence,
                                       font=('Monospace', 10))

        self.submenu_about.add_command(label='Programm',
                                       command=self.program_gui,
                                       font=('Monospace', 10))
        self.submenu_about.add_command(label='Entwickler',
                                       command=self.gui_developer,
                                       font=('Monospace', 10))
        self.submenu_about.add_command(label='Propaganda',
                                       command=self.gui_propaganda,
                                       font=('Monospace', 10))
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: Was ich jetzt nicht kapiere: was soll diese Frame Klasse namens Gui?

Du platziert da im Anwendungsfenster einen Frame mittels grid. Jetzt würde man erwarten, dass du dann die Gui Elemente dieses Frames definierst.

Aber Du definierst in der Frameklasse Gui die Gui Elemente des Anwendungsfensters, etwa: self.frame_programm = tkinter.Frame(master=self.master)

Du solltest die Gui Elemente des Applikationsfenster in der Applikationsklasse definieren und nicht in einem untergeordneten Frame. Und wenn Du darin wieder Containerwidgets definierst, also Frames und Labelframes, dann solltest Du dafür eigene Klassen verwenden, in denen Du dann die Inhalte dieser Container definierst, etwa:

Code: Alles auswählen

import tkinter as tk

class Application(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)

        self.frame_programm = Frame_Programm(self)
        self.lframe_licence = Lframe_Frame(self)

Application().mainloop()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarter Wolf: sorry hatte mich verschrieben

Code: Alles auswählen

# statt self.lframe_licence = Lframe_Frame(self)
# sollte es heissen
self.lframe_licence = Lframe_Licence(self)
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

@Alfons Mittelmeyer

Das, was Du ansprichst, ist der Fehler, den ich gemacht habe. Das hier ist quasi mein erster "größerer" Gui Versuch. Mit Klassen war ich anfangs auch noch nicht so vertraut wie jetzt und von daher hab ich alles in eine Klasse geschrieben mit X Funktionen. Nun finde ich das aber eben bei 420+ Zeilen Code etwas überladen und möchte - bevor ich weitermache - die Struktur etwas verbessern.

Ich habe jetzt mein Beispiel Deinem Beispiel angepasst und hoffentlich auch die Fehler entfernt, die drin waren. Ist für mich leichter das zu posten, als am "großen" Code rumzubasteln. :-)

Aber eines verstehe ich noch nicht. Wo setze ich nun das forget, sodass es funktioniert, bzw. wie kann ich etwas aus einer gridzeile in den Vordergrund holen (wie BlackJack gesagt hat)?

Code: Alles auswählen

import tkinter as tk


class Gui(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)
        self.grid()

        # classes
        one = ExampleClassOne()
        two = ExampleClassTwo()

        # menubar
        top = self.winfo_toplevel()
        self.menubar = tk.Menu(top)
        top['menu'] = self.menubar

        # submenu
        self.file = tk.Menu(master=self.menubar)
        self.example = tk.Menu(master=self.menubar)

        # submenu cascades
        self.menubar.add_cascade(label='Datei', menu=self.file)
        self.menubar.add_cascade(label='Beispiel', menu=self.example)

        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)

        # submenu example
        self.example.add_command(label='Beispiel 1', command=one.example_method_one)
        self.example.add_command(label='Beispiel 2', command=two.example_method_two)


class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        self.master.grid()

    def example_method_one(self):
        one = tk.Label(master=self.master,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)


class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        self.master.grid()

    def example_method_two(self):
        two = tk.Label(master=self.master,
                       text='Test 2\nTest 2\nTest 2')
        two.grid(row=0)


def main():
    Gui().mainloop()


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: da sind noch einige Fehler drin.

Fehler 1 und 2:
In example_method_one und example_method_two kreierst Du bei jedem Aufruf einen Label und pappst ihn sozusagen auf den zuvor darüber. Das ergibt dann einen Label Stapel, der immer mehr anwächst.

Fehler 3 und 4:
ExampleClassOne und ExampleClassTwo definieren Frames ohne Inhalt. Die Labels kreierst Du nämlich im Anwaendungsfenster durch master=self.master
Richtig wäre master=self

Fehler 5 und 6:
Unsinn in ExampleClassOne und ExampleClassTwo: self.master.grid()
self.master ist das Anwendungsfenster. Das kannt Du doch nicht mittels grid in ihm selber platzieren

Fehler 7 und 8:
Unsinn in ExampleClassOne und ExampleClassTwo: self.master.grid()
Gemeint war wohl self.grid(). Aber auch das sind zwei Fehler, einerseits es ging doch darum, den Frame unsichtbar zu machen, anstatt ihn zu zeigen. Und außerdem sollen sich Gui Elemente nicht selber platzieren. Wenn ein grid, dann gehört es zum Anwendungsfenster, also in die Applikationsklasse

Fehler 9 und 10:
example_method_one und example_method_two sollen die Frames wohl jeweils sichtbar und unsichtbar die Mothode soll nicht nur auf den eigenen Frame zugreifen können sondern auch auf den anderen. Damit gehören diese Methoden wiederum in die Applikationsklasse. Außderdem, wie bereits gesagt, platzieren der Frames im Anwendungsfenster gehört in das Anwendungsfenster.

Ist es jetzt das, was Du möchtest?

Code: Alles auswählen

import tkinter as tk
 
 
class Gui(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)
        self.grid()
 
        # classes
        self.one = ExampleClassOne()
        self.two = ExampleClassTwo()
 
        # menubar
        top = self.winfo_toplevel()
        self.menubar = tk.Menu(top)
        top['menu'] = self.menubar
 
        # submenu
        self.file = tk.Menu(master=self.menubar)
        self.example = tk.Menu(master=self.menubar)
 
        # submenu cascades
        self.menubar.add_cascade(label='Datei', menu=self.file)
        self.menubar.add_cascade(label='Beispiel', menu=self.example)
 
        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)
 
        # submenu example
        self.example.add_command(label='Beispiel 1', command=self.example_method_one)
        self.example.add_command(label='Beispiel 2', command=self.example_method_two)
 


    def example_method_one(self):
        self.two.grid_forget()
        self.one.grid()

    def example_method_two(self):
        self.one.grid_forget()
        self.two.grid()

 
class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        one = tk.Label(master=self,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)
        
 
 
class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
         two = tk.Label(master=self,
                       text='Test 2\nTest 2\nTest 2')
        two.grid(row=0)

 
def main():
    Gui().mainloop()
 
 
if __name__ == '__main__':
    main()
Sollte es allerdings darum gehen, nicht die Frames sichtbar oder unsichtbar zu machen, sondern in den Vordergrund oder Hintergrund zu rücken, dann gibt es dafür die Methoden lift und lower.
Siehe dazu: http://effbot.org/tkinterbook/widget.htm
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: ich hatte im Code grid_forget geschrieben. Besser ist aber grid_remove. grid_forget vergißt die grid Optionen. Wenn man also etwa nicht in 0,0 platziert hatte, würde ein nachfolgendes grid() nicht mehr wissen, wo der Frame zuvor war und ihn auf 0,0 platzieren. Wenn man also Frames auch in anderen Grid Zellen platziert hat, dann grid_remove nehmen. Danach braucht man bei einem nachfolgenden grid nicht mehr die Parameter anzugeben, weil diese nicht vergessen wurden.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Die Zeile 59 in deinem Skript wirft ein 'unexpected indent'!

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

@ Alfons Mittelmeyer

:D :D :D

Genau das ist es, was ich nicht hinbekommen habe. Danke, dass Du Dir die Zeit genommen hast. Anhand des Beispiels und dem Text kapiere ich das nun endlich. Wie gesagt, ich tue mich mit der Theorie oftmals ziemlich schwer und kann besser durch Beispiele lernen.

Kann ich eigentlich auch die Menüs in eine "class Menu(tk.Menu)" packen, oder macht man das nur mit Frames?

In Zeile 59 war bei Dir ein Einrückfehler. Ich poste das daher noch einmal neu, falls jemand mal das gleiche Problem hat:

Code: Alles auswählen

import tkinter as tk


class Gui(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)
        self.grid()

        # classes
        self.one = ExampleClassOne()
        self.two = ExampleClassTwo()

        # menubar
        top = self.winfo_toplevel()
        self.menubar = tk.Menu(top)
        top['menu'] = self.menubar

        # submenu
        self.file = tk.Menu(master=self.menubar)
        self.example = tk.Menu(master=self.menubar)

        # submenu cascades
        self.menubar.add_cascade(label='Datei', menu=self.file)
        self.menubar.add_cascade(label='Beispiel', menu=self.example)

        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)

        # submenu example
        self.example.add_command(label='Beispiel 1', command=self.example_method_one)
        self.example.add_command(label='Beispiel 2', command=self.example_method_two)

    def example_method_one(self):
        self.two.grid_forget()
        self.one.grid()

    def example_method_two(self):
        self.one.grid_forget()
        self.two.grid()


class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        one = tk.Label(master=self,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)


class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        two = tk.Label(master=self,
                       text='Test 2\nTest 2\nTest 2')

        two.grid(row=0)


def main():
    Gui().mainloop()


if __name__ == '__main__':
    main()
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: Natürlich macht es viel Sinn, auch das Menü in eine eigene Klasse oder sogar mehrere zu packen für Untermenüs. So ein Menü kann schließlich ganz schon umfangreich werden.

Allerdings kann es dann sein, dass man noch eine zentrale Struktur braucht, indem man dann Verweise auf die aufzurufenden Methoden erfaßt. Wenn dyas was aufzurufen, ganz wo anders steht, als die Menüelemente, dann muss man das ja finden.

Mit nur einer Menüklasse, allerdings kein Problem, da man ja den Master übergeben hat. Mit master.master.master würde es langsam unschön werden. Da ist es besser nicht den internen Aufbau zu kodieren, sondern so etwas zu implememtieren, wie

my_dictionary["example_method_one"]()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Schwarzer Wolf: hier noch ein Beispiel mit gesonderter Klasse für Menübar und Callbackaufrufe über Dictionary. Man sollte schließlich unabhängig von der Hierarchie des Gui Aufbaus aufrufen, es könnte sich ja der Aufbau noch ändern, indem man später weiter aufteilt. Ansonsten direkte Aufrufe nur im selben Container, evtl auch noch in einem Untercontainer.

Code: Alles auswählen

import tkinter as tk
 

callbacks = {}
 
class Gui(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)
        self.grid()

        # register callbacks
        callbacks['example_method_one'] = self.example_method_one
        callbacks['example_method_two'] = self.example_method_two
 
        # Frames
        self.one = ExampleClassOne()
        self.two = ExampleClassTwo()
 
        # menubar
        self['menu'] = MenuBar(self)
 
    def example_method_one(self):
        self.two.grid_forget()
        self.one.grid()
 
    def example_method_two(self):
        self.one.grid_forget()
        self.two.grid()
 
 
class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        one = tk.Label(master=self,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)
 
 
class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        two = tk.Label(master=self,
                       text='Test 2\nTest 2\nTest 2')
 
        two.grid(row=0)
 

class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master=master)
        one = tk.Label(master=self,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)
 
 
class MenuBar(tk.Menu):
    def __init__(self, top=None):
        tk.Menu.__init__(self, master=top)

        # submenu
        self.file = tk.Menu(master=top)
        self.example = tk.Menu(master=top)
 
        # submenu cascades
        self.add_cascade(label='Datei', menu=self.file)
        self.add_cascade(label='Beispiel', menu=self.example)
 
        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)
 
        # submenu example
        self.example.add_command(label='Beispiel 1', command=self.example_method_one)
        self.example.add_command(label='Beispiel 2', command=self.example_method_two)

    def example_method_one(self):
        callbacks['example_method_one']()

    def example_method_two(self):
        callbacks['example_method_two']()

 
def main():
    Gui().mainloop()
 
 
if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: und warum fängst Du schon wieder mit globalen Variablen an? Das ist doch die gleiche Lösung, wie Schwarzer Wolf schon hatte, nur in schlecht, weil es eine unnütze Menu-Klasse gibt und zusätzlich noch globale Variablen. Nur weil etwas umfangreich ist, oder vielleicht irgendwann sein wird, ist es noch lange kein Grund, dafür eine Klasse zu definieren. Klassen sollen für sich abgeschlossene, unabhängige Einheiten sein.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: man kann doch zeigen, wie auch umfangreiche Projekte realisierbar sind.

So gibt es etwa in einem Auto einen Schalter für das Blinklicht und außerdem Blinkleuchten, die sind völlig unabhängig voneinander in verschiedenen Fabriken gefertigt und wissen nichts voneinander. Also müssen sie später durch ein Kabel eines Kabelstrangs, hier im Code war es das callbacks Dictionary, miteinander verbunden werden.

Das wollte ich zeigen. In diesem Beispiel wäre das nicht nötig. Allerdings wenn eine Gui größer wird und es schließlich heißen würde: command = self.master.editframe.notizlabelframe.bedienelemente.stati.update_linecount

So etwas wäre völlig verfehlt, weil ein Wissen über den inneren Aufbau nicht kodiert werden sollte. Bei umstrukturierung, paßt es dann nicht mehr.

Es ist immer die Frage, was von der Gui gehört zusammen und ist ein zusamnmengehöriges Paket und was ist ein anderes Paket, das vom einen nichts weiß und mit Signalen - etwa callbacks - zu verbinden ist.

So könnten auch verschiedene Gui Teile in verschiedenen Modulen liegen, die voneinander nichts wissen und nur über ein Modul mit einem Callback Dictionary (der Kabelstrang) miteinander verbunden sind.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: der übliche Weg für callbacks sind bei anderen Gui Frameworks Signale. Hast du etwas dagegen, weil das global ist?

Signale ist dasselbe wie so ein callback Dictionary, nur noch erweitert. Es können sich da auch mehrere Empfänger für ein und dasselbe Signal registrieren, sodass dann beim Senden des Signals verschiedene Callbacks aufgerufen werden.

Das ist also etwas allgemein Übliches und nicht etwas, was man vermeiden sollte. Das Vermeiden geht bei anderen Gui Framworks eh nicht, weil es da nur über Signale geht.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: dann nimmt man aber den Signal-Mechanismus des Frameworks, oder falls es das nicht gibt, schreibt man sich selbst einen. Aber man nimmt KEINE GLOBALEN VARIABLEN!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: dann nimmt man aber den Signal-Mechanismus des Frameworks, oder falls es das nicht gibt, schreibt man sich selbst einen. Aber man nimmt KEINE GLOBALEN VARIABLEN!
Das Dictionary {} wird nur einmal am Anfang zugewiesen: callbacks = {}

callbacks ist daher eine KONSTANTE.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: lies doch in einem der anderen dreihundertsiebenunzwanzig Beiträge nach, wo man Dir schon globale Variablen abgewöhnen wollte, was globale Variablen sind. Das will ich hier nicht wiederholen.
BlackJack

@Alfons Mittelmeyer: Nein das ist keine Konstante. Konstanten haben einen Konstanten *Wert*. Und den Wert veränderst Du. Global.

Warum trollst Du schon wieder? Du weisst ganz genau dass das globaler Zustand ist. Entweder ist das Vorsatz oder Du weisst es nicht, dann wär's Dummheit. An Dummheit mag ich in diesem Fall nicht glauben, also stellt sich die Frage warum Du hier schon wieder mit diesem getrolle anfängst.
Antworten