'tkinter - duplicate entries in dictionary and unexpected pack() behavior

I am writing a tkinter program that will have multiple modes and would like to change the frames on various parts of the window in response to mode changes.

The following code is intended to create an abstract SwitcherClass that can toggle between contained frames. SwitchedFrame is derived from the SwitcherClass and has a class attribute that lists the frames that should be switched between (in this case Myclass and Myotherclass).

It has two unexpected behaviors:

  1. The dictionary self.children in SwitcherClass should have two entries, but it ends up with four. The Subclass objects end up in the dictionary as keys as well as values, so that each run through the loop creates two dictionary entries!

  2. The pack() and pack_forget() calls in the SwitcherClass do nothing. In fact, if the children of SwitcherClass are never packed, they still show up in the main window. Why?

I would very much appreciate answers to either or both of these puzzles.

import tkinter as tk

class Myclass(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        tk.Button(text='Myclass',command=parent.switch).pack()

class Myotherclass(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        tk.Button(text='Myotherclass',command=parent.switch).pack()

class SwitcherClass(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        self.children = {}
        for label,Subclass in self.contains:
            self.children[label] = Subclass(self)
        print(self.children)
        self.frame = 'first'
        self.children[self.frame].pack()        

    def switch(self):
        old_frame = self.frame
        if self.frame == 'second':
            self.frame = 'first'
        else:
            self.frame = 'second'
        self.children[old_frame].pack_forget()
        self.children[self.frame].pack()

class SwitchedFrame(SwitcherClass):
    contains = (('first',Myclass),('second',Myotherclass))

    
root = tk.Tk()
tk.Label(root,text='root frame').pack()
SwitchedFrame(root).pack()
root.mainloop()


Solution 1:[1]

First: every widget in tkinter uses self.children to keep own children so use different name for yours - ie. self.my_children. Now you see own widgets and widgets added automatically by tkinter.

Second: widget without parent is assigned to root so your buttons are assigned to root too, and you see them all time in main window, not in subframes (but they change frames which without buttons inside have no size).

You have to use Button(self, ...) to assign buttons to correct frames.

import tkinter as tk

class Myclass(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        tk.Button(self, text='Myclass',command=parent.switch).pack()

class Myotherclass(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        tk.Button(self, text='Myotherclass',command=parent.switch).pack()

class SwitcherClass(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.my_children = {}
        for label,Subclass in self.contains:
            self.my_children[label] = Subclass(self)
        for x in self.my_children:
            print('child:', x)
        self.frame = 'first'
        self.my_children[self.frame].pack()        

    def switch(self):
        old_frame = self.frame
        if self.frame == 'second':
            self.frame = 'first'
        else:
            self.frame = 'second'
        self.my_children[old_frame].pack_forget()
        self.my_children[self.frame].pack()

class SwitchedFrame(SwitcherClass):
    contains = (('first',Myclass), ('second',Myotherclass))


root = tk.Tk()
tk.Label(root,text='root frame').pack()
SwitchedFrame(root).pack()
root.mainloop()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1