Extending the MUI Xtra
Once annoyance with the MUI Xtra is you cannot set a callback target - you have to create a global movie script function. Here is a script for extending the MUI Xtra. The purpose of this script is to allow you to specify a callback target for the MUI Xtra (it is a bit of a frankenstein hack - but its does its business fairly discreetly so you can create multiple MUI xtras without having to use global variables or global functions).
Basically use it as if it were the Xtra but with the additional #Target Property in the windowPropList. For example, the following excerpt from a behaviour on a button shows this new property being added (hilighted).
property myDialog on mouseUp me if voidP( myDialog ) then me.OpenSettingsDialog() else -- dialog is still open end if end on KillMUIs(me) if myDialog.ilk = #instance then myDialog.Destroy() myDialog = VOID end on OpenSettingsDialog(me) myDialog = script ("MUI" ).new() -- Initialise the window windowProps = myDialog.GetWindowPropList() windowProps.type = #normal windowProps.name = "Dialog 2" windowProps.mode = #data windowProps.width = 0 windowProps.height = 0 windowProps.callback = "Dialog_Callback" windowProps[#Target] = me windowProps.modal = 0 windowProps.closeBox = 0 windowProps.tooltips = 0 windowProps.xPosition = -1 windowProps.yPosition = -1 -- now starting adding widgets -- empty list for widgets: dialogWidgets =  -- default widget prop list: defaultWidget = myDialog.GetItemPropList() -- windowBegin: widget = defaultWidget.duplicate() widget.type = #WindowBegin dialogWidgets.add(widget)
Here is a demo movie (D11) showing two behaviours each of which will create its own non-modal MUI window. each MUI window will make callbacks to the relevant behaviour.
How this script works.
In Lingo, you can set any sort of object to be the ancestor of another object (thereby allowing us to extend the built in 'base classes' - such as images, lists and Xtra instances). The first thing to note about this MUI script is that the script sets an instance of the MUI Xtra to be its ancestor:
property ancestor on new (me) me.ancestor = xtra("MUI").new() return me end
By doing this, objects created from the script will inherit all the methods of the MUI Xtra. For example, you could call the 'FileOpen' method of the Xtra like this
x = script("MUI").new() put x.FileOpen("Foo")
Since the script doesn't have a 'FileOpen' method, the method of the ancestor is used.
In 'Lingo Object Orientated' thinking, an object created from our MUI script is a special version of the ancestor object. We could also say that the MUI Script extends the MUI Xtra.
The next thing to the MUI script does is over-ride the initialize method of the xtra. This means it has a method with the same name as a method in the ancestor, and this method is used to respond to the 'initialize' message:
on initialize (me, plist) -- override the xtra method by grabbing the callback and target -- properties and initialsing the Xtra me.Callback = pList.windowPropList[#Callback] if pList.windowPropList[#Target].ilk = #instance then me.Target = pList.windowPropList[#Target] pList.deleteProp(#Target) end if pList.windowPropList[#Callback] = script("MUI").__BindCallback(me) ancestor.initialize(plist) end
What happens here is the additional property we want (the 'target') is extracted from the parameter list and stored in our script before we pass the list on to the Xtra (which will ignore this property). We also keep a copy of the callback handler, replacing it with a special customer callback we will create using the __BindCallback method (this is where things start getting a little fruity).
Now, the MUI Xtra will only send callbacks to the global namespace meaning you have to create a unique callback function in a movie script for each MUI dialog or window you might create. In order to catch this callback and re-direct to an object. the MUI script will create a moviescript on the fly with a unique function name. So the first step is create a movie script (if one doesn't already exist)
-- check if we need to make the temp movie script tmp = member("MUI.Proxy.TEMP") if tmp.ilk <> #member then tmp = new(#Script) tmp.scriptType = #movie tmp.name = "MUI.Proxy.TEMP" end if
Next step is create a unique ID for the instance and set the scriptText of the temporary movie script member. To get an unique id, we coerce the instance into a string and grab the 4th word (which is unique for each object instance).
-- create a unique identifier for the object wanting to be bound Nonce = this._GetUniqueIdFromInstance(obj) ... CallBackHandler = "MUIWrapper_CallbackProxy_"&Nonce -- make the temporary script txt = "" txt = txt & "on " & CallBackHandler & "( event, widgetNumber, widgetProps )" & return txt = txt & " Target = script(""E&"MUI""E&").__GETMUIInstanceTarget("& QUOTE & Nonce & QUOTE & ")" & return txt = txt & "Target._HandleCallback( event, widgetNumber, widgetProps )" & return txt = txt & "end" & return tmp.scriptText = txt
And finally, we stash a reference to the script object created from the temporary member. So long as the reference is kept, the script object is kept alive. This way, we can created lots of temporary script objects from the same cast member.
-- create a reference to the script object (this will keep -- the script object 'alive' even if the script cast member -- is changed or deleted tmpObj = script("MUI.Proxy.TEMP") this.__CalllbackMap.addProp(Nonce, [#movieScriptObj: tmpObj, #CallbackTarget: obj])
The end result of all this is we have now created a temporary movie script object with this script that will look like this:
on MUIWrapper_CallbackProxy_fb52ac0( event, widgetNumber, widgetProps ) Target = script("MUI").__GETMUIInstanceTarget("fb52ac0") Target._HandleCallback( event, widgetNumber, widgetProps ) end
Note that the __BindCallback method (which is creating the moviescript to handle the callback) is a method of the MUI Script object rather than an instance of the script (ie. it is called using
script("MUI").__BindCallback(...)). See this short note about the differences between script objects and instance objects. Therefore, to get the 'CallbackMap' (which pairs IDs with objects to send callbacks to), the temporary function queries the script object rather than any particular instance of it).