Button. Button. How do I code this button?
June 29, 1999
by Irv Kalb
This is an article about buttons. No, it's an article about behaviors. No, it's an article about a good object oriented programming technique. No, it's an article about intrasprite communication. Well, actually, it's about all these things rolled together. Stay with me and you'll see what I mean.
Basic button code
A very standard thing you want to do with a button is that when a button is clicked, you want to call some handler. Further, a typical button has an up state and a down state. A good way to start this is to lay out the cast so the you have the up version in one cast member and the down graphic in the next cast member. Assuming this layout, you can easily code a button behavior which calls a handler (for example to start a game) as follows:
Note 1: This is bare-bones code for the purpose of making a point, and does not handle rollovers - we'll get to that later
Note 2: All code here is done in Director 6 syntax so that it can be used with Director 6 or 7)
property spritenum property pnmUp -- property, number of member of the Up button property pnmDown -- prop., number of member of the Down button on beginSprite me set pnmUp = the number of the member of sprite spriteNum set pnmDown = pnmUp + 1 end on mouseDown me set the member of sprite spriteNum = pnmDown end on mouseUp me set the member of sprite spriteNum = pnmUp StartGame() end
Now, assume that you have a slightly different requirement. Instead of starting a game, when the user clicks on a button you want the movie to branch to a specified label. You could write a very similar behavior to handle this case like this:
property spritenum property pnmUp property pnmDown property pLabelToBranchTo -- where should we go? on beginSprite me set pnmUp = the number of the member of sprite spriteNum set pnmDown = pnmUp + 1 end on mouseDown me set the member of sprite spriteNum = pnmDown end on mouseUp me set the member of sprite spriteNum = pnmUp go pLabelToBranchTo end on getPropertyDescriptionList me set lDescription = [:] addProp(lDescription,#pLabelToBranchTo, ¬ [#Default:"", #Format:#String, ¬ #Comment:"Branch to where?"]) return lDescription end getPropertyDescriptionList
Another button might be used for adding to some counter, or stopping some process. Since all these functions are so similar, do you really need to write a different but similar script for each of these buttons? There must be a better way to script things like this - and there is.
The thing to realize about the above examples is that these behaviors actually perform two distinct functions. First, they handle the user interface component of the button going down and up. Then, when when the mouse button is released, some action is taken. Conceptually then, the thing you would really like to do would be to split up the functionality into separate scripts. That is, one script which handles the button movement and a separate script to handle the action. This separation of functionality provides a better object oriented approach to this problem.
Here is how we can restructure the above code to do exactly this. Each of these scripts is placed into a separate cast member. First, we build a generic button script that you can use with any button (assuming a proper layout in the cast):
-- Button behavior property spritenum property pnmUp -- property, number of member of the Up button property pnmDown on beginSprite me set pnmUp = the number of the member of sprite spriteNum set pnmDown = pnmUp + 1 end on mouseDown me set the member of sprite spriteNum = pnmDown end on mouseUp me set the member of sprite spriteNum = pnmUp sendSprite(spriteNum, #mHit) -- Special message! end
And then a second script for the action of the button:
-- Action behavior on mHit me StartGame() end
Sprites can have multiple behaviors. To apply both of these behaviors to a sprite, you drag the first one, the button behavior, from the cast and drop it onto a sprite in the score. Then you add the second behavior by dragging it onto the same sprite in the score.
Notice that the button behavior has no code dealing with what action to perform and that the action behavior has no code dealing with the user interface of the button movement. Now, let's look at the "glue" that makes these two behaviors work together.
The key is in the mouseUp handler of the button behavior. In that handler, after showing the up version of the button, there is the line:
sendSprite(spriteNum, #mHit)
This line of code sends out a special event message called "mHit". sendsprite is a Lingo statement which calls a handler in a behavior attached to a specified sprite. In the button behavior, the variable "spriteNum" is automatically given the value of the current sprite number as long as you declare it at the top of the script along with any other properties. So, by using the variable spriteNum in the call, we are assured that the message is only being sent to any behaviors attached to the current sprite. This is known as "intrasprite" communication - that is, communication within the sprite. The symbol #mHit is a custom message which is then passed.
Note: there is nothing special about the message #mHit, I could have used #specialMessage, or even #xyzzy. I chose #mHit because it is easy to remember. Now that I have been using this approach for a while, mHit is as common to me as mouseUp and mouseDown.
On the receiving end of this message is any behavior attached to the same sprite which has an mHit handler. So, given this approach, any action behavior can be created by simply coding a single handler, "on mHit". Within this handler we can code anything we want to have happen when a button is clicked on.
The go to a specified frame behavior becomes this:
property pLabelToBranchTo on mHit me go pLabelToBranchTo end on getPropertyDescriptionList set lDescription = [:] addProp(lDescription,#pLabelToBranchTo, ¬ [#Default:"", #Format:#String, ¬ #Comment:"Branch to where?"]) return lDescription end getPropertyDescriptionList
You should be careful to remember to attach an action button to each of your buttons. If you attach the button behavior (which sends out the #mHit message) but forget to attach any behavior which receives the message, the button will appear to work correctly. That is, it will move down and up, etc., but no action will occur, not even an error message. This may lead to much hair pulling. Alternatively, you can apply multiple action behaviors to field the #mHit message. If you drag more than one action behavior onto a sprite with the button behavior, each action behavior's mHit handler will be called in the order that the behaviors were dragged onto the sprite.
Extending the button behavior
Now that we have made this split between the button behavior and the action behavior, we can make the button behavior more intricate. And because the button behavior communicates with an action behavior by the means of the single #mHit message, any changes we make to the button behavior are completely independent of the action behavior.
Some other typical "standard" features of buttons include:
- Highlighted state when the cursor rolls over the button
- Ability to gray out or make the button unavailable
- Having a cursor change when over a clickable button
- Play a sound when clicking on the button
Again, all of these things can be added to the generic button behavior without affecting the action behavior in the slightest. When a button is in the gray or unavailable state, the button should not highlight, the cursor should not change, a sound should not play, and the down state should not show when the button is clicked. Most importantly, the button should not do any action on mouseUp. Given this structure, when the button is in the unavailable state, it does not send out the #mHit message, and therefore, the action is not initiated.
I have written a button behavior which incorporates all the above functionality. For the sake of convenience, I have hard coded a cursor to use (280, the pointing cursor), and a sound to play when the button is clicked. I have done this so that this behavior is usable without making any choices. However, if you want to, you can easily allow for customization of this behavior by allowing choices of these elements in a getPropertyDescriptList handler.
Download a sample movie in Mac or PC format.
Other user interface buttons
In my own coding, I have used the basic concept of splitting the button behavior from the action behavior in different types of user interface elements. To build in consistency, and because I can't remember too many handler names, I have always used the same #mHit message. For example, I have built a radio button behavior, a check box behavior, a slider behavior, a toggle behavior and others in a similar fashion. For some of these behaviors, the mHit message also uses one or more parameters to pass state information. For example, in my toggle behavior, when the button is clicked, the toggle behavior sends out the following message.
sendSprite(spriteNum, #mHit, pfNewState)
where pfNewState is a property which is a flag which has the value of TRUE or FALSE depending on the new state of the toggle button. The action behavior must have an mHit handler which expects a boolean variable and reacts accordingly:
on mHit me, fTrueOrFalse if fTrueOrFalse then -- do whatever when button goes to TRUE state else -- do whatever when button goes to FALSE state end if end
Summary
I have shown how to write a build a generic button behavior using instrasprite communication. And further, how to extend the use of this concept to different types of user interface elements. I hope that by reading this article, you have learned something about buttons and behaviors and good object oriented programming style and instrasprite communication ... Well, you get the idea.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.