Article
Difficulty Rating
9
Messages and the Message Hierachy

.

 

The scripting language in MetaCard is based on the principal of messages. A good understanding of this principal and of the hierarchy or path messages take is essential to efficiently learning MetaCard.

Whenever something happens, a message gets sent. These messages can be generated directly by the user, or by a script. For example, when someone clicks on a button, it will receive a mouseDown message as the mouse is pressed, and then a mouseUp message when the mouse is released over the button. The mouseUp message is the most usual message to use to respond to a user clicking on a button. Other messages include mouseEnter for when a mouse moves over an object, mouseLeave for when a mouse moves out of an object, mouseMove for when the mouse moves whilst over an object, keyDown for when a key is pressed, preOpenCard for just before a card is displayed on the screen, and many, many more.

To respond to a message, you write a script. At its most basic, you select the object you want to write the script for with the pointer tool, right-click (or control-click if you are using the MacOS) and choose Edit Script from the contextual menu that comes up. You then type in a routine or handler that starts with the name of the message you want to respond to.

Here is an example of a very basic handler you can place in any button:

on mouseUp
  beep
  move me relative 100,0 in 5 seconds without waiting
end mouseUp
                           

This handler will cause the system to beep and the object to move 100 pixels to the right, when the mouse is pressed and released on the object that contains this script.

You can make the behavior a little more complex by including another handler.

on moveStopped
  set the location of me to 200,200
end moveStopped
                           

This handler will cause the button to jump to a location 200 pixels right and down from the top left of the card. It is an example of a message generated by MetaCard in response to an event (the button stopping moving), as opposed to a message directly sent by the user (e.g. when the user pressed the button).

The message hierarchy

In the example above, it is very clear where messages go. The user clicks on the button, and the mouseUp message is received by the button, which then does the mouseUp handler. However, it is not always so simple. Suppose there isn't a mouseUp handler in the button. When the user presses the button, the message gets sent to the button anyway. As there is no handler for the message in the button, MetaCard then sends the message to the next object in the message path.

The message path (also know as the message hierarchy) is a logical system for working out what object gets what message. Here is the path:

button -> card -> stack -> mainStack -> Home Stack -> MetaCard

Note that the first item in that path, the button, could be any object residing on a card, such as a field, image, etc.

There are a simple set of rules to determine where in that path a message will get sent and intercepted.

  • Messages are always sent to the lowest possible object in that path. For example, if a button is clicked on, a mouseUp message will be sent to that button. If a card is opened, a preOpenCard message is sent to that card.
  • Messages that are intercepted by handlers do not get any sent further up the path, so all messages only get processed once
  • Messages that are not intercepted get sent up the path until they are intercepted
  • If messages are never intercepted, they reach MetaCard, which may or may not do something with the message

Why would you want a message to be passed up a path? Surely if you haven't placed a message handler in a button for a particular message, you don't intend the message to be acted on? This is not the case. Message passing is actually a very useful feature, because it means that you can write the minimum number of scripts to achieve the effect you want. For example, if you have 10 buttons on a card that all do the same thing when clicked, you can put a mouseUp handler in the card, and put the common commands in it.

What if you have some objects that all use the same script, but others that don't? A simple way of dealing with this is to insert the objects into a group, and place the common script into the group object. When an object is grouped, the message path looks like this:

button -> group -> card -> stack -> mainStack -> Home Stack -> MetaCard

Another way of doing it is to check the target function. That function contains the name of the object that would originally have received the message, and can be used by handlers further up the message path to determine if they need to take action, and what action to take. Place the following example into a card script:

on mouseUp
  put the target
end mouseUp
                           

Now create multiple objects on the card, choose the browse tool to switch to run time mode, and click each object. The message box will display the object clicked on. (The put command is used to place text into the message box.)

What happens when the message reaches MetaCard? That depends on the message. In most cases, MetaCard doesn't do anything. However, in some cases it does. For example, if MetaCard receives a closeStackRequest message, it will close the current stack. And if it receives a keyDown message it will place the key that has just been typed into the field with the insertion point. Intercepting these messages is useful to prevent a user doing something, e.g. putting inputting invalid data into a field (you can check what the user typed before putting anything in the field) or closing a stack without saving (you can ask the user if he or she wants to save before closing).

