0

I'm building a GUI with code that originally came from another stack exchange answer. I've modified it since I want to pass variables to following pages and have them display the values. As a way to do this, rather than display on a button event, I am trying to create the page with the show() method:

import Tkinter as tk
#import vpnRename12 as Rename
#import vpnRename_pullconfig as pullConfig

"""
Create multiple pages to go through in the same frame. Use lift() to bring
the desired page into view and stack them on top of one another. Define
page-specific methods in each page.
"""

class vpnRenameProgram(tk.Tk):
    """
    Create a page class that will allow you to lift to front the applicable
    page with the proper functions to keep the script running.
    """
    def __init__(self,*args,**kwargs):
    
        tk.Tk.__init__(self,*args,**kwargs)
        
        # Create an empty dictionary to contain all of the page names
        self.frames = {}
    
        self.show("MainView")

    # Instantiate the next page on call, rather than at start to allow 
    flexibility
    def instantiate_page(self, cls):
        """
        Since all of the pages are defined in the same scope/namespace, we 
        can use
        the globals()[] dict to find and instantiate the pages dynamically 
        with the show() method.
        cls is the class argument we are doing a lookup on in the global() 
        dict. 
        """
    
        try:
            newframe = globals()[cls](container,self)
            page_name = newframe.__name__
        except:
            print("\nError defining inline class %s"%cls)#("Class %s is not defined" %cls)
            newframe = None
            page_name=globals()[cls].__name__
        
        return newframe, page_name

    # Create lift function to bring desired page to front of view, 
    #instantiate page if
    # it isn't already (check frames dict)
    def show(self, cls):
        if cls not in self.frames.keys():
            container = tk.Frame(self)
            container.pack(side="top", fill="both", expand=True)
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)
    
            frame, page_name = self.instantiate_page(cls)
        
            if frame==None:
                frame = globals()[cls](parent=container, controller=self)
        
            self.frames[page_name] = frame
            frame.grid(row=0, column=0, sticky="news")
    
        frame = self.frames[cls]
        frame.lift()
    
    def get_page(self, classname):
        """
        Return instance of page when it's class name is passed in as string
        """
        for page in self.frames.values():
            if str(page.__class__.__name__) == classname:
                return page
            return None

