Chapter 11. Custom Widgets and Classes: With an object in mind

image with no caption

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 DJ wants to play more than one track

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).

How will you create the widgets for each track?

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.

image with no caption
image with no caption

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.

Create code for each track as a function

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.

image with no caption

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?

The new function contains other functions

With all the code gathered together in a new function, the code for the create_gui() function looks like this:

image with no caption

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.

Your new function needs to create widgets and event handlers

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.

image with no caption

Now, let’s update the program to use this new function.

image with no caption

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 DJ is confused

The program does exactly what the DJ wants, right? Well, not quite.

image with no caption

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.

image with no caption

Group widgets together

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?

image with no caption

Create a new type of widget

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:

image with no caption

Your new widget becomes a new building block for your GUI interface.

So, how are new widgets created? And how do they work?

A frame widget contains other widgets

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:

image with no caption

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:

image with no caption

Do this!

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()?

image with no caption

A class is a machine for creating objects

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:

image with no caption

You need code that creates a new grouped widget in the GUI every time you make a call like this:

image with no caption

Let’s define a SoundPanel() class.

A class has methods that define behavior

The SoundPanel() class creates a new kind of tkinter Frame(), and you can specify this relationship using the code like this:

image with no caption

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:

image with no caption

But how does an object call a method?

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?

image with no caption

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.

self identifies the widget calling the method

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...

The SoundPanel class looks a lot like the create_gui() function

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:

image with no caption

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.

Note

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.

class = methods + data

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 has an entire directory of tracks

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.

image with no caption

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.

It’s party time!

image with no caption

The mixer program brought the house down!

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!

Congratulations!

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.

image with no caption

Well done!

Your Programming Toolbox

You’ve got Chapter 11 under your belt. Let’s look back at what you’ve learned in this chapter:

Programming Tools

* 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.

Python Tools

* 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.

Leaving town...

image with no caption

It’s been great having you here in Codeville!

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!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.111.9