In the last few tutorials we have been looking at various parts of what make up a graphical user interface. We’ve created labels and buttons and have attached images to a label (and a button if you did the homework). We have also associated an action with pressing a button. In this tutorial we’re going to put these together to make a simple image viewer application. As with the previous tutorials, this will only work with images which are saved as a “gif” format. Also, a word of warning – you need to do pretty much all of the code before any of it will work.

Here are some images for you to use. As with the earlier tutorial, you can either use ‘save as’ to download a copy, or you can use the Python code in the note below to download them (if you use Python you should be in your python4kids directory when you do the download otherwise the files will get saved somewhere else). A gold star if you use Python for the download.

Planning

When writing a program it is a good idea to plan out what tasks the program is intended to do first. Then, when you write the program the plan can guide you from one component to another. Also, you can write a plan as a comment at the start of your program and it can serve as a docstring – at least until you’ve gone back and written a proper docstring for your code.

Exercise: Use your text editor to make a list of the things that would need to happen in order for a slide show program to run. Save the list to a file called ‘p4kSlideshow.py’ in your python4kids directory. Make your edits to this file. Enclose the list in triple quotes ->

'''
item 1 (and explanation)
item 2 etc
...
'''

Solution:

The things that I think a slideshow should do are:

make a list of all the images in the directory

get the first image and display it

on user input, display the next image (in this case, user input will be a button click)

when all images have been displayed exit.

My list of things will likely be different from your list of things. For example, you might want the images to be displayed in a random order, or when you get to the end of the images you might want to start again from the beginning, or you might want the user to choose the image to show from a list. There are many ways of approaching it. I’m showing you something simple, but if you’ve got other things on your list, extend the work we do here and implement them in your own program.

Here’s my note:

'''
p4kSlideshow
When run in a directory this program:
* searches the directory for files with a '.gif' extension
* displays the first image it finds
* when a user clicks, it displays the next image
* if user clicks last image, the program exits.
'''

The benefit of doing this is it tells us what to do (and, hopefully, also in what order – in our case we are dealing with a handler so it’s a little mixed up). It’s our map to completing the program. Let’s do the first one.

Coding

We are going to use a function from the os module called os.listdir (we have used this before) and another called os.path.splitext. Splitext splits a file into its base file name and its extension so thisfile.txt would become [‘thisfile’,’.txt’]. We do this to identify which files are gif files. We could have also used the slicing operator [-3:] (reminder – see here):

Note that there’s a ‘[-1]’ after splitext(). This is because splitext returns a list, with the last entry in the list ([-1] means last in the list) being the extension. This code stores a list of images in the list named listOfImages.

Now we do the GUI bit. We need to add the Tkinter import at the start of the file:

import os
from Tkinter import *

We also need to keep track of how many images there are, and which image it is that we are currently up to:

indexes = {}
'''
indexes is a dictionary containing some values
that we need to keep track of.
I am using a dictionary rather than two variables (say totalImages and currentImage)
so that I don't have to explain global and local variables to you yet (maybe next tute?)
'''
indexes['totalImages'] = len(listOfImages)
if indexes['totalImages'] <1:
sys.exit()

After that we set up a button:

# set up the GUI
displayButton = Button(None,text= "If you can see this text, there's something wrong")
# we shouldn't ever see the text
# we could leave the text= parameter out
displayButton.pack()

Now we create a function to handle clicks. Later we will need to point the button’s ‘command’ key at this function.

def nextImage():
indexes['currentImage'] += 1
# += 1 says "add one to the current value of this variable"
if indexes['currentImage'] == indexes['totalImages']:
# if past last image, then exit
# as the list starts at index 0 and runs to the number of entries -1
# it will reach the last one on total entries -1, if it's equal to totalImages
# then when we added one, we went past the last one, so exit instead of displaying.
sys.exit()
else:
imageIndex = indexes['currentImage']
imageFile = listOfImages[imageIndex]
print "imageFile = %s"%imageFile
# this is just for debugging, you can remove the print statement if you want
imageObject = PhotoImage(file=imageFile)
displayButton['image']=imageObject
displayButton.image=imageObject # NOTE: WON'T WORK WITHOUT THIS BIT OF SORCERY!
displayButton.pack()

Note the note! This assignment really is sorcery. If you remove it your program (probably) won’t work! Moreover, if I didn’t tell you there’s no way you’d work it out for yourself. What this line does is it forces Python to keep a reference to the imageObject. Because of the way Tkinter does the assignment displayButton[‘image’], Python does not keep a reference to the object imageObject. Because it has not kept a reference, Python thinks the object is not being used (even though it is). Python therefore throws the imageObject out in order to save memory. This process is called “garbage collection” and is happening all the time in Python. When we do the assignment displayButton.image=imageObject we force Python to remember the object and therefore not to throw it out with the rest of the garbage.

Exercise: once you’ve finished the tute comment out the line displayButton.image=imageObject run it again and see what happens.

We are just about ready now. For the function nextImage() to work the entry indexes[‘currentImage’] needs to be initialised. We also need to display the first image and hook up the function to the button click. These are all pretty easy:

# initialise the index we will use to display the images.
indexes['currentImage'] = -1

We use -1 here so that on the first call to nextImage 1 will be added to it, giving 0 (-1+1=0) and 0 is the first entry of the list of file names we just created.

Display the first image:

nextImage()

Hook up the nextImage function to be run when the button is clicked:

displayButton['command']=nextImage