class MainView(tk.Frame):

    def __init__(self,parent, controller,**kwargs):
        tk.Frame.__init__(self,parent)
        self.controller = controller
        self.edit_directory="edit_dir"
        self.complete_directory ="comp_dir"
    
        TitleLabel = tk.Label(self, text="VPN Script Editor")
        TitleLabel.pack({"side":"top"})
    
        EditLabel = tk.Label(self, text="Edit File Directory: 
            %s"%self.edit_directory)
        EditLabel.pack()
    
        CompLabel = tk.Label(self, text="Completed File Directory: 
            %s"%self.complete_directory)
        CompLabel.pack()
    
        Next = tk.Button(self, text="Next", command=lambda: 
            controller.show("listVPN"))
        Next.pack()
    
    
class listVPN(tk.Frame):
    """
    This is the second page, it contains a text box where you will list the 
    names of the vpn's that you want to edit. It will also display the 
    directories
    obtained by the pullconfig script.
    """

    def read_list(self):
        vpn_list=str(self.var_vpn_list.get()).upper()
        return vpn_list

    def __init__(self, parent, controller, **kwargs):
        self.controller = controller
        tk.Frame.__init__(self, parent)
    
        self.var_vpn_list = tk.StringVar()
    
    
        label=tk.Label(self, text="Please list the VPNs desired to edit")
        label.pack()
    
        #Create text box to submit a list of vpns back to the main program
        vpnLabel = tk.Label(self, text="VPN Names").pack()
        self.TextBox = tk.Entry(self, textvariable=self.var_vpn_list)
        self.TextBox.pack()
    
        vpnListSubmit = tk.Button(self, text="Enter", command= lambda: 
        self.read_list() and self.controller.show("pickFiles"))
        vpnListSubmit.pack()
        
    
class pickFiles(tk.Frame):
    """
    Second page that allows you to select your desired files from the 
    edit directory specified in the config file. Check all desired files, 
    list will be returned to the program.
    """

    def get_vpn_list(self):
        list = self.controller.get_page("listVPN").var_vpn_list.get()
        self.vpn_list = str(list).upper()
    
        self.vpn_label.configure(text="VPN List: %s"%self.vpn_list)
    
        return self.vpn_list

    def __init__(self, parent, controller,**kwargs):

        # Inherits from the tk.Frame class 
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.vpn_list = tk.StringVar()
    
        list = self.controller.get_page("listVPN").var_vpn_list.get()
        self.vpn_list = str(list).upper()
    
    
    
        show_vpn = tk.Button(self, text="Show vpnlist", command = 
            self.get_vpn_list)
        show_vpn.pack()
    
        self.vpn_label = tk.Label(self, text="VPN List: %s" %self.vpn_list)
        self.vpn_label.pack()
    
        # todo: get external module function to run with variable input
        #file_list = Rename.searchFile(vpnlist)

# Execute program on calling the parent class       
if __name__=="__main__":
    app = vpnRenameProgram()
    app.mainloop()

EDIT: Above is my whole code with custom scripts I've imported commented out. My main question is about layout. I want the frames to stack on top of one another, but they are not. Why is it doing this and what would get me on track to getting the layout I want?

pppery
  • 3,731
  • 22
  • 33
  • 46
C.Nivs
  • 12,353
  • 2
  • 19
  • 44
  • I'm very confused about what you want. Can you provide an complete example that we can run that shows the problem you are having? – Novel Apr 14 '17 at 15:44
  • _"I've modified it since I want to pass variables to following pages "_ - why have you done that? The code you copied from makes it very simple to either share a common block of data between pages, or have each page get the data it wants from any other page. There is no need to pass data to the pages when you create them,. – Bryan Oakley Apr 14 '17 at 17:55
  • This statement is hard to understand: _"I want each page to only use the one instance of container, since this is causing each page to layer separately."_ Each page _is_ using only one instance of the container. You don't need to change anything. Also, you wrote _"I don't want container to be a global variable"_, but `container` is not global. It's a local variable passed to each page. – Bryan Oakley Apr 14 '17 at 17:58
  • Have you read the following questions and answers? http://stackoverflow.com/questions/32212408/how-to-get-variable-data-from-a-class and http://stackoverflow.com/questions/33646605/how-to-access-variables-from-different-classes-in-tkinter-python-3? Can you explain why neither of those pages solve your problem? – Bryan Oakley Apr 14 '17 at 18:01
  • This appears to be an [XY problem](http://xyproblem.info/). Perhaps instead of trying to get us to fix your broken code, you can explain the real problem that you are trying to solve. – Bryan Oakley Apr 14 '17 at 18:04
  • Essentially my problem is layout. The windows, instead of stacking on top of one another and obscuring the previous page, are displaying in different locations. I don't think I completely understand why it's displaying in this fashion. – C.Nivs Apr 14 '17 at 19:02
  • I've edited the question with runnable script and a clarification of what I'm looking for. I don't know why the frames aren't stacking on top of one another the way I want. – C.Nivs Apr 14 '17 at 19:24
  • your edited code has some hard linebreaks in the middle of the line, preventing the code from running. – Bryan Oakley Apr 14 '17 at 20:04

1 Answers1

0

The main problem with your code is that you're creating multiple containers. The code that served as a base for your program was specifically designed to have a single container with multiple frames within the container.

The first step is to create the container, and save a reference so it can be used later:

class vpnRenameProgram(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.container = tk.Frame(self)
        self.container.pack(side="top", fill="both", expand=True)
        self.container.grid_rowconfigure(0, weight=1)
        self.container.grid_columnconfigure(0, weight=1)

        self.frames = {}
        self.show_frame("MainView")

I'm also going to recommend that you pass the actual class instead of a class name. This way you don't have to dig into globals() to try to find the right class based on the name.

Change the last line in the above to look like this:

    self.show_frame(MainView)

You will also need to change get_page, but it's now a simple lookup:

def get_page(self, page_class):
    return self.frames.get(page_class, None)

The final step is to redefine show to create the frame on demand. You've created a method called instantiate_page, but I see no real reason not to put it all in a single function since it's only a couple extra lines of code:

def show(self, page_class):
    if page_class in self.frames:
        frame = self.frames[page_class]
    else
        frame = page_class(parent=self.container, controller=self)
        frame.grid(row=0, column=0, sticky="nsew")
        self.frames[page_class] = frame

    frame.tkraise()

That's all there is to it. You just need to remember to pass the class rather than the name of the class when calling show or get_page (eg: controller.show(listVPN), controller.get_page(pickFiles)`, etc)

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks so much, that pretty much hits exactly where I thought I was hitting an issue. Now it's creating a single container which I wanted. – C.Nivs Apr 15 '17 at 22:52