Frames don't stack on top of each other in tkinter

I have a problem stacking "Pages" on top of each other in tkinter.

I have a main Frame

one that contains two subframes that contain different information. The first subframe contains buttons Listbox

and a pair and is packed left in the main frame. It is assumed that the 2nd frame will represent different "Pages" (two at the moment) and fill the entire frame. My problem is that both Pages are displayed side by side and not on top of each other.

import tkinter as tk


class Settings(tk.Tk):

    def __init__(self, master=None):
        tk.Tk.__init__(self, master)
        self.focus_force()
        self.grab_set()
        # set focus to settings window
        # Main window title
        self.title("Settings")


        # set up grid containers
        container_main = tk.Frame(self, width=500, height=700)
        container_main.pack(side='top', fill='both', expand=True)
        container_main.grid_rowconfigure(0, weight=1)
        container_main.grid_columnconfigure(0, weight=1)

        container_listbox = tk.Frame(container_main, bg='blue', width=200, height=700)
        container_listbox.pack(side='left', fill='both', expand=True)
        container_listbox.grid_rowconfigure(0, weight=1)
        container_listbox.grid_columnconfigure(0, weight=1)

        container_settings = tk.Frame(container_main, bg='red', width=300, height=700)
        container_settings.pack(side='right', fill='both', expand=True)
        container_settings.grid_rowconfigure(0, weight=1)
        container_settings.grid_columnconfigure(0, weight=1)

        # build settings pages
        self.frames = {}

        self.frames["Options"] = Options(parent=container_listbox, controller=self)
        self.frames["General"] = General(parent=container_settings, controller=self)
        self.frames["Future"] = Future(parent=container_settings, controller=self)

      

if I disagree with these two lines. I am getting an error: I cannot use the geometry manager mesh internally.

        # self.frames["General"].grid(row=0, column=0, sticky='nsew')
        # self.frames["Future"].grid(row=0, column=0, sticky='nsew')

      

...

    def show_frame(self, page_name):
        frame = self.frames[page_name]
        frame.tkraise()


