Button it up (Buttons and event handlers)

Cut to the gift department. A large lady is standing by counter holding a large cylinder with a rose attachment.
Lady     Yes this looks the sort of thing. May I just try it?
Assistant     Certainly, madam.
The lady presses button and a sheet of flame shoots out across the hall.
Lady     Oh! Sorry! So sorry! (she is happy though) Yes that’s fine.

We’re having a look at another kind of widget today, as well as seeing some stuff about “event handling”. The widget we’re looking at is the Button – it’s what has “Ok” on it when you get a pop up dialog.   The process is much the same as what we did last time for labels:

>>> from Tkinter import *
>>> b1 = Button(None, text="This is a button.  Can you see it has some edges that the label didn't have?")
>>> b1.pack()

This should give you a button that looks like this:

Now let’s do something zany  and add another one:

>>> b2 = Button(None, text="Click me!")
>>> b2.pack()

Here’s the pic:

Can you see that the second button has appeared below the first button?  It is also in the middle, rather than on either of the sides.  Notice now that the pack() method has brackets (which indicates it’s a function?).   Have a look at the function’s help info to see more details about the parameters that the pack() method accepts:

>>> help(b2.pack)

Let’s try one of them (make sure you can see the window with the buttons in it when you hit enter for this line):

>>> b2.pack(side=LEFT)

Did you see the Button move?

You can try side=RIGHT on your own.

You can change the text of these buttons if you want:

>>> b2['text']='hi'

Each of these buttons has associated with it a dictionary – which you should have noticed because of the use of the square brackets [] with an index which is not a number.  What we did above was change the value of the key ‘text’ in b2’s dictionary.  To see all of the keys we in the dictionary use the standard .keys() method of dictionaries:

