Tkinter: How to Show / Hide a Window

Today we're going to take a look at Tkinter! I was curious about how one would go about hiding a frame and then re-showing it using Tkinter and I kept finding threads (like this one) that talked about using withdraw() and deiconify() but didn't really provide any usable code. In wxPython, I did this sort of thing using pubsub. We'll go over three different versions of how to hide and show the root frame.

My First Example

A lot of example code for Tkinter is not very object oriented. What I mean by that is that the code I see isn't in classes. To each their own, but I find GUI code easier to follow in a class. Anyway, that how I ended up creating my first example:

import Tkinter as Tk

########################################################################
class MyApp(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        self.root = parent
        self.root.title("Main frame")
        self.frame = Tk.Frame(parent)
        self.frame.pack()
                
        btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
        btn.pack()
        
    #----------------------------------------------------------------------
    def hide(self):
        """"""
        self.root.withdraw()
        
    #----------------------------------------------------------------------
    def openFrame(self):
        """"""
        self.hide()
        otherFrame = Tk.Toplevel()
        otherFrame.geometry("400x300")
        otherFrame.title("otherFrame")
        handler = lambda: self.onCloseOtherFrame(otherFrame)
        btn = Tk.Button(otherFrame, text="Close", command=handler)
        btn.pack()
        
    #----------------------------------------------------------------------
    def onCloseOtherFrame(self, otherFrame):
        """"""
        otherFrame.destroy()
        self.show()
        
    #----------------------------------------------------------------------
    def show(self):
        """"""
        self.root.update()
        self.root.deiconify()
        
    
#----------------------------------------------------------------------
if __name__ == "__main__":
    root = Tk.Tk()
    root.geometry("800x600")
    app = MyApp(root)
    root.mainloop()

Let's take a few moments to break this down a little. We have a simple class where we pass in a "root" object (Tk.Tk()) as the top-level parent. This in turn is used as the parent of the Tk.Frame. The pack() command is one of the geometry managers that Tkinter comes with. It allows you to "pack" widgets into columns or rows and has various options like fill, expand and side. Next we create a Tk.Button and pack it. If you don't call pack (or one of the other geometry managers) then your widgets won't appear at all. In the Button instantiation process, we pass it a parent, a string for its label and a command to be run when the button is clicked.

When the user clicks the button, we create another Top level window and give it a different title, size and a close button. We use the lambda anonymous method to create the callback as we need to pass the otherFrame instance to the handler so we can close it. We could have just created the otherFrame as a class property (i.e. self.otherFrame) too and skipped the lambda, but if you do much with Tkinter, then you really need to get used to seeing that kind of callback setup. When the close button is called, it destroys the otherFrame and calls the show method, which shows the original frame. Some examples say that you need to call the update() method before you call the deiconify() one, however if you comment out the update() call, you'll see that it works fine. At least it did on Windows 7 with Python 2.6.

Now let's try splitting the second frame into its own class!

Splitting the Second Frame into a Class

Putting the second frame into it's very own class promotes code re-use and better organization of your code, especially if the second frame were to be really complex. Here's one way to do it:

import Tkinter as Tk

########################################################################
class OtherFrame(Tk.Toplevel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        Tk.Toplevel.__init__(self)
        self.geometry("400x300")
        self.title("otherFrame")
    
########################################################################
class MyApp(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        self.root = parent
        self.root.title("Main frame")
        self.frame = Tk.Frame(parent)
        self.frame.pack()
                
        btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
        btn.pack()
        
    #----------------------------------------------------------------------
    def hide(self):
        """"""
        self.root.withdraw()
        
    #----------------------------------------------------------------------
    def openFrame(self):
        """"""
        self.hide()
        subFrame = OtherFrame()
        handler = lambda: self.onCloseOtherFrame(subFrame)
        btn = Tk.Button(subFrame, text="Close", command=handler)
        btn.pack()
        
    #----------------------------------------------------------------------
    def onCloseOtherFrame(self, otherFrame):
        """"""
        otherFrame.destroy()
        self.show()
        
    #----------------------------------------------------------------------
    def show(self):
        """"""
        self.root.update()
        self.root.deiconify()
        
    
#----------------------------------------------------------------------
if __name__ == "__main__":
    root = Tk.Tk()
    root.geometry("800x600")
    app = MyApp(root)
    root.mainloop()

Now this is mostly the same as the first version of the code. It would be really nice to create the second frame's button in the second frame's class, but if we do that then it becomes hard to tell the original frame to deiconify. Still for completeness, let's see how that would look:

import Tkinter as Tk

########################################################################
class OtherFrame(Tk.Toplevel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, original):
        """Constructor"""
        self.original_frame = original
        Tk.Toplevel.__init__(self)
        self.geometry("400x300")
        self.title("otherFrame")
        
        btn = Tk.Button(self, text="Close", command=self.onClose)
        btn.pack()
        
    #----------------------------------------------------------------------
    def onClose(self):
        """"""
        self.destroy()
        self.original_frame.show()
    
########################################################################
class MyApp(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        self.root = parent
        self.root.title("Main frame")
        self.frame = Tk.Frame(parent)
        self.frame.pack()
                
        btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
        btn.pack()
        
    #----------------------------------------------------------------------
    def hide(self):
        """"""
        self.root.withdraw()
        
    #----------------------------------------------------------------------
    def openFrame(self):
        """"""
        self.hide()
        subFrame = OtherFrame(self)
        
    #----------------------------------------------------------------------
    def show(self):
        """"""
        self.root.update()
        self.root.deiconify()
    
#----------------------------------------------------------------------
if __name__ == "__main__":
    root = Tk.Tk()
    root.geometry("800x600")
    app = MyApp(root)
    root.mainloop()

Note that in this version, we have to pass the instance of the MyApp class to the other frame so we can call its show method. You can also see that we no longer need the lambda function since we don't need to pass the other frame instance to the handler any more. That makes things simpler. Still this is a fragile way of doing things. Why? Well if you decide to change the main frame's show method to showFrame or anything else, then you have to remember to change it in the other class too or it breaks. This can get tedious very quickly if you are passing instances around to multiple classes. Fortunately there's a simple solution and it's called pubsub!

Using pubsub to Communicate Between 2 Tkinter Windows

You'll need to go to pubsub's website and install the package as it's not included with Python. It IS included with wxPython, although I don't think you can really use that version outside of wxPython very easily. Anyway, once you have it, you can follow along with this code:

from pubsub import pub
import Tkinter as Tk

########################################################################
class OtherFrame(Tk.Toplevel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        Tk.Toplevel.__init__(self)
        self.geometry("400x300")
        self.title("otherFrame")
        
        # create the button
        btn = Tk.Button(self, text="Close", command=self.onClose)
        btn.pack()
        
    #----------------------------------------------------------------------
    def onClose(self):
        """
        closes the frame and sends a message to the main frame
        """
        self.destroy()
        pub.sendMessage("otherFrameClosed", arg1="data")
    
########################################################################
class MyApp(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        self.root = parent
        self.root.title("Main frame")
        self.frame = Tk.Frame(parent)
        self.frame.pack()
                
        btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
        btn.pack()
        
        pub.subscribe(self.listener, "otherFrameClosed")
        
    #----------------------------------------------------------------------
    def listener(self, arg1, arg2=None):
        """
        pubsub listener - opens main frame when otherFrame closes
        """
        self.show()
        
    #----------------------------------------------------------------------
    def hide(self):
        """
        hides main frame
        """
        self.root.withdraw()
        
    #----------------------------------------------------------------------
    def openFrame(self):
        """
        opens other frame and hides main frame
        """
        self.hide()
        subFrame = OtherFrame()
            
    #----------------------------------------------------------------------
    def show(self):
        """
        shows main frame
        """
        self.root.update()
        self.root.deiconify()
        
    
#----------------------------------------------------------------------
if __name__ == "__main__":
    root = Tk.Tk()
    root.geometry("800x600")
    app = MyApp(root)
    root.mainloop()

As might be expected, this code just integrate the pubsub stuff. We create a listener method in our main frame and we "register" it by calling

pub.subscribe(self.listener, "otherFrameClosed")

The "signature" is otherFrameClosed. So if we publish a message with that signature, then the main frame and any other class that has subscribed to that signature will call their respective methods. In the other frame, we add a pub.sendMessage call to the end of our close method where we publish to that aforementioned signature and we pass along a dummy argument. You don't have to do that, but I thought it would be better if you knew how to pass information between classes. You can pass pretty much any Python object / type that you want to.

Wrapping Up

Now you know a little bit about Tkinter and a few of its top-level methods. You can make your frames disappear and reappear on command! You have also gotten a taste of the power of pubsub. Go forth and code with this new knowledge!

Additional Resources

Download the Source Code

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary