Creating a GUI in Python using Tkinter – Part 2


This post is going to build off of Creating a GUI in Python using Tkinter which introduces the basic elements needed to create a GUI using TKinter. This post will take that introduction a bit further and build a more complex GUI application and use some more widgets.

The GUI that we are going to start creating is the GUI that we will eventually use for the RSS reader that I am creating.

Not much having to do with the RSS reader will happen in this post since we will just be creating the GUI shell, but it will help to explain some of the variable names.

GuiLayoutWe are going to start off with a simple shell that will hopefully resemble the following layout. We will have RSS sites on the left, then the selected sites RSS story titles on the right, and finally the selected stories text on the bottom.

To create this GUI we will be using the following widgets:


The first thing that we are going to do is start off with a blank GUIFramework based on what we did on part one:

#! /usr/bin/env python
from Tkinter import *
import tkMessageBox

class GUIFramework(Frame):
    """This is the GUI"""
    
    def __init__(self,master=None):
        """Initialize yourself"""
        
        """Initialise the base class"""
        Frame.__init__(self,master)
        
        """Set the Window Title"""
        self.master.title("RSS Reader - Eventually")
        """Display the main window
        with a little bit of padding"""
        self.grid(padx=15, pady=15,sticky=N+S+E+W)
        self.CreateWidgets()
       
    def CreateWidgets(self):
        """Create all the widgets that we need"""
                
if __name__ == "__main__":
    guiFrame = GUIFramework()
    guiFrame.mainloop()

Running this code will simply create a blank window with nothing in it, not very interesting. So what we need to do is add our first ListBox. We’ll also add a label to our listbox so that we know what it is:

"""Create the Text"""
self.lbRSSSiteText = Label(self, text="Select Site:")
self.lbRSSSiteText.grid(row=0, column=0, sticky=W)

"""Create the ListBox"""
self.lbSites = Listbox(self, selectmode=BROWSE
                        , relief=SUNKEN)
self.lbSites.grid(row=1, column=0)

GuiLayoutYou’ll notice that we are setting a few properties for the ListBox and Label widgets. The first is that we are making the Label widget sticky to the West (sticky=W) this simply aligns the order to the left of the ListBox. The other settings are applied to the ListBox and they are: selectmode=BROWSE and relief=SUNKEN .

The selectmode option controls how the user will be able to interact with the ListBox, there are for available options:

  • SINGLE – The user can select one item
  • BROWSE – Just like single, except the user can switch the selection using the mouse
  • MULTIPLE – Multiple items can be selected. The user can select an item by simply clicking on it.
  • EXTENDED – Just like multiple, except that in order to select multiple items, the used must use the SHIFT or CTRL button. If they simply select items using the mouse this setting will function like the BROWSE setting

The other setting that we apply is the relief=SUNKEN , this gives the listbox a sunken border, I just think that it looks nice.

Now in order to interact with the RSS Site ListBox we are going to let the user Add, Edit, Remove, and View the sites. We are going to use buttons in order to let the user perform these tasks. The code to create the buttons is pretty simple:

"""Create the Add, Remove, Edit, and View Buttons"""
self.btnAdd = Button(self, text="Add")
self.btnAdd.grid(column=0, row=2, stick=E, pady=5)
self.btnRemove = Button(self, text="Remove")
self.btnRemove.grid(column=1, row=2, stick=E, pady=5)
self.btnEdit = Button(self, text="Edit")
self.btnEdit.grid(column=2, row=2, stick=E, pady=5)
self.btnView = Button(self, text="View")
self.btnView.grid(column=3, row=2, stick=E, pady=5)

We also have to make a few changes to the way that the ListBox displays itself:

self.lbSites.grid(row=1, column=0, columnspan=4, sticky=N+W+S+E)

The columnspan=4 setting just makes the ListBox span 4 columns so that it is in line with the four buttons that will interact with it. The sticky=N+W+S+E makes the ListBox fill the entire grid container instead of being centered in the middle.

I also added a little bit code to populate the ListBox with 40 numbers so that we can play with it a bit more:

"""Just fill up the listbox with some numbers"""
for i in range(40):
    self.lbSites.insert(END, i)

GuiLayoutNow that’s starting to look a little better but why don’t we have any scrollbars on the ListBox? We can scroll it but there are no scroll-bars? The interesting thing about TKinter is that widgets like the ListBox are scrollable by default, but they do not display a scrollbar unless you create one yourself. So in order to have a fully functioning GUI application we are going to start having to add some ScrollBar widgets.