>>> b2.keys()
['activebackground', 'activeforeground', 'anchor', 'background', 'bd', 'bg', 'bitmap', 'borderwidth', 'command', 'compound', 'cursor', 'default', 'disabledforeground', 'fg', 'font', 'foreground', 'height', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'justify', 'overrelief', 'padx', 'pady', 'relief', 'repeatdelay', 'repeatinterval', 'state', 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength']

Changing the values of these keys will change various characteristics of the Button.  Let’s put the text back the way it was:

>>> b2['text']="Click me!"

Now, if you actually click one of these buttons you’ll see that it changes visually (more or less as you would expect a button to – it may look a little different from the buttons you’re used to because its styling comes from Tkinter not your operating system).  However, you’ll also notice that it doesn’t actually do anything.  This should not come as a surprise, given that we have just created a button but haven’t told Python what to do when the button is pressed.

So, let’s do that.  In the list of keys above, there is a key called ‘command’.  It’s currently empty:

>>> b2['command']
 ''

This is where we tell Python what to do when the button is pressed.   The ‘command’ key expects to receive the name of a function, so we need to create a function, then set the ‘command’ key as the name of that function.  I start by setting up two constants in order to avoid spelling errors:

>>> B2BASETEXT="Click me!"
>>> B2ALT_TEXT="Click me again!"
>>> def b2Click():
 ...    if b2['text']== B2BASETEXT:
 ...       b2['text']=B2ALT_TEXT
 ...       b2.pack(side=RIGHT)
 ...    else:
 ...       b2['text']=B2BASETEXT
 ...       b2.pack(side=LEFT)

Once that is done, we set the command:

>>> b2['command']=b2Click

Now clicking the button should bounce it from one side of the window to the other, changing the text each time*[see note below]:

However, we could have put anything at all in that function b2Click(), and that’s a really powerful idea.  The only problem is that Tkinter won’t let you pass arguments to the function.  However, not only is this not actually a problem for us at the moment (it’s not something we need to do), there’s also a way around it so it’s not really a problem at all.

So, let’s add quit button:

>>> b3 = Button(None,text="Click here to quit (without further warning)", command=quit)
>>> b3.pack()

If you click this new button, Python’s quit() function is run.  That will close the windows – and, indeed, exit Python as well.  This buggers up my console (new lines don’t work properly).  If you have this problem the reset command from the console should work (:> reset).  Normally if you have a “quit” command it may result in users losing data they haven’t saved, so you will usually give users a warning to confirm they really want to quit.  You would probably “wrap” this call to quit() inside a separate function which asks the user to confirm that they want to quit.

Clicking a Button (or, indeed any other part of the window) is called an “event”.  The function which does something when you click the button is called a “handler” (also “event handler” or “callback handler”).

Another thing to notice here is where the third button ended up (since we didn’t specify a side to pack it on).  All I’m going to say about this is that laying out a user interface with Tkinter can be a little difficult to understand.  We’ll talk more about layout later.

Notes:

* the proper way to do this is to start up Tkinter’s mainloop() method.  We will do this later (and if your clicks don’t seem to work let me know!) This method does the job of listening for events (clicking the mouse on a button is an “event”) generating messages and communicating them to callback handlers (in this case the function b2Click is a callback handler).

Tkinter tinkering (Graphical User Interfaces)

Man     Shut up! (It goes quiet next door) That’s better.
He walks to a side wall and hangs his club on a hook beneath big old-fashioned art-nouveau sign clearly labelled `The Burlington Wall-banger’. He goes across to bed and gets in. In the bed are a party of four Japanese businessmen in suits with lapel badges, two lady American tourists with rain hats and cameras, three other moustached English gentlemen in pyjamas, four Tour De France riders, three Swedish businessmen, and Winston Churchill.

So far we have been dealing with a command line interface for working with our Python programs.  While command lines are good for a lot of things, they’re usually pretty bad for interfacing with a general user of the program who doesn’t know how the various pieces work.  What we are going to look at now is a different way of presenting the program to a user (and probably a way that you are more familiar with) – GUIs.   GUI stands for “Graphical User Interface”.

In order to use a GUI, we need to use an external module to do all the grunt work behind making the interface components and presenting them to the end user.  There are a number of different modules which can be used.  We are going to use one called “Tkinter“.  We are going to use Tkinter because it should come as part of every Python installation, so there’s no additional downloading to do, nor any need to get the Responsible Adult involved – but leave a comment if you have problem with Tkinter.

To start using Tkinter is pretty easy.  You do this:

>>> from Tkinter import *

and… absolutely nothing should happen!

DANGER WILL ROBINSON!

A word of warning here: as a general rule using “from X import *”  is considered really bad form in Python because it means every object (* is a “wildcard” and means everything) is imported by its own name into your program, rather than part of the X namespace.   So, in our previous examples we’ve used import random, then accessed the randint() function via the random namespace: random.randint(). Had we used from random import *, then we could have said randint() and not random.randint().  However, this would be a bad thing to do.  If you have two packages X and Y, each of which has its own object called x, then, if you use “import *” the two objects X.x and Y.x will both end up in your program called ‘x’.  As you don’t have the X and Y namespaces to distinguish them, they ‘collide’.

So, why am I using “import *” for you?  Because Tkinter is an exception.  The Tkinter package has been designed so that as few objects as possible are imported into your program, and because their names are specifically GUI related, so there is less risk of a naming collision.  If you are feeling uneasy about all this do multiple imports of the specific Tkinter components you need.

Hello World in the GUI

Graphical User Interfaces use a specific, common set of graphical components to display information to a user and to get feedback from them.  These components are called “widgets”.  As you are using a graphical interface all of the time, you are actually already aware of widgets, but you just don’t realise that they’re there.  So, for example, whenever you are presented with an “Ok/Cancel” dialog on the computer, the text of the dialog (“About to delete all your files”) is presented in a “Label” widget, while the “Ok” and “Cancel” are “Button” widgets.

So let’s do a label:

>>> labelWidget = Label(None,text="Hello Python4Kids!")  # note: None has a capital

When you hit return you should see something like this (note: if you are running python-idle, this won’t work – at least not yet, run Python from a command line shell.  If you don’t know what python-idle is, you can ignore this note):

A Tkinter window

Python has told Tkinter it’s going to need a window to put a Label widget in.  Tkinter has asked the operating system to give it a window and the operating system has given it a default window.  Along the top of the window are a number of widgets which have been provided by your operating system (not Python).  Your window may look a little different if you’re running an alternative operating system.   On my system above, there are 6 widgets – from left to right, a menu widget (X), a button (to pin this window across multiple desktops), a label ‘tk’, and three more buttons (minimise, maximise and close).  You might have a different set of operating system widgets.

Where is the label we defined? Well, it exists:

>>> labelWidget
<Tkinter.Label instance at 0x7fcf9966e368>

However, it’s not visible yet.  It’s not visible yet because we haven’t defined a “geometry” for it.   We do this through a function which is part of the widget called .pack() (.pack() is called a ‘method’ of the widget, but we haven’t done that yet).

Debugging tip:  If you can’t see your widget, make sure you’ve .pack()ed it.

So let’s .pack() it:

>>> labelWidget.pack()

The .pack() method can take a heap of arguments, which define how the widget is to be placed within the window.  As we only have a single widget, we just use the default packing, and therefore put no arguments in.   You should see the tk window above change to look something like this:

One of the widgets in the title bar is obscured here (only four are visible), but grabbing the bottom right hand corner of the window will resize it allowing you to see the lost widgets:

To close the window click the close button in the top right corner.

That’s it for now, more on GUIs in the coming tutes.

PS:

Did you notice that we did a basic GUI interface in only three lines of code (including the import statement)???  Is that amazing?

PPS:

Hello to visitors from the Podnutz webcast.