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

First published 26/04/2008