Framewechsel "zieht" nicht

Fragen zu Tkinter.
Antworten
bob.george
User
Beiträge: 4
Registriert: Freitag 5. November 2021, 14:54

Hallo liebe Forum-Mitglieder,
ich versuche gerade eine TKinter-App mit 3 Frames zu bauen, wobei der mittlere Frame wechselnd sein muss. Folgenden Code habe ich versucht:

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk



class Frame1(ttk.Frame):
    def __init__(self, container, job_selection):
        super().__init__(container)

        self.job_selection = job_selection

        # field options
        self.options = {'padx': 5, 'pady': 5}

        # job label
        self.job_label = ttk.Label(self, text="Parameter " + job_selection)
        self.job_label.grid(column=0, row=0, sticky='w', **self.options)

        # Termin label + Eingabe
        self.termin_label = ttk.Label(self, text="Termin Frame1: ")

        self.termin = tk.StringVar()
        self.termin_entry = ttk.Entry(self, textvariable=self.termin)

        self.termin_label.grid(column=0, row=1, sticky='w', **self.options)
        self.termin_entry.grid(column=1, row=1, sticky='w', **self.options)


        # add padding to the frame and show it
        self.grid(column=0, row=1, padx=5, pady=5, sticky="nsew")

        self.execframe = ExecFrame(container,
                                   self.termin
                                   )
        self.execframe.tkraise()

    def reset(self):
        self.job_label.text = ''


class Frame2(ttk.Frame):
    def __init__(self, container, job_selection):
        super().__init__(container)
        self.job_selection = job_selection

        # field options
        self.options = {'padx': 5, 'pady': 5}

        # job label
        self.job_label = ttk.Label(self, text="Parameter " + job_selection)
        self.job_label.grid(column=0, row=0, sticky='w', **self.options)

        # Termin label + Eingabe
        self.termin_label = ttk.Label(self, text="Termin Frame2: ")

        self.termin = tk.StringVar()
        self.termin_entry = ttk.Entry(self, textvariable=self.termin)

        self.termin_label.grid(column=0, row=1, sticky='w', **self.options)
        self.termin_entry.grid(column=1, row=1, sticky='w', **self.options)

        # add padding to the frame and show it
        self.grid(column=0, row=1, padx=5, pady=5, sticky="nsew")


        self.execframe = ExecFrame(container,
                                   self.termin)
        self.execframe.tkraise()

    def reset(self):
        self.job_label.text = ''


class ExecFrame(ttk.Frame):
    def __init__(self,
                 container,
                 termin,
                 ):
        super().__init__(container)

        self.termin = termin

        self.options = {'padx': 5, 'pady': 20}
        self.start_button = ttk.Button(self, text='Start', command=self.job_start)
        self.start_button.grid(column=0, row=2, sticky='w', **self.options)
        self.grid(column=0, row=2, padx=5, pady=5, sticky="nsew")

    def job_start(self):
        print(self.termin.get())



class ControlFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)

        self.selected_frame = tk.StringVar()
        self.frame_selection = ('Frame1', 'Frame2')
        self.auswahl = ttk.Combobox(self, textvariable=self.selected_frame)
        self.auswahl['values'] = self.frame_selection
        self.auswahl['state'] = 'readonly'
        self.auswahl.current(0)
        self.auswahl.grid(column=0, row=0, padx=5, pady=5)

        self.grid(column=0, row=0, padx=5, pady=5, sticky='ew')

        self.frames = {}
        self.frames['Frame1'] = Frame1(
            container,
            'Aktion1')
        self.frames['Frame2'] = Frame2(
            container,
            'Aktion2')

        self.auswahl.bind('<<ComboboxSelected>>', self.change_frame)

    def change_frame(self, event):
        frame = self.frames[self.auswahl.get()]
        frame.reset()
        frame.tkraise()


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Framebeispiel')
        self.geometry('300x600')
        self.resizable(False, False)


if __name__ == "__main__":
    app = App()
    ControlFrame(app)
    app.mainloop()

Wenn ich es starte zeigt er in der Combobox "Frame1" an und unten aber den Inhalt von Frame 2. Wenn ich jetzt einen Termin eingebe und Start drücke, gibt er diesen auch aus. Jetzt wechsel ich "richtig" zu Frame 1 und gebe nochmal einen Wert ein, aber beim Klick auf Start kommt immer noch der Wert von Frame2. Es scheint, als ob er Frame1 nicht aktiviert. Wie bekomme ich das hin?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Frame1 und Frame2 sind identische Klassen, die sollten also nur eine sein.
Nicht alles muß an self gebunden werden. self.job_selection wird gar nicht benutzt, options hat nichts auf self verloren, job_label, termin_label, und termin_entry werden außerhalb von __init__ auch nicht mehr gebraucht.
StringVar brauchen auch ein Parentwidget.