Special cases

What if you have a handler in an object that intercepts a message, does something, but then you want the message to continue up the hierarchy so that a handler further up can also process the message? This is useful if you have a common script that does processing after specific processing has been performed by each object. It is also useful in cases where you have used a message such as keyDown to validate a key for entry into a field, but now want to allow the data entry to proceed having checked it. The solution is to use the pass command. This command causes the message to be sent up the hierarchy, just as if it had never been intercepted. This script would only enter text into a field if the user typed a number:

on keyDown theKey
 if theKey is a number then pass keyDown
  else beep
end keyDown
                           

Note the third word on the first line of that script "theKey". TheKey is a variable that contains the actual key the user pressed. Many messages come with optional parameter information like this. For example, the mouseUp message comes with the number of mouse button that was pressed. On Mac and Windows systems that will usually be 1 or 3 (use the control-key to do a right click on MacOS systems), on UNIX workstations, there may also be a button number 2. The following script displays which mouse button was used:

on mouseUp whichButton
  put whichButton
end mouseUp

What if you want to include a script before or after all other scripts temporarily? For example, you might want to capture every user action before it was sent to any object and do something with it. An example would be displaying a coordinates window with the mouse coordinates. You would want to intercept the mouseMove message before it got sent, and potentially used, by any other object. The solution to this type of problem is to use frontScripts and backScripts. FrontScripts are object scripts inserted before the message hierarchy, and backscripts are scripts inserted after the message hierarchy, but before MetaCard itself.

frontScript -> button -> card -> stack -> mainStack -> Home Stack -> backscript -> MetaCard

Here is an example of a frontScript in use. Create a button, name it "coordinates" (select it and choose Object Properties... from the Edit menu to change its name) and place the following script into it:

on mouseMove
  put the mouseLoc
end mouseMove
                           

If you choose the browse tool and move the mouse over that button, you will see that the location of the mouse is displayed in the message box. Now, type the following into the message box and press return:

insert the script of button "coordinates" into front
                           

Now move the mouse around the card and over other objects. The coordinates are displayed as you move. To remove this script:

remove the script of button "coordinates" from front
                           

To remove all scripts from front:

remove all scripts from front
                           

It is always a good idea to include a pass statement at the end of a frontScript. This allows the message intercepted in the frontscript to go on to be received by the object that would normally expect that message, such as the button under the mouse. So in the coordinates example above, you should insert a line before end mouseMove that says pass mouseMove.

Backscripts are the same as frontScripts, except that these get called only at the end of the message path, and therefore won't be called if any handler intercepts the message. They are often useful to prevent a user from closing any stack in large series of stacks, or to do a check on a certain type of message that must always be intercepted somewhere in the path. Passing backScript messages is only necessary where you want the message to continue on to MetaCard itself to be acted on.

The mainStack script

The mainStack script is worthy of particular note. Any message sent to any object, card or subStack stored in the same file as the mainStack, that is not intercepted by an object will be sent through the mainStack. This has two important ramifications. The first is that you can use the mainStack to store frequently used commands and functions - basically anything that multiple parts of your program might want access to. The second is that the mainStack may intercept and act on messages you don't want it to act on if you're not careful.

For example, a handler for the preOpenStack message (sent to all stacks when they are opened) will be executed every time a subStack stored in the same file as the mainStack is opened, providing the message isn't intercepted by that subStack first. Sometimes that's useful; often it isn't. To avoid it, set a variable in the mainStack script like this:

on preOpenStack
  global gHaveDonePreOpenStack
  if gHaveDonePreOpenStack is true then exit to MetaCard
  put true into gHaveDonePreOpenStack

--put the rest of the handler here.....

 

As you probably guessed, that handler will only execute once - i.e. the first time the mainStack is loaded. The global variable gHaveDonePreOpenStack retains its value until you quit, and gets set to true the first time the handler is executed. The handler will always be exited before execution in subsequent cases.

Did you find this article useful? Have any ideas for future topics? Email Us!