So right before we create the ListBox we need to create the scrollbars that we will use, this code is pretty self explanatory, the only interesting portion is that we do not make the scrollbars members of self. The reason for that is that for right now we won’t be interacting with them at all:

scrollbarV = Scrollbar(self, orient=VERTICAL)
scrollbarH = Scrollbar(self, orient=HORIZONTAL)

Then when we create the ListBox we need to tell it that scrollbarV and scrollbarH will be used to control the scrolling:

self.lbSites = Listbox(self, selectmode=BROWSE
                        , yscrollcommand=scrollbarV.set
                        , xscrollcommand=scrollbarH.set
                        , relief=SUNKEN)

The yscrollcommand=scrollbarV.set connects the yscrollcommand callback (fired when the ListBox is scrolled in the vertical direction) with the scrollbarV.set method. So when we scroll the ListBox, the scrollbar will follow.

The next thing we need to do is make the connection in the reverse, make it so that when the scrollbar is scrolled the ListBox follows. We also have to display the ScrollBars in the correct grid positions, namely the positions directly to the right and bottom of the ListBox. Doing this means that we will also have to move the Buttons down one more row to row 3. We also have to make the horizontal ScrollBar span 4 columns so that it is always aligned with the ListBox.

scrollbarV.grid(row=1, column=4, sticky=N+S)
scrollbarV.config(command=self.lbSites.yview)
scrollbarH.grid(row=2, column=0, columnspan=4, sticky=E+W)
scrollbarH.config(command=self.lbSites.xview)

GuiLayoutNow we have two functioning scrollbars, the horizontal one doesn’t do much since none of the numbers are long enough, but you can see that the vertical one is working properly.

So the next thing to do is add the next ListBox, I won’t go over this code since it’s basically a duplication of the above code. The only thing I will point out is that when you look at the final code you’ll see that I added a Frame widget between the two ListBox Widgets, this is to simple add some space between them. I also added a Read button under the second ListBox.

GuiLayout

Now we need to add the Text Widget below the buttons, the code should feel familiar to you now that you are used to adding scrollbars to items, the only difference are a few of the text settings:

"""Create the Text Widget"""
scrollbarV = Scrollbar(self, orient=VERTICAL)
scrollbarH = Scrollbar(self, orient=HORIZONTAL)
self.txtItem = Text(self, wrap=WORD
                     , yscrollcommand=scrollbarV.set
                     , xscrollcommand=scrollbarH.set
                     , relief=SUNKEN
                     , takefocus=0
                     , borderwidth=1
                     , state=NORMAL
                     , cursor="arrow")                    
self.txtItem.grid(row=5, column=0
                   , columnspan=7, sticky=N+W+S+E)
"""Show the scrollbars and attach them"""
scrollbarV.grid(row=5, column=7, sticky=N+S)
scrollbarV.config(command=self.txtItem.yview)
scrollbarH.grid(row=6, column=0, columnspan=7, sticky=E+W)
scrollbarH.config(command=self.txtItem.xview)

You’ll notice that we set the Text widgets cursor to be an arrow, we do this because the Text isn’t supposed to be editable, it’s read-only. If we don’t set the cursor the Text widget will use the default “i-beam” or text-editor cursor.

The takefocus=0 settings tells the Text widget not to take the focus, or allow the user to tab to the Text widget.

So that’s about it for this this GUI, it’s pretty simple when you look at each part one by one, but creating a large GUI does get to be a bit complicated when having to pay attention to the grid positions and scrollbars. Here is the full code of the GUI, you’ll notice that there are a few additions to the code, like the resizing and some functions, but I’ll let you try to figure out how those work. If you have any questions or comments feel free to post them.

Download the code as a text file here.

#! /usr/bin/env python
from Tkinter import *
import tkMessageBox

