GUI Programming in Python For Beginners: Create a Timer in 30 Minutes

By: Akkana Peck
Thursday, March 26, 2009 11:54:38 AM EST
URL: http://www.linuxplanet.com/linuxplanet/tutorials/6708/1/

Saving Absent-Minded Geeks From Soggy Gardens and Burned Food

Python programming is all the rage, because the language has such simple, clean syntax, is easy to learn and has lots of libraries available. But best of all, you're not limited to writing command-line applications. Python has at least four ways to make graphical apps, but today I'll concentrate on one: TkInter.

TkInter isn't necessarily the most powerful of the Python windowing packages, but it's well supported on all operating systems. You can give your application to your Windows- and Mac-using friends and they'll be able to run it, as long as they have Python installed. On Linux, though, you may have to install it: on Ubuntu or Debian the package is named python-tk, while in Fedora it's tkinter.

For a project, start with a simple one. How about an "eggtimer"? I'm always forgetting that I have cornbread baking in the oven, or that I turned on the sprinklers in the garden. I need something to remind me.

Let's start with a very simple Tkinter program. Paste the following into your editor of choice (vim, emacs, kate, leafpad or whatever) and write it to a file named, say, eggtimer:

#!/usr/bin/env python

from Tkinter import *

root = Tk()

button = Button(root, text="Hello", command=quit)
button.pack()

root.mainloop()

You can run your script by typing:

python eggtimer
or, if you make the file executable (see the shell scripting article), you can run it just by typing eggtimer or double-clicking it from your file manager. (Figure 1, Listing 1.)

Adding controls

Okay, a single button isn't so useful. For a timer, you need to be able to set when it will alert you. And for that, a Scale is a good choice. It gives you a slider you can use to choose how many minutes you want your timer to wait. Add these two lines to your script right before the line where you create your button:

scale = Scale(root, from_=1, to=45, orient=HORIZONTAL, length=300)
scale.pack()

from_ and to set the scale's low and high bounds. Notice the underscore on the end of from_: from without an underscore is a keyword in Python so Tkinter isn't allowed to use it. orient=HORIZONTAL makes the slider horizontal, and length=300 sets it to 300 pixels wide. Figure 2 (and Listing 2) shows the window with the Scale widget added.

Of course, Tkinter has other controls (called widgets) besides Scale.

You can make an Entry if you want to read in a text string (or type in the number of minutes), a Checkbutton or Radiobutton to set yes/no options or other binary options, or a Text widget to let the user enter a lot of text (more than one line).

You can also add controls to make things look nicer. For instance, you can add a Label just before you create the Scale, to specify that the slider means a time in minutes:

minutes = Label(root, text="Minutes:")
minutes.pack(side=LEFT)
Figure 3 shows what the window looks like now, and Listing 3 has the code.

======================================================
# Listing 1
#!/usr/bin/env python

from Tkinter import *

root = Tk()

button = Button(root, text="Start timing", command=quit)
button.pack(side="left")

root.mainloop()
======================================================

======================================================
# Listing 2
#!/usr/bin/env python

from Tkinter import *

root = Tk()

scale = Scale(root, from_=1, to=45, orient=HORIZONTAL)
scale.pack()

button = Button(root, text="Start timing", command=quit)
button.pack()

root.mainloop()
======================================================

======================================================
# Listing 3
#!/usr/bin/env python

from Tkinter import *

root = Tk()

minutes = Label(root, text="Minutes:")
minutes.pack(side=LEFT)

scale = Scale(root, from_=1, to=45, orient=HORIZONTAL, length=300)
scale.pack()

button = Button(root, text="Start timing", command=quit)
button.pack()

root.mainloop()
======================================================

Packing Them In

Packing them in

The pack commands for each of these widget -- button.pack(), label.pack(side=LEFT), and so forth -- tell Tkinter to add the widget in the window that contains it.

But pack doesn't really give you much control over where widgets go, so most Tkinter programmers use another method for placing widgets: grid.

Think of your window as a grid, with some number of rows and columns. Each time you add a new widget, you can specify exactly where in the grid it goes. For example, try removing all the pack lines from your script, and replace them with these grid calls:

minutes.grid(row=0, column=0)

scale.grid(row=0, column=1)

button.grid(row=1, column=1, pady=5, sticky=E)

Now the window looks like Figure 4 (Listing 4).

Doing something useful: setting the timer

Now you have a nice window ... but it still doesn't do anything yet. How do you make it start a timer when you click Start?

