2

I've used Tkinter to create a GUI with different menu options (a similar example is produced below). Each menu has different commands, which when clicked create a new frame. Now what is happening is if I switch to a different command, the new frame stacks below the current frame instead of replacing the old one.

I want to know what is the best way to move forward.

import Tkinter as tkinter
root = tkinter.Tk()
root.minsize(400,300)
welcome = tkinter.Frame(root).grid()
label = tkinter.Label(welcome, text="Welcome to my program").grid(row=0, column=3)
button = tkinter.Button(welcome,text="Exit",command=root.destroy).grid(row=3, column=1)
def newFrame():
newFrame = tkinter.Frame(root).grid()
newFrame_name = tkinter.Label(newFrame, text="This is another frame").grid()
menu = tkinter.Menu(root)
root.config(menu=menu)
main_menu = tkinter.Menu(menu)
menu.add_cascade(label="Main Menu", menu= main_menu)
main_menu.add_command(label="New Frame", command=newFrame)
main_menu.add_command(label="Another Frame", command=newFrame)
#menu.add_command(label="Exit", command=root.destroy, menu= filemenu)
root.mainloop()

Now if I switch between New Frame and Another Frame, the windows stack up, but I want one window to replace the other.

Any ideas? Thanks.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
nEO
  • 5,305
  • 3
  • 21
  • 25
  • Your code is full of bugs. Indentation is off, and there are typos and other mistakes. For example, you reference `Frame1` in `Generator` but `Frame1` is undefined. There's also a bunch of code that seems unrelated to the problem. – Bryan Oakley Oct 06 '14 at 16:38
  • Are all of these dynamically created frames identical, or do they each have custom content? – Bryan Oakley Oct 06 '14 at 16:53
  • @BryanOakley this is a very sample content. Please don't look at indentation, or other bugs. The thing is it won't be possible for me to share the exact code. So what I've done is given an idea of what I am doing. The question is - Can I shift between frames when I click from one menu to other without stacking frames. – nEO Oct 07 '14 at 04:24
  • Guys, I think I managed to do it. So here's what I did. I don't know whether's its pythonic. Whenever a menu button is called, the frame is generated. I added this to a list and then then used .pack_forget() to hide all existing frames i.e. in a list with length n, I hid n-1 frames. This ensures that the current frame is still there while all other frames that I've used are gone. This seems to work. Thanks to all for the comments – nEO Oct 07 '14 at 05:03
  • if you can't give us code that actually duplicates the problem, it makes us hard to help you. If you expect us to spend time trying to figure out your code, you should likewise be willing to spend time writing a high quality question. But honestly, the code isn't for us. – Bryan Oakley Oct 07 '14 at 10:39

3 Answers3

5

Here is a minimal example of one method I used recently; the key is in PythonGUI.show_frame, which moves the appropriate frame to the front for display.

import Tkinter as tk

class BaseFrame(tk.Frame):
    """An abstract base class for the frames that sit inside PythonGUI.

    Args:
      master (tk.Frame): The parent widget.
      controller (PythonGUI): The controlling Tk object.

    Attributes:
      controller (PythonGUI): The controlling Tk object.

    """

    def __init__(self, master, controller):
        tk.Frame.__init__(self, master)
        self.controller = controller
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        """Create the widgets for the frame."""
        raise NotImplementedError


class ExecuteFrame(BaseFrame):
    """The application home page.

    Attributes:
      new_button (tk.Button): The button to switch to HomeFrame.

    """

    def create_widgets(self):
        """Create the base widgets for the frame."""
        self.new_button = tk.Button(self,
                                    anchor=tk.W,
                                    command=lambda: self.controller.show_frame(HomeFrame),
                                    padx=5,
                                    pady=5,
                                    text="Home")
        self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)


class HomeFrame(BaseFrame):
    """The application home page.

    Attributes:
      new_button (tk.Button): The button to switch to ExecuteFrame.

    """

    def create_widgets(self):
        """Create the base widgets for the frame."""
        self.new_button = tk.Button(self,
                                    anchor=tk.W,
                                    command=lambda: self.controller.show_frame(ExecuteFrame),
                                    padx=5,
                                    pady=5,
                                    text="Execute")
        self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)


class PythonGUI(tk.Tk):
    """The main window of the GUI.

    Attributes:
      container (tk.Frame): The frame container for the sub-frames.
      frames (dict of tk.Frame): The available sub-frames.

    """

    def __init__(self):
        tk.Tk.__init__(self)
        self.title("Python GUI")
        self.create_widgets()
        self.resizable(0, 0)

    def create_widgets(self):
        """Create the widgets for the frame."""             
        #   Frame Container
        self.container = tk.Frame(self)
        self.container.grid(row=0, column=0, sticky=tk.W+tk.E)

        #   Frames
        self.frames = {}
        for f in (HomeFrame, ExecuteFrame): # defined subclasses of BaseFrame
            frame = f(self.container, self)
            frame.grid(row=2, column=2, sticky=tk.NW+tk.SE)
            self.frames[f] = frame
        self.show_frame(HomeFrame)

    def show_frame(self, cls):
        """Show the specified frame.

        Args:
          cls (tk.Frame): The class of the frame to show. 

        """
        self.frames[cls].tkraise()

if __name__ == "__main__":
    app = PythonGUI()
    app.mainloop()
    exit()
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Hi, That was really helpful. But the thing is I've around 20 functions. So does that means I've to declare 20 classes. That'd be tough. I do understand somewhat what you did there. Isnt there a simpler way to clear out existing frames when you open a new frame. – nEO Oct 06 '14 at 12:23
  • 2
    @nEO but presumably you have to define those 20 sets of widgets *somewhere*, and doing so in these `BaseFrame` subclasses keeps everything related together. – jonrsharpe Oct 06 '14 at 12:38
2

There are two basic ways to solve the problem:

  1. stack all of the frames on top of each other (eg: put in the same grid cell, or use place with the same options) and then raise the one that should be visible to the top of the stack (using frame.lift()). An example of the technique is in this answer: Switch between two frames in tkinter

  2. Whenever you show a new frame, destroy (with .destroy() or hide (with pack_forget or grid_forget) the old frame.

Community
  • 1
  • 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
2

I have a simple example. I'm sorry, because I don't know how to do this code by class. I just using simple code. But it's works! :D Here it is:

from Tkinter import *
def gantihal(frame):
    frame.tkraise()

root = Tk()

f1 = Frame(root)
f2 = Frame(root)
f3 = Frame(root)
f4 = Frame(root)
for frame in (f1, f2, f3, f4):
    frame.grid(row=0, column=0, sticky='news')

Button(f1, text='goto frame 2', command=lambda:gantihal(f2)).pack()
Label(f1, text='this is in frame 1').pack()

Label(f2, text='this is in frame two').pack()
Button(f2, text='goto frame 3', command=lambda:gantihal(f3)).pack()

Label(f3, text='this is in frame 3').pack(side='left')
Button(f3, text='next frame :)', command=lambda:gantihal(f4)).pack(side='left')

Label(f4, text='fourth frame').pack()
Button(f4, text='goto 1st frame', command=lambda:gantihal(f1)).pack()

gantihal(f1)
root.mainloop()
recobayu
  • 581
  • 4
  • 6