Custom Controls
One of the most useful features of LiveCode is the ability to group controls and view them as a single object. In this lesson, we demonstrate how to use a group as an custom control in LiveCode, while constructing a scrollbar.
You can download the sample stack from this url: https://tinyurl.com/ydx8s8bj
What is a LiveCode custom control?
The idea of a custom control in LiveCode is similar to the idea of an "object" in programming languages such as Java. A custom control is a group whose functionality is entirely encapsulated within itself, and which doesn't affect its environment in any way.
Using custom controls in your programs has many advantages, two of the most significant are increased modularity of the program, and the greater ability to re-use code between programs. A well designed custom control can be copied and pasted into any application and will be ready for use almost immediately. Over time you can build up a library of controls which will greatly speed up programming. The modularity helps when debugging your application, because a custom control is insulated from errors in other parts of the program, and likewise, the rest of the program is insulated from errors in the control.
Groups and Group Scripts
A simple and effective way to write a custom control is to make use of the group script, and have a single handler that updates the control based on its state. The state of the control is the only thing that the "outside world" knows about it, and custom properties in LiveCode are an ideal way to express this. Below we show how this idea can be used to create a scrollbar control that can be used in exactly the same way as the default LiveCode scrollbar, with the advantage of being fully customizable. Using this control it is possible to have "skinned" scrollbars throughout your program in no time at all!
Creating a custom control
The first thing to do is to create your custom control by dragging the fields, buttons etc that you require into your program, naming them, and grouping them.
For the scrollbar, create three buttons "End 1", "End 2" and "Slider" and an image area called "Background". Group these four controls and call the group "Scrollbar", don't worry about arranging or resizing the controls. Now open the script of your new custom control and lets begin.
The state of the custom control
The state of the custom control is the first thing to set up. The state will consist of a number of local variables, and a number of getProps and setProps to allow the rest of the program to manipulate them. We need to consider what properties the control should have. With the scrollbar this is easy, because we can just drag a LiveCode scrollbar control from the tools palette into the stack and examine its properties in the inspector.
Essential properties
The essential properties of the scrollbar are
- Orientation: whether the scrollbar is vertical or horizontal
- Start value: the number that the scrollbar runs from
- End value: the number that the scrollbar runs to
- Thumb size: the size of the scrollbar's slider
- Thumb position: the position of the scrollbar's slider
There are also some other properties that will help make the scrollbar more customizable:
- Line increment: how much to scroll when the user clicks on the scroll buttons
- Page increment: how much to scroll when the user clicks on the back of the scrollbar
So we use the variables sOrientation, sStartValue, sEndValue, sThumbSize, sThumbPosition, sLineInc and sPageInc. These are declared at the top of the group script, outside any handler.
Using setProp and getProp handlers
Next we write setProp handlers for each variable. It is a good idea to comment these handlers explaining what the format of each variable should be. It is sensible also to check the input is correct to help find errors and keep the state of the scrollbar valid. Below is the setProp and getProp for the scrollbar's orientation.
setProp cOrientation pValue
if pValue is not among the items of "horizontal,vertical" then
error "invalid orientation, expected horizontal or vertical, got " & pValue
end if
put pValue into sOrientation
updateScrollbar
end cOrientation
getProp cOrientation
return sOrientation
end cOrientation
The update function
Once all the properties of the control are set up in this manner, we can write the "update" function, which simply needs to take all the variables and set the scrollbar up correctly. This function should consist of the following steps:
- Verify all the parameters are valid
- Layout the scrollbar's controls
Also it is very useful here to add an update lock to the control. This enables multiple changes to be made to the control's state without needing to update many times, because the update handler may become quite large in more complex custom controls. The update lock requires another variable "sUpdateLock" and a setProp as follows:
setProp cUpdateLock pValue
if pValue is not among the items of "true,false" then
answer error "invalid value, expected Boolean, got " & pValue
end if
if pValue then
add 1 to sUpdateLock
else if sUpdateLock > 0 then
subtract 1 from sUpdateLock
end if
if sUpdateLock is 0 then
updateScrollbar
end if
end cUpdateLock
If we add an if statement to the top of the update handler that exits the handler immediately if sUpdateLock is not zero, then this effectively means we can set all the properties and then only call update once when we are done.
on updateScrollbar
local tResult
if sUpdateLock > 0 then
exit updateScrollbar
end if
put updateCheckParams() into tResult
if tResult is not empty then
answer error tResult
end if
updateLayout
end updateScrollbar
Setting up the scrollbar
Once the update handler has been written, all that remains is to handle the scrollbar's user interaction. This requires writing mouseDown, mouseUp and mouseMove handlers, plus a few more to fine tune the interaction. These handlers should set the properties of the scrollbar, thus causing it to update, rather than manipulate the controls directly.
It is relatively straightforward to write the remaining code, and if done correctly, the scrollbar can now be configured using a handler similar to this, in the script of the card that you wish to use the scrollbar on.
on scrollbarSetup pBar
set the cUpdateLock of pBar to true
set the cOrientation of pBar to the label of button "Orientation"
set the cStartValue of pBar to the text of field "startValue"
set the cEndValue of pBar to the text of field "endValue"
set the cThumbSize of pBar to the text of field "thumbSize"
set the cThumbPosition of pBar to the text of field "thumbPosition"
set the cLineInc of pBar to the text of field "lineInc"
set the cPageInc of pBar to the text of field "pageInc"
set the cUpdateLock of pBar to false
end scrollbarSetup
Note: pBar is a reference to a scrollbar group, for example the long id of group "Scrollbar"
Using the scrollbar
When the scrollbar is used, its cThumbPosition will be set to the same value as if it was a standard scrollbar in LiveCode, so that if you are already using a standard scrollbar in your program, it will be very easy to change your code to use a custom scrollbar group.
The key to this design is the fact that the custom control has a minimal interaction with the rest of the program. Its state can be changed, but no other messages need to be passed, and no shared variables are used. You can easily use the place command to duplicate the control across your whole stack, and can with equal ease use the scrollbar in many different stacks.
There are many ways to extend this custom control, if you download the stack attached to this lesson you can experiment. A very easy and useful extension is to give the scrollbar group default values for all its properties, which are stored as constants in the script. These default values can then be used if any of the scrollbar's properties are not set, allowing a scrollbar to be created without any initialisation.
Jim
Could the example stack be explained a little please? For example:
1) How is the custom control placed on the card of the Scrollbar Example stack?
2) In the example it looks like the "template" Scrollbar custom control is on a card which is a substack of the stack where it is to be used. Does that have to be the case? Is it the best way?
Thank you.
Peter Bogdanoff
Answering question 2 first: A substack is used in this case because a hidden substack is a handy way to store the custom scrollbar and other objects or resources used by any other stack in the project. That substack can also then be copied to be used in other projects.
Question 2: To place the custom control, in your script you would have something like this:
on moveScrollbar
copy group "MyScrollbar" of card 1 of stack "MySubStack" to card "MyCard" of stack "MyMainstack"
set the loc of group "MyScrollbar" of card "MyCard" of stack "MyMainstack" to 300,300
on moveScrollbar
Mark Guzdial
I'm trying this stack in LiveCode 9, and the scrollbar collapses into a single button and won't work. I'm trying to figure out what's going wrong in the code, but am not familiar enough with differences in LiveCode versions to figure out where the backwards compatibility broke. Any suggestions?
Elanor Buchanan
Hi Mark, thanks for bringing that to our attention.
The scrollbar did not have an initial orientation set which was causing it to be laid out incorrectly. I have updated the example stack with this fixed.
Kind regards
Elanor
Kim
Is there an "entirely encapsulated within itself" way to have a Custom Control respond to it's size being altered by script? Using this lesson as an example, having - set the width of group "scrollbar" - trigger the updateLayout handler? On resizeStack - Send "updateLayout" to group "scrollbar" - would work, but isn't self encapsulated. What is the correct way to have Custom Controls handle resizeStack events?
Regards
Kim
Kim
Sorry, cancel my previous question. I can achieve what I'm after using resizeControl. This directly contradicts the LC dictionary entry for resizeControl, but how it actually works is much better than how it is described in the dictionary ;-)
"The resizeControl message is only sent when the user resizes a control by dragging its handles. It is not sent if a handler changes the size of a control by changing its properties (width, height, and so on)."
Elanor Buchanan
Hi Kim
I just did a test with the stack from this lesson. I executed
set the width of group "Scrollbar" to 400
from the Message Box and a resizeControl message was not sent. How are you resizing the control and causing a resizeControl message to be sent?
This is probably a documentation bug, I just want to clarify what is happening before reporting it.
Thank you.
Elanor
Kim
Hi Elanor
Thanks (as always) for your prompt response.
1) Add this to the script of group "Scrollbar" -
On resizeControl
updateLayout
end resizeControl
2) Add this to the script of card 1002 -
On resizeStack
Set the rectangle of group "Scrollbar" to 300,148,300+(the width of me * 0.5),148+(the height of me * 0.05)
end resizeStack
3) Press the "Update" button on the stack. I'm not sure why, but it doesn't work unless you do this.
After doing this the four controls that make up group scrollbar will resize themselves in response to: dragging the handles of group "scrollbar" (as documented in the dictionary), and using the MessageBox to set the height / width of group scrollbar (as incorrectly specifically excluded in the dictionary), and drag resizing the stack. All of which is great, as it means that the geometry of a custom control can easily be entirely encapsulated.
Regards
Kim
Elanor Buchanan
Thanks Kim,
I found that there is already a bug report about the Dictionary entry for resizeControl.
Testing the stack I found that the reason it is not working until you click the "Update" button is that some of the script local variables to not have values until the script in the "Update" button sets them. If you change your resizeControl handler to the one below it should work automatically.
Kind regards
Elanor
on resizeControl
set the cUpdateLock of me to true
set the cOrientation of me to the label of button "Orientation"
set the cStartValue of me to the text of field "startValue"
set the cEndValue of me to the text of field "endValue"
set the cThumbSize of me to the text of field "thumbSize"
set the cThumbPosition of me to the text of field "thumbPosition"
set the cLineInc of me to the text of field "lineInc"
set the cPageInc of me to the text of field "pageInc"
set the cUpdateLock of me to false
breakpoint
updateScrollbar
end resizeControl
Elanor Buchanan
You can find the bug report here
https://quality.livecode.com/show_bug.cgi?id=17118
Kim
Thanks Elanor