With an object in mind

Since we learned about custom widgets and object orientation, we've become a real class act.

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.

mixing multiple tracks

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.

IfTT

La MIX

volume

H

Control the volume.

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.

A function, when Called, CoaM create the interface for a single track 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?

new function h

£ong Excise

Here is the code from the end of the previous chapter. Study it carefully, and then, in the space on the next page, write the code for your new function (based on the code below).

from tkinter import * Begin by importing the import pygame.mixer * " librar^s y°u n«d-

app.title("Head First Mix") sound_file = "50459_M_RED_Nephlimizer.wav"

mixer = pygame.mixer

sound system.

def track_toggle():

track.play(loops = -1) ^ The event-handler functions else: detail what happens when an track. stop () ^^ event oCCurs.

def change_volume(v):

track.set_volume(volume.get())

Define the checkbox widget.

track = mixer.Sound(sound_file) track_playing = IntVar() track_button = Checkbutton(app, variable = track_playing, command = track_toggle, text = sound_file) track_button.pack(side = LEFT)

volume = DoubleVar () Define the slider widget.

volume.set(track.get_volume())

volume_scale = Scale(variable = volume, from_ = 0.0, to = 1.0, resolution = 0.1, command = change_volume, label = "Volume", orient = HORIZONTAL)

volume_scale.pack(side = RIGHT) def shutdown():

S- fondle a clidk on track.stop() app.destroy()

i app.protocol("WM_DELETE_WINDOW" , shutdown) app.mainlo°p()^ start the event loop.

function created fat #v tot\% E%€RciSe SoLytlOH

Here is the code from the end of the previous chapter. You were to study it carefully, and then, in the space on the next page, write the code for your new function (based on the code below).

from tkinter import * import pygame.mixer app = Tk()

app.title("Head First Mix")

sound_file = "50459_M_RED_Nephlimizer.wav"

mixer = pygame.mixer mixer.init()

def track_toggle():

track.stop()

def change_volume(v):

track.set_volume(volume.get())

track = mixer.Sound(sound_file) track_playing = IntVar()

track_button = Checkbutton(app, variable = track_playing, command = track_toggle, text = sound_file) track_button.pack(side = LEFT) volume = DoubleVar() volume.set(track.get_volume())

volume_scale = Scale(variable = volume, from_ = 0.0, to = 1.0, resolution = 0.1, command = change_volume, label = "Volume", orient = HORIZONTAL)

volume_scale.pack(side = RIGHT)

def shutdown(): track.stop() app.destroy()

app.protocol("WM_DELETE_WINDOW", shutdown) app.mainloop()

ty sporting .

the libraries you »eed to use in this »nodule.

Create a new function that contains the GUI-creating Code from the Current program.

from tkinter import * import pygame def Create_gui(app, mixer, sound_file): def track_toggle():

if track_playing..get() =•=• 1: track.play(loops = -1)

else:

track.stopO

def change_volum track.set_volume(volumeget())

AJ of this ¿ode is part track = mixer.Sound(sound_file)

the function, so it J

«eeds to be indented.^. . ( trac.k_playin.g..=. . In.tVa.rQ

. \ .. track_button. .=. checkbutton^app,. . variable .=. .track_playing,

Command = track_toggle, text = sound file)

track_button.pack(side = LEFT)