class GUIFramework(Frame):
    """This is the GUI"""
    
    def __init__(self,master=None):
        """Initialize yourself"""
        
        """Initialise the base class"""
        Frame.__init__(self,master)
        
        """Set the Window Title"""
        self.master.title("RSS Reader - Eventually")
        top=self.winfo_toplevel()
        """Display the main window"
        with a little bit of padding"""
        self.grid(padx=15, pady=15,sticky=N+S+E+W)
        self.InitResizing()
        self.CreateWidgets()
        
    def InitResizing(self):
        """Initialize the Resizing of the Window"""
        top=self.winfo_toplevel()
        top.rowconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)
        top.columnconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(6, weight=1)
       
    def CreateWidgets(self):
        """Create all the widgests that we need"""
                       
        """Create the Text"""
        self.lbRSSSiteText = Label(self, text="Select Site:")
        self.lbRSSSiteText.grid(row=0, column=0, sticky=W)
        self.lbRSSItemText = Label(self, text="Select RSS Item:")
        self.lbRSSItemText.grid(row=0, column=6, sticky=W)
        
        """Create the First ListBox"""
        scrollbarV = Scrollbar(self, orient=VERTICAL)
        scrollbarH = Scrollbar(self, orient=HORIZONTAL)
        
        self.lbSites = Listbox(self, selectmode=BROWSE
                                , yscrollcommand=scrollbarV.set
                                , xscrollcommand=scrollbarH.set
                                , relief=SUNKEN)
        self.lbSites.grid(row=1, column=0, columnspan=4, sticky=N+W+S+E)
        """Show the scrollbars and attatch them"""
        scrollbarV.grid(row=1, column=4, sticky=N+S)
        scrollbarV.config(command=self.lbSites.yview)
        scrollbarH.grid(row=2, column=0, columnspan=4, sticky=E+W)
        scrollbarH.config(command=self.lbSites.xview)
        """Set the command"""
        self.lbSites.bind("<double -Button-1>", self.DblCLickSites)
        
        """Create the Add, Remove, Edit, and View Buttons"""
        self.btnAdd = Button(self, text="Add", command=self.Display)
        self.btnAdd.grid(column=0, row=3, stick=E, pady=5)
        self.btnRemove = Button(self, text="Remove", command=self.Display)
        self.btnRemove.grid(column=1, row=3, stick=E, pady=5)
        self.btnEdit = Button(self, text="Edit", command=self.Display)
        self.btnEdit.grid(column=2, row=3, stick=E, pady=5)
        self.btnView = Button(self, text="View", command=self.Display)
        self.btnView.grid(column=3, row=3, stick=E, pady=5)
        
        """Create a frame for space between the two items"""
        spaceframe = Frame(self, width=15)
        spaceframe.grid(row=3,column=5)
        
        """Create the Second ListBox"""
        scrollbarV = Scrollbar(self, orient=VERTICAL)
        scrollbarH = Scrollbar(self, orient=HORIZONTAL)
        
        self.lbRSSItems = Listbox(self, selectmode=BROWSE
                                , yscrollcommand=scrollbarV.set
                                , xscrollcommand=scrollbarH.set
                                , relief=SUNKEN)
        self.lbRSSItems.grid(row=1, column=6, sticky=N+W+S+E)
        """Show the scrollbars and attatch them"""
        scrollbarV.grid(row=1, column=7, sticky=N+S)
        scrollbarV.config(command=self.lbRSSItems.yview)
        scrollbarH.grid(row=2, column=6, sticky=E+W)
        scrollbarH.config(command=self.lbRSSItems.xview)
        
        """Create the Frame for space between the ListBoxes and
        the Text"""
        spaceframe = Frame(self, height=5)
        spaceframe.grid(row=4,column=1)
        
        """Create the Text Widget"""
        scrollbarV = Scrollbar(self, orient=VERTICAL)
        scrollbarH = Scrollbar(self, orient=HORIZONTAL)
        self.txtItem = Text(self, wrap=WORD
                             , yscrollcommand=scrollbarV.set
                             , xscrollcommand=scrollbarH.set
                             , relief=SUNKEN
                             , takefocus=0
                             , borderwidth=1
                             , state=NORMAL
                             , cursor="arrow")
        self.txtItem.grid(row=5, column=0
                           , columnspan=7, sticky=N+W+S+E)
        """Show the scrollbars and attatch them"""
        scrollbarV.grid(row=5, column=7, sticky=N+S)
        scrollbarV.config(command=self.txtItem.yview)
        scrollbarH.grid(row=6, column=0, columnspan=7, sticky=E+W)
        scrollbarH.config(command=self.txtItem.xview)
        
        self.txtItem.tag_config("a", foreground="blue", underline=1)
        self.txtItem.tag_bind("a", "<enter>", self.show_hand_cursor)
        self.txtItem.tag_bind("a", "<leave>", self.show_arrow_cursor)
        self.txtItem.tag_bind("a", '<button -1>', self.ClickText)
        self.txtItem.config(cursor="arrow")
        
        self.txtItem.insert(INSERT, "click here!", "a")
        self.txtItem.config(state=DISABLED)
               
        """Create the Set TextButton"""
        self.btnSetText = Button(self, text="SetText", command=self.SetStoryText)
        self.btnSetText.grid(column=6, row=3, stick=E, pady=5)
        
        """Just fill up the listbox with some numbers"""
        for i in range(40):
            self.lbSites.insert(END, i)
            self.lbRSSItems.insert(END, i)
            
    def show_hand_cursor(self, event):
        event.widget.configure(cursor="hand2")
    def show_arrow_cursor(self, event):
        event.widget.configure(cursor="arrow") 
            
    def DblCLickSites(self, event):
        """Called when lbSites is double-clicked on"
        event containts the x and y position of the click, but since
        we only care about the current selection we can ignore it"""
        self.Display()
        
    def ClickText(self, event):
        """Called when hypelink text is clicked on in the Text Widget
        event contians event information but since
        we only care about the current selection we can ignore it"""
        self.SetStoryText()
        
    def Display(self):
        """Called when btnDisplay is clicked, displays the contents of self.enText"""
        lstCurrSel = self.lbSites.curselection();
        strSelection = "";
        
        if (len(lstCurrSel)==0):
            strSelection = "Nothing yet!"
        else:
            strSelection = self.lbSites.get(lstCurrSel)
        tkMessageBox.showinfo("Text", "You selected: %s" % strSelection)
    
    def SetStoryText(self):
        """Set the Story text, called form the btnSetText"""
        
        """Get the Current selection"""
        lstCurrSel = self.lbRSSItems.curselection();
        strSelection = "";
        
        if (len(lstCurrSel)==0):
            strSelection = "Nothing yet!"
        else:
            strSelection = self.lbRSSItems.get(lstCurrSel)
        """Set the Text Widgets text"""
        self.txtItem.config(state=NORMAL)
        self.txtItem.delete(1.0,END)
        self.txtItem.insert(INSERT, strSelection, "a")
        self.txtItem.config(state=DISABLED)
                