The answer is that command=quit when you created the Button. quit is the name of the function that's called when the user clicks the button. You can replace it with your own function. That function will need to get the number of minutes the user chose from the Scale widget. You do that with scale.get(). So your function might look like this:

def start_timer() :
    root.after(scale.get() * 60000, show_alert)
scale.get() gets the value the user set with the slider. root.after() sets a timeout -- but it's in milliseconds, not minutes, so you have to multiply the scale's value by 60,000.

Here's a tip, though: when you're debugging, you might want to use 1000 rather than 60000, so the slider represents seconds, not minutes and you don't have to wait so long to see if your code works.

Now all that's left is the show_alert() function that grabs your attention when the timer fires. Here's one that beeps and then shows an "info" dialog:

def show_alert() :
    root.bell()
    tkMessageBox.showinfo("Ready!", "DING DING DING!")
    quit()

bell() makes a noise, while tkMessageBox.showinfo() will bring up a little dialog (Figure 5, Listing 5).

======================================================
# Listing 4
#!/usr/bin/env python

from Tkinter import *

root = Tk()

minutes = Label(root, text="Minutes:")
minutes.grid(row=0, column=0)

scale = Scale(root, from_=1, to=45, orient=HORIZONTAL, length=300)
scale.grid(row=0, column=1)

button = Button(root, text="Start timing", command=quit)
button.grid(row=1, column=1, pady=5, sticky=E)

root.mainloop()
======================================================

====================================================== # Listing 5 #!/usr/bin/env python from Tkinter import * import tkMessageBox def show_alert() : root.bell() tkMessageBox.showinfo("Ready!", "DING DING DING!") quit() def start_timer() : # Next line should be * 60000, but use 1000 to speed debugging: root.after(scale.get() * 1000, show_alert) root = Tk() minutes = Label(root, text="Minutes:") minutes.grid(row=0, column=0) scale = Scale(root, from_=1, to=45, orient=HORIZONTAL, length=300) scale.grid(row=0, column=1) button = Button(root, text="Start timing", command=start_timer) button.grid(row=1, column=1, pady=5, sticky=E) root.mainloop() ======================================================

Bringing Up New Windows

That works fine. But you know what? That little dialog is awfully easy to miss, especially if you have a big screen with lots of other windows on it. It would be better to bring up a huge window that covers the whole screen.

Unfortunately, you can't do much about what tkMessageBox.showinfo() does. But you can make your own dialog quite easily:

def messageWindow() :
    win = Toplevel()

    b = Button(win, text='DING DING DING',
               bg="blue", fg="yellow",
               activebackground="red", activeforeground="white",
               command=quit)
    b.pack(ipadx=root.winfo_screenwidth()/2,
           ipady=root.winfo_screenheight()/2)

    root.mainloop()

What's new here?

First, the button colors: you can set any widget's colors to anything you like. For a button, the active colors are the ones it shows when the mouse is over the button, while the base colors apply when the mouse is somewhere else. (Move your mouse into the window's title bar to see the color change.)

padx and pady specify how much padding the button should leave around the text in the middle. If the X padding is set to half the screen width on either side, then the button will be at least fill the screen, and likewise with height. Actually this requests a size slightly bigger than the screen, because the button's text and the window's titlebar take up some space too.

That's it! You can get more information on Tkinter programming from the links below.

Links:

Akkana Peck is a freelance programmer and writer and a Python fan; you can find some of her other Python hacks at http://shallowsky.com/software/ She's also the author of Beginning GIMP: From Novice to Professional.

======================================================
# Listing 6
#!/usr/bin/env python

from Tkinter import *

def messageWindow() :
    win = Toplevel()

    b = Button(win, text='DING DING DING',
               bg="blue", fg="yellow",
               activebackground="red", activeforeground="white",
               padx=root.winfo_screenwidth()/2,
               pady=root.winfo_screenheight()/2,
               command=quit)
    b.pack()

    root.mainloop()

def show_alert() :
    root.bell()
    messageWindow()
    quit()

def start_timer() :
    root.after(scale.get() * 60000, show_alert)

root = Tk()

minutes = Label(root, text="Minutes:")
minutes.grid(row=0, column=0)

scale = Scale(root, from_=1, to=45, orient=HORIZONTAL, length=300)
scale.grid(row=0, column=1)

button = Button(root, text="Start timer", command=start_timer)
button.grid(row=1, column=1, pady=5, sticky=E)

root.mainloop()
======================================================

Copyright Jupitermedia Corp. All Rights Reserved.