class Options(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(parent, text='List Box')
        label.grid(row=0, column=0, sticky='nsew', padx=1, pady=1)
        button1 = tk.Button(parent, text='General', command=lambda: controller.show_frame('General'))
        button2 = tk.Button(parent, text='Future', command=lambda: controller.show_frame('Future'))
        button1.grid(row=1, column=0, sticky='ew')
        button2.grid(row=2, column=0, sticky='ew')


class General(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(parent, text='General')
        label.pack(side='left', fill='both', expand=True, )
        print("Hi I'm General")

class Future(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(parent, text='Future')
        label.pack(side='left', fill='both', expand=True)
        print("Hi I'm Future")

app = Settings()
app.mainloop()

      

Both "Pages" are initialized and displayed at the same time, which makes sense. I just don’t know how to do one above the other as I frame.tkraise()

have to do it, but it’s not. I would also like to be able to do grid_forget()

on a page or pages that are not on top to avoid accidentally entering values ​​into a hidden input box in the future.

EDIT: If I comment out the Future page, the General page will take up all of the frame space, so with help grid_forget()

I'll give the same result. I just don't know where I would like to, but grid_forget()

then where would I reconfigure or make a call grid()

?

+3


source to share


2 answers


My problem is that both Pages are displayed side by side and not on top of each other.

If you use pack()

to position a frame in the root window and then use grid()

inside that frame then it will work, but if you try to use pack()

inside a frame and then try to use grid()

inside that same frame it won't work.

The same goes for the root window and frames. If the pack()

frame is in the root window, you cannot use grid()

to place anything in the same root window.

The problem with the grid()

vs issue pack()

was that the location where the class General

and class Future

set up the shortcut widgets was the parent frame in which it was used pack()

. This prevented it from being used grid()

in the same parent frame to accommodate shared and future frames.

To fix this, we'll change:



label = tk.Label(parent, text='General')

and

label = tk.Label(parent, text='Future')

      

in

label = tk.Label(self, text='General')

and

label = tk.Label(self, text='Future')

      

above was the only fix needed to work properly.

import tkinter as tk


class Settings(tk.Tk):

    def __init__(self, master=None):
        tk.Tk.__init__(self, master)
        self.focus_force()
        self.grab_set()
        # set focus to settings window
        # Main window title
        self.title("Settings")

        container_main = tk.Frame(self, width=500, height=700)
        container_main.pack(side='top', fill='both', expand=True)
        container_main.grid_rowconfigure(0, weight=1)
        container_main.grid_columnconfigure(0, weight=1)

        container_listbox = tk.Frame(container_main, bg='blue', width=200, height=700)
        container_listbox.pack(side='left', fill='both', expand=True)
        container_listbox.grid_rowconfigure(0, weight=1)
        container_listbox.grid_columnconfigure(0, weight=1)

        container_settings = tk.Frame(container_main, bg='red', width=300, height=700)
        container_settings.pack(side='right', fill='both', expand=True)
        container_settings.grid_rowconfigure(0, weight=1)
        container_settings.grid_columnconfigure(0, weight=1)

        self.frames = {}

        self.frames["Options"] = Options(parent=container_listbox, controller=self)
        self.frames["General"] = General(parent=container_settings, controller=self)
        self.frames["Future"] = Future(parent=container_settings, controller=self)   


        self.frames["General"].grid(row=0, column=0, sticky='nsew')
        self.frames["Future"].grid(row=0, column=0, sticky='nsew')

    def show_frame(self, page_name):
        frame = self.frames[page_name]
        frame.tkraise()

class Options(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(parent, text='List Box')
        label.grid(row=0, column=0, sticky='nsew', padx=1, pady=1)
        button1 = tk.Button(parent, text='General', command=lambda: controller.show_frame('General'))
        button2 = tk.Button(parent, text='Future', command=lambda: controller.show_frame('Future'))
        button1.grid(row=1, column=0, sticky='ew')
        button2.grid(row=2, column=0, sticky='ew')


class General(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text='General')
        label.pack(side='left', fill='both', expand=True)
        print("Hi I'm General")

class Future(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text='Future')
        label.pack(side='left', fill='both', expand=True)
        print("Hi I'm Future")

app = Settings()
app.mainloop()

      

+2


source


I took the liberty of restructuring your application logic to communicate how it might be easier to solve your problem. I've also added some additional information.

The problem with your current implementation in its ability hide

and show

your pages is in the keyword assignment command

for your buttons. The realization that I have provided for the class Options

- this is where all the magic happens - in particular, methods show_general

and show_future

. It's just that when the shared button is clicked, I call pack_forget()

to the widget future_page

, pack(...)

to the widget general_page

and vice versa, when I want to show the future page. (This can be easily scaled up, but I'll leave that up to you.)

Also, I structured the application logic in such a way that it modulates the code, so at every step I could test it and see where I am, for example. I started by main()

creating each container and made sure everything was positioned correctly (I like to give each container a different color, making it easier to visualize the spacing, etc.), before going to Settings

and Options

where I would place the widgets and write the code needed in order for the GUI to function as intended.



I won't explain every line, but I'm sure at this point you can see what I am getting. I recommend reading the code (starting at main()

) and figuring out how and why I wrote it the way I did it. (I wrote this to function as I assume you intended, and also to offer some pointers here and there - it doesn't have to be perfect.)

import tkinter as tk

class Options(tk.Frame):

    def __init__(self, master):

        tk.Frame.__init__(self, master, bg='#FFFFFF', relief='ridge', bd=1)

        # Grab the 'Settings' tk.Frame object
        settings_frame = self.master.master.winfo_children()[1].winfo_children()[0]
        self.settings = settings_frame

        self.options()


    def options(self):
        self.label = tk.Label(self, text='List Box')
        self.button1 = tk.Button(self, text='General', command=self.show_general)
        self.button2 = tk.Button(self, text='Future', command=self.show_future)

        self.label.grid(row=0, sticky='nsew', padx=1, pady=1)  # column is set to 0 by default
        self.button1.grid(row=1, sticky='nsew', padx=1, pady=1)
        self.button2.grid(row=2, sticky='nsew', padx=1, pady=1)


    def show_general(self):
        self.settings.future_page.pack_forget()
        self.settings.general_page.pack(fill='both', expand=True)


    def show_future(self):
        self.settings.general_page.pack_forget()
        self.settings.future_page.pack(fill='both', expand=True)


class Settings(tk.Frame):

    def __init__(self, master):

        tk.Frame.__init__(self, master, bg='#FFFFFF', relief='ridge', bd=1)

        self.pages()


    def pages(self):
        self.general_page = tk.Label(self, fg='#FFFFFF', bg='#FF0000',
            relief='ridge', bd=1, text="Hi, I'm General.")
        self.future_page = tk.Label(self, fg='#FFFFFF', bg='#0000FF',
            relief='ridge', bd=1, text="Hi, I'm Future.")

        self.general_page.pack(fill='both', expand=True)


def main():
    root = tk.Tk()
    root.title('Settings')
    root.configure(bg='#DDDDDD', width=500, height=500)
    root.resizable(width=False, height=False)

    main_container = tk.Frame(root, bg='#FFFFFF', width=500, height=500)
    main_container.pack(fill='both', padx=20, pady=20)  # Add some padding to see container difference
    main_container.pack_propagate(False)  # Avoid sizing based on widget contents

    listbox_left = tk.Frame(main_container, bg='#4285F4', width=235)  # Take 15 from both sides for padding
    settings_right = tk.Frame(main_container, bg='#272727', width=235)

    listbox_left.pack(side='left', fill='y', padx=(10, 0), pady=10)
    listbox_left.pack_propagate(False)
    settings_right.pack(side='right', fill='y', padx=(0, 10), pady=10)
    settings_right.pack_propagate(False)

    settings = Settings(settings_right)  # Must be instantiated before Options
    options = Options(listbox_left)

    settings.pack(fill='both', expand=True, padx=2, pady=2)
    options.pack(fill='both', expand=True, padx=2, pady=2)

    root.mainloop()


if __name__ == '__main__':
    main()

      

Hope this helps!

+2


source







All Articles