Articles Archive
Articles Search
Director Wiki
 

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:

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.

Irv Kalb has been working as an independent software developer in Director and Lingo for over ten years. He has written extensively on object oriented programming in Lingo, and his on-line Ebook on this topic can be found at http://www.furrypants.com/loope. Irv is always interested in discussing new projects.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.