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)

Screencapture

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 custom 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).

Last updated 26th April, 2008

© 2006 MeccaMedialight. Site Powered by Wrangler 8.