LessonsAdvanced: Bigger programsLesson 47

Properties with handlers

Make properties that work something out when read, check or react when set, and serve many objects from one place: getprop and setprop.

  • Advanced
  • Lesson 47 of 83
  • About 9 minutes
  • 8 examples

A property that works itself out

A custom property (Custom properties) holds whatever you put in it. A getprop handler goes further: when a script asks for the property, the handler runs, and what it returns is the property's value. The property can be worked out from others, so it is never out of date:

ExampleRun it in the playground
create button "Oven"
set the script of it to \
      "getprop fahrenheit" & return & \
      "   return the celsius of me * 9 / 5 + 32" & return & \
      "end fahrenheit" & return & \
      "setprop fahrenheit pValue" & return & \
      "   set the celsius of me to (pValue - 32) * 5 / 9" & return & \
      "end fahrenheit"

set the celsius of button "Oven" to 180
put the fahrenheit of button "Oven"
set the fahrenheit of button "Oven" to 212
put the celsius of button "Oven"
put the customKeys of button "Oven"
Output
356
100
celsius

Only celsius is stored. fahrenheit is a virtual property: reading it runs the getprop handler, and setting it runs the setprop handler, which sets celsius instead. To the scripts that use it, it is a property like any other.

Checking and reacting: setprop

A setprop handler runs when the property is set, with the new value as its parameter. It can refuse a bad value, update what the user sees, and then pass the message, which stores the value:

ExampleRun it in the playground
create button "Score"
set the script of it to \
      "setprop cScore pValue" & return & \
      "   if pValue is not an integer or pValue < 0 then" & return & \
      "      throw " & quote & "a score is a whole number, 0 or more" & quote & return & \
      "   end if" & return & \
      "   set the label of me to " & quote & "Score: " & quote & " & pValue" & return & \
      "   pass cScore" & return & \
      "end cScore"

set the cScore of button "Score" to 12
put the cScore of button "Score"
put the label of button "Score"
try
   set the cScore of button "Score" to "lots"
catch tError
   put "refused:" && tError
end try
put the cScore of button "Score"
Output
12
Score: 12
refused: a score is a whole number, 0 or more
12

Now no script can give the button a score that is not a number, and its label can never disagree with its score. Without the pass, the value is not stored at all:

ExampleRun it in the playground
create button "Forgetful"
set the script of it to "setprop cName pValue" & return & "end cName"
set the cName of button "Forgetful" to "Ada"
put the cName of button "Forgetful" is empty
Output
true

One handler for many objects

getprop and setprop are messages, sent to the object whose property is used, so they travel the message path like any other. A handler further along it, in a card, a stack, a behavior or a library, can serve every object below it. the target is the object whose property it is:

ExampleRun it in the playground
set the script of this card to \
      "getprop area" & return & \
      "   return the width of the target * the height of the target" & return & \
      "end area"

create button "Wide"
set the rect of it to 20,20,220,50
create graphic "Square"
set the rect of it to 20,80,60,120
put the area of button "Wide"
put the area of graphic "Square"
Output
6000
1600

Property arrays

A getprop or setprop handler can take a key in square brackets, for a property used like an array. The key arrives in the handler's name line:

ExampleRun it in the playground
create button "Translator"
set the script of it to \
      "getprop cWord[pKey]" & return & \
      "   switch pKey" & return & \
      "      case " & quote & "hello" & quote & return & \
      "         return " & quote & "bonjour" & quote & return & \
      "      case " & quote & "cat" & quote & return & \
      "         return " & quote & "chat" & quote & return & \
      "   end switch" & return & \
      "   return " & quote & "?" & quote & return & \
      "end cWord"
put the cWord["hello"] of button "Translator"
put the cWord["cat"] of button "Translator"
put the cWord["dog"] of button "Translator"
Output
bonjour
chat
?

Going round the handlers

While lock messages is in force, no messages are sent, so setting a property stores it directly and its setprop handler does not run. Saving and loading use this, to put values back exactly as they were without setting off every handler on the way:

ExampleRun it in the playground
create button "Watched"
set the script of it to \
      "setprop cLevel pValue" & return & \
      "   put " & quote & "the handler saw " & quote & " & pValue" & return & \
      "   pass cLevel" & return & \
      "end cLevel"
set the cLevel of button "Watched" to 1
lock messages
set the cLevel of button "Watched" to 2
unlock messages
put the cLevel of button "Watched"
Output
the handler saw 1
2

The property inside its own handler

Inside its own handler, a property of me is just what is stored: reading it there does not run the getprop handler again, and setting it there stores the value without running the setprop handler again. So a handler can use the stored value, and a setprop handler that changes the value it was given sets the property itself instead of passing (a pass stores the value as it was set):

ExampleRun it in the playground
create button "Doubler"
set the script of it to \
      "setprop cAmount pValue" & return & \
      "   set the cAmount of me to pValue * 2" & return & \
      "end cAmount" & return & \
      "getprop cAmount" & return & \
      "   return the cAmount of me && " & quote & "(doubled)" & quote & return & \
      "end cAmount"
set the cAmount of button "Doubler" to 5
put the cAmount of button "Doubler"
Output
10 (doubled)

Try it yourself

Make a progress bar: a grey graphic for the track and a teal one for the bar, 300 pixels at full. Give the bar a cPercent property whose setprop keeps the value between 0 and 100, sets the bar's width to match, and stores it. Set it to 140, then to 65, and put the width and the cPercent each time.

Show a solution
ExampleRun it in the playground
create graphic "Track"
set the filled of it to true
set the backgroundColor of it to "#E2E8F0"
set the lineSize of it to 0
set the rect of it to 40,40,340,64

create graphic "Bar"
set the filled of it to true
set the backgroundColor of it to "#14B8A6"
set the lineSize of it to 0
set the rect of it to 40,40,41,64
set the script of it to \
      "setprop cPercent pValue" & return & \
      "   put max(0, min(100, pValue)) into pValue" & return & \
      "   set the width of me to max(1, round(3 * pValue))" & return & \
      "   set the left of me to 40" & return & \
      "   set the cPercent of me to pValue" & return & \
      "end cPercent"

set the cPercent of graphic "Bar" to 140
put the width of graphic "Bar" && the cPercent of graphic "Bar"
set the cPercent of graphic "Bar" to 65
put the width of graphic "Bar" && the cPercent of graphic "Bar"
Output
300 100
195 65
Stage what the code made, as the OXT-Beyond engine draws it
The stage, with a progress bar near the top: a light grey track with a teal bar filling 65 percent of it, from the left.

The handler stores the value it kept in range by setting the property itself: a pass would have stored 140, the value as it was set, and the first line would say 300 140.

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.