Now, we need to start the program waiting for mouse clicks. This has not been something we’ve done in the past few tutes because we were using an interactive Python session. However, now we’re going to need it. We do this by calling the ‘mainloop()’ method:

displayButton.mainloop()

Ta da! We’re done (exercise: check that we’ve implemented everything in our original list). Run this program from your command line like this:

> python p4kSlideshow.py

and you should have a simple slideshow. You must make sure you’ve got the gif files saved in the same directory though!

When the program hits the mainloop() function it sort of slips into a Tkinter Netherworld, in which you don’t really see what it’s doing. It is only when Tkinter notices a mouse click on the button that control comes back to your program (executing the handler function nextImage()). Even then, when nextImage() finishes running, program execution passes back into Tkinter and you can’t see what’s happening there.

There are a lot of limitations with this program. Most obviously it only works with .gif files! Argh! However, it has other problems. First, you can’t go backwards – it’s one way from start to finish, and then the program ends. You might want to start the slideshow from the start again. Second, it is quite fragile. If one of the files is deleted, renamed or moved half way through your slide show (ie after running os.listdir() but before displaying the file) then you’re going to have a problem. The program doesn’t check that the filename remains valid. The program is unhelpful if there are no gif files in the directory (simply exiting). There are many ways the program could be improved. If you are game, have a go!

Exercise: change the program so that, once it displays all of the images it begins again with the first one.

Hint: at the end of the function nextImage() test to see if you’re at the end of the images and, if so, assign a certain value to indexes[‘currentImage’].

This was quite a complex program because we had to knit three components together. Moreover, handlers are pretty difficult conceptually. If you can follow what’s happening and get it running, give yourself a gold star.

Notes:

Downloading the images:

>>> imageLinks = ["https://python4kids.files.wordpress.com/2011/08/p4kbuttonimage1.gif", "https://python4kids.files.wordpress.com/2011/08/p4kbuttonimage2.gif", "https://python4kids.files.wordpress.com/2011/08/p4kbuttonimage3.gif", "https://python4kids.files.wordpress.com/2011/08/p4kbuttonimage4.gif"]
>>> import urllib
>>> for i in imageLinks:
... filename = i[-19:] # [-19:] was trial and error and based on a set length of the filename
... urllib.urlretrieve(i,filename)

The complete code:

# -*- coding: utf-8 -*-
'''
p4kSlideshow
When run in a directory this program:
* searches the directory for files with a '.gif' extension
* displays the first image it finds
* when a user clicks, it displays the next image
* if user clicks last image, the program exits.
'''
import os
import sys
from Tkinter import *
IMAGE_EXTENSIONS=['.gif']
listOfFiles = os.listdir('.')
# list dir doesn't necessarily give alphabetical order so we sort
listOfFiles.sort()
listOfImages = []
for f in listOfFiles:
if os.path.splitext(f)[-1] in IMAGE_EXTENSIONS:
listOfImages.append(f)
# some print statements I used for debugging the code:
#print listOfFiles
#print listOfImages
# if there are no image files, then stop!
indexes = {}
'''
indexes is a dictionary containing some values
that we need to keep track of.
I am using a dictionary rather than two variables (say totalImages and currentImage)
so that I don't have to explain global and local variables to you yet (maybe next tute?)
'''
indexes['totalImages'] = len(listOfImages)
if indexes['totalImages'] <1:
sys.exit()
# set up the GUI
displayButton = Button(None,text= "If you can see this text, there's something wrong")
# we shouldn't ever see the text
# we could leave the text= parameter out
displayButton.pack()
# create a function to handle clicks
def nextImage():
indexes['currentImage'] += 1
# += 1 says add one to this variable
if indexes['currentImage'] == indexes['totalImages']:
# if past last image, then exit
# as the list starts at index 0 and runs to the number of entries -1
# it will reach the last one on total entries -1, if it's equal to totalImages
# then when we added one, we went past the last one, so exit instead of displaying.
sys.exit()
else:
imageIndex = indexes['currentImage']
imageFile = listOfImages[imageIndex]
print "imageFile = %s"%imageFile
# this is just for debugging, you can remove the print statement if you want
imageObject = PhotoImage(file=imageFile)
displayButton['image']=imageObject
displayButton.image=imageObject # NOTE: WON'T WORK WITHOUT THIS BIT OF SORCERY!
displayButton.pack()
# initialise the index we will use to display the images.
indexes['currentImage'] = -1
'''
-1 is a bit of a magic number
We use -1 because the nextImage function below adds one each time it is called
so the first time it is called it will display the image at location 0 (-1+1 =0)
the next time it is called it will display the image at location 1 and so on
'''
# display the first image
nextImage()
# set the button to run the nextImage function when clicked
displayButton['command']=nextImage
# Now start the program waiting for mouse clicks
# when you click the button it will run the nextImage function
displayButton.mainloop()

Or, perhaps you’re one of those students who desires a challenge, or even a good addition to your résumé for Grad school.
As with everything else, it is only the mindset of the entrepreneur that dictates the success and failure of a
company. Always choose a new account, and look for a specialized bank whenever possible.

If you’re educated (or seeking an education) you
will probably find a ton of opportunity in a small town. With good habit and savings, you can be assured of having enough to start your own business.
For clients, you can target neighborhoods,
communities or even companies.

As with everything else, it is only the mindset of the entrepreneur that dictates
the success and failure of a company. A customer is injured while using a product you
sold and files a claim for indemnity.