volume = DoubleVar() volumeset(trackget_volum volume_scale = Scale(variable = volume, from_ = 0.0,. to = 1.0, resolution = 0.1, command = change_volume, label = "Volume", orient = HORIZONTAL) volume_scalepack(side = RIGHT)

function in 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:

TUndtion.

When this

•fundtion is

¿ailed, it starts executing from here-

As always, the tails -to yatV.0" add the Widgets the QAl from tkinter import * import pygame def create_gui(app, mixer, sound_file): def track_toggle():

track.stop()

M>te: when this faction is ¿ailed, * " W parameters.

These are the event def change_volume(v):

track.set_volume(volume.get())

track = mixer.Sound(sound_file) track_playing = IntVar()

track_button = Checkbutton(app, variable = track_playing, command = track_toggle, text = sound_file) track_button.pack(side = LEFT) volume = DoubleVar() volume.set(track.get_volume())

volume_scale = Scale(variable = volume, from_ = 0.0, to = 1.0, resolution = 0.1, command = change_volume, label = "Volume", orient = HORIZONTAL) volume_scale.pack(side = RIGHT)

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 localfunction is just a function inside a function.

Let's see why they're important for the DJ's program.

A function-in-a-function is called a local function.

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.

But if you are creating several checkoxes, you are going to need a separate event handler for each of them.

That's why you have localfunctions 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.

Each widget needs its own event handler.

localize functionality

Functions inside of functions inside of functions inside of functions... now, that's what I call complexity. Of course, everything's local, you know.

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.

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

Frank: That's all I was trying to say...

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?

Frank: OK. A function of functions it is, then. I'm all for keeping it simple, stupid. ;-)

Jim: Yeah... their last album was really something special, wasn't it? Frank & Joe: Eh?!?

Begin by putting the create_gui () function in a separate module called sound_panel. py. Then, write a new version of the hfmix. pyw program that uses the sound_panel. py module:

Wite your code here.

sound panel sound panel

You were asked to begin by putting the create_gui () function in a separate module called sound_panel.py. Then, you were to write a new version of the hfmix.pyw program that uses the sound_panel. py module:

from tkinter import *

'"-port all the tundtions -A-om from sound_panel import * the new module.

import pygamemixer app = Tk()

app.title("Head First Mix")

mixer = pygamemixer mixer.mitO

By calling the new y*. cre.ate_gui(appi . mixer.,. ,,5.0451_M_RED_Ne.phl.im.ize.r.wav"). frea^S crsafejuiiarc, . mixer,. "49ll1_M_RE.D_HardBouncer.wav.").

sound controls on the

def shutdown(): track.stop() app.destroy()

app..pr.otocol(".WM_D.ELETE_WiINDOW", . shutdown).

app.mainloop()

Tqst Drivq

With the code typed into IDLE, take this latest version of the DJ's program for a quick spin by pressing F5.

These check buttons start and stop each , -■-": >

These check buttons start and stop each , -■-": >

These scales control the volume.

The program has created a checkbox and a volume slider for each track. The program called the create gui() function twice, creating two sets of widgets. Of course, you can call it as many times as you like and the create gui () function will create the two widgets for each track.

When you click on the two checkboxes, the two tracks both play at the same time! You can start and stop each of them independently by selecting and deselecting the checkboxes. But more than that, the volume sliders independently change the volume of the tracks, allowing you to mix them together.

This is a big deal. The create gui() function is not only creating the separate widgets and adding them to the interface, but it is also creating the event handlers that allow each pair of widgets to control each separate track. Here the program is controlling just two tracks, but if you added more calls to create gui () in the code, there's no reason why you couldn't get the interface to work with as many tracks as you like.

Let's see what the DJ thinks.

confusing interface

The DJ is confused

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

How am I supposed to know which volume scale is for which track?

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.

How am I supposed to know which volume scale is for which track?

T^k #/ „ W #z? Track #2 or track #1?

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.

To avoid confusion, the GUI needs to look something like this-

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?

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:

Add a checkbox Add a slider. Add a checkbox...

Your new widget "glues together" the other W.dgets so they aUys stay grouped-

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

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

frame widgets

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:

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:

Create a new SoundPanel widget.

These are the same parameters you passed to the "create_guiO" method.

These are the same parameters you passed to the "create_guiO" method.

You can add your widget to the Gui using the packer, just like with all the other widgets.

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?

Do this!

Even though you haven't created the SoundPanelcode 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.

How do you convince the computer to create a new widget each time you call SoundPanel()?

programming class

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:

You need a new SoundPanelO dass.

SoundPanel

SoundPanel ates OW-Ts, ^

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

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

Use the class to create a new object.

panel = SoundPanel(app, mixer, "49119_M_RED_HardBouncer.wav")

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:

This says that we are defining a new CLASS.

This is the class name.

This says that we are defining a new CLASS.

Indicate that the new class is a type of Frame.

The METHODs of the class go here. The word "method" is used in O°P instead of the word "fWW

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:

This will be a method inside the alarm button's class.

The method tells the button how to behave when something happens.

This will be a method inside the alarm button's class.

class AlarmButton(Frame): def button_hit(self):

klaxon.hoot_loudly()

Someone moves the volume slider.

The computer starts up.

Create the interface.

The method tells the button how to behave when something happens.

You need to create some methods for the new SoundPanel () class. Which of the following behaviors do you think you need to create methods for? Draw a circle around each one:

Someone clicks the checkbox.

You get to the end of the track.

necessary methods sharpen your pencil ^ , Solution event handlers that do something like this.

The computer starts up.

You needed to create some methods for the new SoundPanel () class. You were asked to identify which of the following behaviors you thought you needed to create methods for:

You've already created

Someone clicks the checkbox.

The computer starts up.

event handlers that do something like this.

You get to the end of the track.

Create the interface.

t^Wbai do you need ^ to do io ¿rcälc the inier-fate?

therei ore no

Dumb Questions

Q/ Why is there a method to create the widget?

A" There isn't a method to create the widget. But there is a method to create the interface. That method will run immediately after the widget is created.

I don't get it. What's the difference between a widget and an object?

A" A widget is a particular type of object. It's an object that you can add to a graphical user interface.

Q/ So there are some objects that are not widgets?

A." Absolutely. Most objects are used behind the scenes in programs. All of the numbers and strings you've used so far have actually been objects.

Q/ So you can't always see objects on the screen then?

A." No, most objects run quietly in memory and they don't have any display at all.

Q/ Is Python the only object oriented language?

A." Lots of languages—such as Java, C#, and Ruby—use objects to handle complexity.

Q/ So learning object orientation is a good way of getting into other languages?

A." Yes, understanding object orientation gives you a insight into how other languages think.

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?

Hey, someone clicked the checkbox. The event handler for that is called "track_toggle". What happens now, SoundPanel class?

This is -the Code to the

V The method takes a

def track_toggle(self):

self.track.stop()

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.

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

class replaces function

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:

Most of this code looks very similar to the VeateAuiO" method, except for all those uses of "self".

from tkinter import * import pygame.mixer class SoundPanel(Frame):

def track_toggle(self):

self.track.stop()

def change_volume(self):

self.track.set_volume(self.volume.get())

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 rnitdiz«- me-thods constructors, because they detail what happens when a new object is created or "constructed."

Let's create the initializer for the SoundPanel() class.

Code Magnets

We've started to create the initializer code for you, but there are still a few parts missing. See if you can work out where the missing code fragments fit. Here is the code that creates a SoundPanel ( ) object. Position the code magnets properly to complete the method:

track_button = Checkbutton(

Because SoundPanel() inherits from tkinter's Frame(), you need t° be sure to initialize the Frame() BEFORE yw. initialize the SoundPanelO.

command = self.track_toggle, text = sound_file)

track_button.pack(side = LEFT)

self.volume.set(track.get_volume())

volume_scale = Scale( , variable = self.volume, from_ = 0.0, to = 1.0, resolution = 0.1, command = , label = "Volume", orient = HORIZONTAL) volume_scale.pack(side = RIGHT)

initializer constructed

Code Magnets Solution

We've started to create the initializer code for you, but there are still a few parts missing. See if you can work out where the missing code fragments fit. Here is the code that creates a SoundPanel ( ) object. You were asked to position the code magnets properly to complete the method:

That's a douWcy Frame._init_(self, app)

underscore at either the word '

track = mixer.Sound(BQund_file)

= intVarO

selfjJl track_playing_

track button = Checkbutton( I s e 1 f I , variable

Each SoundPanel() object has its own track.

Each SoundPanelO object has its own checkbox.

s e 1 f . I I tra ck_p l ayi ng command = self.track_toggle, text = sound_file)

volume = DoubleVar()

self.volume.set(track.get volume())

volume_scale = Scale( f s sf f f , variable = self.volume, from_ = 0.0, to = 1.0, y» LJ

Each SoundPa^l" ' object has its own slider-

resolution = 0.1, command =

sisess_eesseS| , label = "Volume", orient = HORIZONTAL) volume_scale.pack(side = RIGHT)

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 Class Exposed

This week's interview:

Life in senior object management.

Head First: Hello, Class. It's good of you to find the time to speak to us.

Class: I assure you the inestimable pleasure is all mine.

Head First: I'm sorry. What's that?

Class: Apologies.Just checking my initializer. I always do it when I create.

Head First: Ah, yes. That's your constructor, isn't it? The method you use to create objects?

Class: Well, I'm aware that some people refer to it as a constructor, but I prefer initializer. I don't use it to create objects, you see. I just use it to configure them once they've been created.

Head First: You have a lot of methods?

Class: Oh, more than you can possibly imagine.

Head First: In the code we've just seen, the SoundPanel () class, there were only three methods, weren't there?

Class: Oh, dear boy, there were only three methods defined explicitly in the class. But SoundPanel () inherited many, many more methods from its parent class, dear old tkinter's Frame ().

Head First: Frame () has a lot of methods, too?

Class: Too many to discuss, really. There are methods to paint components on the screen and details of what to do if things change size. Frame is a fearfully busy fellow. <beep beep> Excuse me. Hello? Yes? No, you need to stop playing track four. No, no, it's quite all right. Goodbye.

Head First: One of your objects?

Class: Yes. They keep me very busy, but I'd miss them if they didn't call.

Head First: I believe when someone calls an object method, the object always asks you to get involved?

Class: Yes. I'm in charge of the object's behavior. I do think it is so important to behave properly. Don't you?

Head First: Of course! Class, thank you. Class: Love the tie, by the way.

code review

Cède Review

It's always good every once in a while to check back on the state of your code and make sure everything's looking spiffy. This is what your program should look like at this point. It's probably worth checking to make sure everything in your code looks like this:

from tkinter import * from sound_panel import * import pygame.mixer app = Tk()

app.title("Head First Mix") mixer = pygame.mixer panel = SoundPanel(app, mixer, "50459 M RED Nephlimizer.wav") panel.pack()

panel = SoundPanel(app, mixer, "4 9119_M_RED_HardBouncer.wav") panel.pack()

def shutdown(): traer.stop() app.destroy()

app.protocol("WM_DELETE_WINDOW", shutdown)

app.mainloop()

pid you remember to «se So«»dPa»elO instead of dreaie_9u.()?

Sound_paneI.py from tkinter import *

when the object is Created import pygame .mixer An initializer method Comes class

SoundPanel(Frame):

when the object is Created def _init_(self, app, mixer, sound_file):

self.track = mixer.Sound(sound_file) self.track_playing = IntVar()

track_button = Checkbutton(self, variable = self.track_playing, track_button.pack(side = LEFT)

self.volume = DoubleVar()

self.volume.set(self.track.get_volume())

volume_scale = Scale(self, variable = self.volume, from_ = 0.0, to = 1.0, volume_scale.pack(side = RIGHT)

def track_toggle(self):

self.track.stop()

def change_volume(self, v):

self.track.set_volume(self.volume.get())

command = self.track_toggle, text = sound_file)

resolution = 0.1, command = self.change_volume, label = "Volume", orient = HORIZONTAL)

Was this article helpful?

0 0

Post a comment