Ein Frame sollte sich nicht selbst positionieren, das macht die übergeordnete Instanz, und man sollte auch keine Frames außerhalb des eigenen Frames platzieren. Das macht bei Dir die ganzen Probleme, weil ControlFrame die TerminFrames in App platziert und die TerminFrames die ExecFrames auch in App platzieren, beim Wechsel der TerminFrames weiß aber der ControlFrame gar nichts von den ExecFrames.

Der Sinn der ExecFrames mal dahingestellt, sollte App den Controlframe erzeugen und platzieren.
Der Controlframe erzeugt die TerminFrames und platziert sie in sich selbst.
Die Terminframes können meinetwegen ExecFrames erzeugen, diese sollten dann aber innerhalb des eigenen Frames liegen.

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk


class TerminFrame(ttk.Frame):
    def __init__(self, container, job_selection, nummer):
        super().__init__(container)
        self.termin = tk.StringVar(self)

        options = {'padx': 5, 'pady': 5}
        ttk.Label(self, text=f"Parameter {job_selection}").grid(column=0, row=0, sticky='w', **options)
        ttk.Label(self, text=f"Termin Frame{nummer}: ").grid(column=0, row=1, sticky='w', **options)
        ttk.Entry(self, textvariable=self.termin).grid(column=1, row=1, sticky='w', **options)

        self.execframe = ExecFrame(self, self.termin)
        self.execframe.grid(column=0, row=2, padx=5, pady=5, sticky="nsew")

    def reset(self):
        pass


class ExecFrame(ttk.Frame):
    def __init__(self, container, termin):
        super().__init__(container)
        self.termin = termin
        options = {'padx': 5, 'pady': 20}
        ttk.Button(self, text='Start', command=self.job_start).grid(column=0, row=0, sticky='w', **options)

    def job_start(self):
        print(self.termin.get())


class ControlFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)

        self.frames = {}
        for nummer in range(1,3):
            frame = TerminFrame(self, f'Aktion{nummer}', nummer)
            frame.grid(column=0, row=1, padx=5, pady=5, sticky="nsew")
            self.frames[f'Frame{nummer}'] = frame

        self.selected_frame = tk.StringVar(self)
        auswahl = ttk.Combobox(self, textvariable=self.selected_frame)
        auswahl['values'] = list(self.frames)
        auswahl['state'] = 'readonly'
        auswahl.current(0)
        auswahl.grid(column=0, row=0, padx=5, pady=5)
        auswahl.bind('<<ComboboxSelected>>', self.change_frame)
        self.change_frame(None)

    def change_frame(self, event):
        frame = self.frames[self.selected_frame.get()]
        frame.reset()
        frame.tkraise()


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Framebeispiel')
        self.geometry('300x600')
        self.resizable(False, False)
        self.controlframe = ControlFrame(self)
        self.controlframe.grid(column=0, row=0, padx=5, pady=5, sticky='ew')


if __name__ == "__main__":
    app = App()
    app.mainloop()
bob.george
User
Beiträge: 4
Registriert: Freitag 5. November 2021, 14:54

Sirius3 hat geschrieben: Freitag 5. November 2021, 16:14 Frame1 und Frame2 sind identische Klassen, die sollten also nur eine sein.
Danke für die vielen Hinweise. Ich habe mein Beispiel bewusst vereinfacht, um mein Problem zu verdeutlichen. Letztendlich sollten jedoch 2 alternative Frames in der Mitte platziert werden mit teilweise unterschiedlichen Inhalten. Die Auswahl des Frames soll über den ControlFrame erfolgen. Der ExecFrame soll dann die Eingabedaten eines der beiden Frames "abholen" und ausgeben bzw. weiter verarbeiten.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Entweder löst meine Antwort Dein Problem, oder Du hast es zu sehr vereinfacht. Dann fehlt wichtige Information.
bob.george
User
Beiträge: 4
Registriert: Freitag 5. November 2021, 14:54

Wahrscheinlich hab ich es zu sehr vereinfacht. Die widgets im TerminFrame hängen von der ausgewählten nummer ab. Das kann ich jetzt z.B. mit einer Bedingung lösen:

Code: Alles auswählen