if __name__ == "__main__":
    guiFrame = GUIFramework()
    guiFrame.mainloop()

Links that helped me get through this post:

If you like this post remember to digg it.

selsine

del.icio.us del.icio.us

6 Responses to “Creating a GUI in Python using Tkinter – Part 2”

  1. mario
    Says:

    Hi,

    Thanks for the good tutorial. Exactly what I was looking for.
    The only thing I cn’t figure out is the reason for the resizing function.
    Commented it out to see the the difference, but ca’t see any different behaviour.

    Regards,
    Mario

  2. selsine

    selsine
    Says:

    Hi Mario,

    The weight property is defined as follows by the pythonware site:

    A relative weight used to distribute additional space between columns (rows). A column with the weight 2 will grow twice as fast as a column with weight 1. The default is 0, which means that the column will not grow at all.

    If you comment out all of the InitResizing() function you should see that while the entire application still resizes, the items in the grid retain their original size.

    Whereas with it uncommented the Site ListBox and the Text widget will resize when you size the application.

    I hope this helps! If you have anymore questions let me know.

  3. bookymonster
    Says:

    Hey, nice tutorial. Gave me a quick intro into tkinter, which was what I was looking for.

    I think I have found a couple of errors in the code you have posted on this page. It differs from your txt version in a few places, which results in it not running(on my version of python anyway).

    On line 57, and 113-115, there seems to be errors with the capilisation and spacing of the event strings. They are fine in the txt file though.

    I also had a problem with the hand cursor, it seems I needed to change it to ‘hand2′ in order for it to work.

  4. selsine

    selsine
    Says:

    Hi bookymonster,

    Thanks for the information I was trying to figure out (after looking at the code) why it would be different in the two locations, since I generally paste my code into the post directly from my editor.

    Then after attempting to fix I remembered! It’s a strange wordpress bug that doesn’t like the in the events and things that they are actually HTML tags! For whatever reason the lower case and the spaces seem to help.

    Thanks for reminding me about this, I’ll have to come up with some sort of a solution.

  5. Alan
    Says:

    Emmett

    Better a lean peace than a fat victory…

  6. Examples | Pearltrees
    Says:

    [...] BROWSE – Just like single, except the user can switch the selection using the mouse learning python » Blog Archive » Creating a GUI in Python using Tkinter – Part 2 [...]

Leave a Reply

 

Popular Posts