Requirements can be complex, but programs don’t have to be.
By using object orientation, you can give your programs great power without writing lots of extra code. Keep reading, and you’ll create custom widgets that do exactly what you want and give you the power to take your programming skills to the next level.
The World Music Mixing Expo is only a few weeks away. Your DJ friend is thrilled with the work you’ve done, but now he has a few extra requirements.
Just playing one track isn’t enough. To be useful, the program needs to be able to mix several tracks together at the same time. Each track needs its own graphical interface elements (or widgets).
To play and control a single track, you created two widgets on the GUI: a check button to start and stop the track and a scale to change the volume. Then you added event-handling code to hook those widgets up to the sound system.
To play multiple tracks together, you just need more of the same. Each track will need its own set of widgets and its own event handlers to connect the widgets and the track together. Then, each set of widgets needs to be added to the same window in the GUI.
Let’s generate the widgets and event handlers.
You could just copy and paste the code several times for each track. But duplicated code is a bad idea, because it can lead to all sorts of problems whenever you need to change how the code for each track works. Instead of duplicating code, you should always try to reuse it.
One way to reuse code is by creating a function that will generate the widgets and the event handlers as needed.
If you have a function that creates the widgets and event handlers for a single track, you could call it for each of the tracks, which would then quickly let you build the entire interface.
But what code would you need in such a function?
With all the code gathered together in a new function, the code for the create_gui()
function looks like this:
A function-in-a-function is called a local function.
Do you notice anything strange? The new function actually has two other functions inside it. Python (and several languages) lets you create local functions. A local function is just a function inside a function.
Let’s see why they’re important for the DJ’s program.
When you’re wiring up the widgets in the interface, you need event handlers to respond to changes in the state of the widgets. If someone clicks the checkbox to play the track, the track_toggle()
event handler gets called and switches the track on or off.
Each widget needs its own event handler.
But if you are creating several checkboxes, you are going to need a separate event handler for each of them.
That’s why you have local functions inside create_gui()
. As well as creating new widgets for the interface, it also uses the local functions to create new event handlers.
Now, let’s update the program to use this new function.
Frank: A function inside a function? Surely that’s not legal?
Jim: Well... it depends on the programming language.
Joe: Don’t tell me this is something that only works with Python?!?
Jim: No... there are lots of programming languages that allow you to put a function inside another function.
Frank: Name one (other than Python)!
Jim: Pascal.
Frank: Pasc... what?!?
Jim: Look, it really doesn’t matter which programming language supports this feature. What is important is that we can do it.
Frank: And by “we” you mean “Python.”
Jim: OK, yes. Python can do it.
Frank: That’s all I was trying to say...
Joe: So, this is cool how?
Jim: Because it lets you localize functionality and handle complexity.
Joe: What?!?
Jim: Look: if a function gets big and complex, it can help to break the function down into a collection of smaller functions, just like we do when programs get big. We can keep the functionality local to the code that needs it inside the function. That way, we keep things as simple as we can
Joe: Even if the code itself is complex?
Frank: Like the GUI-building code we are working on now?
Jim: Yes.
Frank: OK. A function of functions it is, then. I’m all for keeping it simple, stupid. ;-)
Joe: Yes, I like KISS, too.
Jim: Yeah... their last album was really something special, wasn’t it?
Frank & Joe: Eh?!?
The program does exactly what the DJ wants, right? Well, not quite.
The problem is that even though the program technically works, it has a confusing interface. All of the checkboxes are added on the left of the interface, and all of the volume controls are on the right.
There’s nothing in the interface that indicates which volume scale goes with which track.
The checkboxes are labeled with the filename of the track, but the volume sliders aren’t. Even though each volume slider is linked to a single track, there is nothing in the interface that tells the user which track that is.
So... what to do? You could just add labels to each volume slider. That would probably fix it, but adding more widgets, like labels, can make an interface more complex. And you want your interfaces (and your GUI code) to be simple and clean.
Fortunately, there is a way of rearranging the widgets in the interface to make it a lot clearer.
If the interface were laid out with the checkbox for a track grouped alongside the volume slider for the same track, it would look better.
Each track could then have a row of widgets associated with it. As long as you know which widgets belong to which track, you can load a lot more tracks at once without the checkboxes and sliders getting separated (and without your users getting confused).
The program currently uses a function to add the checkboxes and sliders to the interface one widget at a time. If you call the function several times, the computer creates two more widgets with each call. But the widgets are not grouped. So, how do you group widgets together in a GUI interface?
What if you don’t just hand the computer a set of instructions? What if you give it a brand new widget instead?
If you create a new kind of widget that groups a checkbox with a slider, you can add your new widget to the interface and then guarantee that the checkbox and slider stay together:
Your new widget becomes a new building block for your GUI interface.
So, how are new widgets created? And how do they work?
Most GUI libraries (including tkinter) let you create custom widgets from a set of other components, and tkinter includes a special kind of widget called a frame. A frame works just like a picture frame, in that it surrounds other things. It’s rectangular and it can contain other widgets:
In tkinter, a frame is created using Frame()
. If you can work out a way to create a new type of frame (called, say, SoundPanel
) that contains the checkbox and the slider, then you could use code something like this in your program:
Even though you haven’t created the SoundPanel
code yet, let’s replace the calls to create_gui()
in hfmix.pyw
with these lines of code now. Just don’t try to run it yet.
This look like a great solution. However, you still have a big problem.
This code uses an entirely new type of object, a whole new kind of widget that has never existed before. How do you tell the computer to create something like that, which is effectively a custom GUI object?
How do you convince the computer to create a new widget each time you call SoundPanel()?
Object oriented programming (OOP) languages (like Python) let you create an entirely new kind of object using a class. A class is like a template that you use to create new objects.
Think of the class like a cookie-cutter, and think of the object as the cookie that is created based on the class. As all the cookies are created from the same cookie cutter, they all have the same characteristics, even though they are all individual cookies. When an individual object is created from a class, it’s referred to as an instance of that class.
So, if you can arrange for SoundPanel()
to be a class, you can create custom widgets as required:
You need code that creates a new grouped widget in the GUI every time you make a call like this:
Let’s define a SoundPanel() class.
The SoundPanel()
class creates a new kind of tkinter Frame()
, and you can specify this relationship using the code like this:
As well as the what (it’s a frame), you also have to worry about the how, which will define the behavior of your new widgets. To do this, you need to add methods inside the class. To understand how this works, imagine you have created an alarm button object from a class. The alarm button will need to know what to do when somebody hits it:
To see in more detail how the new SoundPanel
widgets use the methods in the SoundPanel
class, let’s look in more detail at just one of the methods. What happens if someone clicks on the checkbox within the widget?
The method you need to add to your class should look familiar. This code is almost the same as the track_toggle()
event handler we created before. The only difference is that this method is a little more selfish.
The methods in the class are going to be used for lots of objects, so the code in the class needs some way to know which SoundPanel
object it is working with at any point in time. It does that with the self
variable.
The self
variable is passed to each of the methods in the class automatically by Python and it identifies the current widget object being used. By adding “self.
” to the front of the object’s variable names in the class code, you make sure the code is using the data that belongs to the current widget.
Let’s add some methods to the SoundPanel() class...
If you convert the original change_volume()
function to a method and add it to the class, you end up with code that looks rather like the original create_gui()
function:
In fact, the new SoundPanel()
class can completely replace the code in the sound_panel.py
file (as create_gui()
is no longer needed).
But before you do that, there’s still a little more code to write. The class needs to be told what to do when the brand new SoundPanel()
is created. The class needs an initializer method that knows how to create instances of the class.
Some programming languages call these initializer methods CONSTRUCTORs, because they detail what happens when a new object is created or “constructed.”
Let’s create the initializer for the SoundPanel() class.
The SoundPanel()
class has methods that define the behavior that it implements. In addition to the methods, the class also has to detail the data that it holds. For the SoundPanel()
class, this data is made up from three things: the track to play, its checkbox, and its associated slider.
The DJ is so impressed by how usable your program is that he wants to try it out tonight for his complete set, prior to its official unveiling at the World Music Mixing Expo. The DJ needs to work with a lot more than two tracks. In fact, he has an entire directory full of loops.
Now you could just change the code to add the extra files to the interface, but the DJ wants to be able to manage which tracks the program uses. So you will have to find all of the WAV files in the current directory and then add them to the interface when the program starts.
Let’s get this thing to work.
The DJ took your program to the World Music Mixing Expo and rocked the house, with your name in lights! By using classes for the widgets and creating an object oriented program, you made your good program (and the DJ’s performance) great.
Object orientation is meant to help you create complex programs with very little code. A lot of languages use object orientation, and not just for graphical user interfaces. You can use objects to build web applications or simulators or games. Any time you need to write an advanced program but don’t want your code to turn into a tangled mess of spaghetti, object orientation can come to the rescue!
You got to the end of the book! And what a great journey it’s been. You’ve ruled with control statements. You’ve powered-up your programs with modular code. You’ve made graphical user interfaces that sing and, finally, you took your coding skills to the next level with object orientation.
Well done!
You’ve got Chapter 11 under your belt. Let’s look back at what you’ve learned in this chapter:
* Local functions live inside other functions.
* Object orientation is a way of using software objects to handle complexity.
* Classes are machines to create objects; think of them like a “cookie cutter”.
* Classes have methods that define their objects’ behavior.
* Created objects are known as “instances”of some class.
* An initializer tells an object what to do once it’s been created.
* Some languages call initializers “constructors.”
* Not all objects are GUI objects.
* Widgets are a kind of object.
* Frame() - tkinter’s frames are widgets that contain other widgets and help to keep the widgets together (grouped).
* class - a keyword that introduces a new class definition.
* __init__() - the name of the method that is called automatically on object creation.
* self - methods have a special variable called “self” that is set to the current object.
* Adding “self.” to the start of a variable means it belongs to the current object.
We’re sad to see you leave, but there’s nothing like taking what you’ve learned and putting it to use. You’re just beginning your programming journey and we’ve put you in the driving seat. We’re dying to hear how things go, so drop us a line at the Head First Labs web site, www.headfirstlabs.com, and let us know how programming is paying off for YOU!
18.119.111.9