class TerminFrame(ttk.Frame):
    def __init__(self, container, job_selection, nummer):
        super().__init__(container)
        self.termin = tk.StringVar(self)

        options = {'padx': 5, 'pady': 5}
        ttk.Label(self, text=f"Parameter {job_selection}").grid(column=0, row=0, sticky='w', **options)
        ttk.Label(self, text=f"Termin Frame{nummer}: ").grid(column=0, row=1, sticky='w', **options)
        ttk.Entry(self, textvariable=self.termin).grid(column=1, row=1, sticky='w', **options)

        if nummer == 2:
            ttk.Label(self, text=f"Sondertermin Frame{nummer}: ").grid(column=0, row=2, sticky='w', **options)
            ttk.Entry(self, textvariable=self.termin).grid(column=1, row=2, sticky='w', **options)

        self.execframe = ExecFrame(self, self.termin)
        self.execframe.grid(column=0, row=3, padx=5, pady=5, sticky="nsew")

    def reset(self):
        pass
Aber das muss doch auch eleganter bzw. "objektorientierter" gehen.
bob.george
User
Beiträge: 4
Registriert: Freitag 5. November 2021, 14:54

Ich hab jetzt nach viel Probieren und Forschen folgende Lösung gefunden, die auch mein Problem ganz gut widerspiegelt:

Code: Alles auswählen

import tkinter as tk
from tkinter import ttk

class BasisFrame(ttk.Frame):
    def __init__(self, container, job_selection):
        super().__init__()
        options = {'padx': 5, 'pady': 5}
        ttk.Label(self, text=f"Parameter {job_selection}").grid(column=0, row=0, sticky='w', **options)

        ttk.Label(self, text=f"{job_selection}: ").grid(column=0, row=1, sticky='w', **options)
        self.inhalt_aktion = tk.StringVar(self)
        ttk.Entry(self, textvariable=self.inhalt_aktion).grid(column=1, row=1, sticky='w', **options)


class Termin1Frame(BasisFrame):
    def __init__(self, container, job_selection):
        super().__init__(container, job_selection)
        self.termin1 = tk.StringVar(self)

        options = {'padx': 5, 'pady': 5}

        ttk.Label(self, text="Termin 1: ").grid(column=0, row=2, sticky='w', **options)
        ttk.Entry(self, textvariable=self.termin1).grid(column=1, row=2, sticky='w', **options)

        options = {'padx': 5, 'pady': 20}
        ttk.Button(self, text='Start', command=self.job_start).grid(column=0, row=8, sticky='w', **options)

    def job_start(self):
        print(self.inhalt_aktion.get())
        print(self.termin1.get())


    def reset(self):
        pass


class Termin2Frame(Termin1Frame):
    def __init__(self, container, job_selection):
        super().__init__(container, job_selection)
        self.termin2 = tk.StringVar(self)

        options = {'padx': 5, 'pady': 5}

        ttk.Label(self, text="Termin 2: ").grid(column=0, row=3, sticky='w', **options)
        ttk.Entry(self, textvariable=self.termin2).grid(column=1, row=3, sticky='w', **options)

        options = {'padx': 5, 'pady': 20}
        ttk.Button(self, text='Start', command=self.job_start).grid(column=0, row=8, sticky='w', **options)

    def job_start(self):
        print(self.inhalt_aktion.get())
        print(self.termin1.get())
        print(self.termin2.get())


    def reset(self):
        pass


class ControlFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)

        self.frames = {}

        frame = Termin1Frame(self, 'Aktion 1')
        frame.grid(column=0, row=1, padx=5, pady=5, sticky="nsew")
        self.frames['Aktion 1'] = frame

        frame = Termin2Frame(self, 'Aktion 2')
        frame.grid(column=0, row=1, padx=5, pady=5, sticky="nsew")
        self.frames['Aktion 2'] = frame

        self.selected_frame = tk.StringVar(self)
        auswahl = ttk.Combobox(self, textvariable=self.selected_frame)
        auswahl['values'] = list(self.frames)
        auswahl['state'] = 'readonly'
        auswahl.current(0)
        auswahl.grid(column=0, row=0, padx=5, pady=5)
        auswahl.bind('<<ComboboxSelected>>', self.change_frame)
        self.change_frame(None)

    def change_frame(self, event):
        frame = self.frames[self.selected_frame.get()]
        frame.reset()
        frame.tkraise()


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Framebeispiel')
        self.geometry('300x600')
        self.resizable(False, False)
        self.controlframe = ControlFrame(self)
        self.controlframe.grid(column=0, row=0, padx=5, pady=5, sticky='ew')



if __name__ == "__main__":
    app = App()
    ControlFrame(app)
    app.mainloop()
Was meint ihr dazu? Ist eine gangbare Lösung oder bin ich komplett auf dem Holzweg? Hab ich vielleicht noch ein paar grobe Schnitzer drin, die man besser gestalten kann bzw. muss?
Antworten