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