OK, thanks to Mauricio for pointing me in the right direction to get this figured out.
The confusing part was that the events assigned to a form control object are not (as one would presume) contained within the control itself, rather its parent, the form object.
Perhaps in the future the control object will have additional properties and methods for viewing, getting and setting events associated with it.
While Python is a very popular way to run macros in Libre Office, this discussion uses Basic. If Python’s your bag, it shouldn’t be that hard to refactor.
Assuming you have a form object “oForm” with 5 controls, and a push button control whose index in the list of form controls is “3” to which you wish to add or update its “mousePressed” event:
oControl = oForm.getByIndex(3) gives you the button object.
oControl.Name is the name of the button (useful for determining which control to work with)
aControlEvents = oForm.getScriptEvents(3) will give you an array of all the events associated with the button, one element of which may or may not have an .EventMethod property set to “mousePressed”
So, like Mauricio said, getScriptEvents is how we can get a control’s events. But what about setting?
It can vary depending on which type of control you’re working with, as they support different events.
Here we’ll again assume a push button, and use a couple of iterators. “i” is the index to the form’s control, and “j” is the index to the array of the control’s event(s).
- oControl = oForm.getByIndex(i) ’ The control
- aControlEvents = oForm.getScriptEvents(i) ’ The array of events associated with the control
An array entry in aControlEvents is a ScriptEventDescriptor type, containing five string entries:
Create the descriptor: descriptor = New com.sun.star.script.ScriptEventDescriptor
To add an event, you call oForm.registerScriptEvent(i, descriptor)
So far as how to populate the descriptor fields:
For .EventMethod and .ListenerType, a pushbutton has 15 possible events. In design mode, examine a pushbutton’s control properties, and look at the events tab. Match the label of the event you wish to affect to the .EventMethod shown below set and populate the two fields accordingly:
- .EventMethod → .ListenerType
- mousePressed → XMouseListener
- resetted → XResetListener
- approveReset → XResetListener
- approveAction → XApproveActionListener
- mouseMoved → XMouseMotionListener
- mouseDragged → XMouseMotionListener
- mouseEntered → XMouseListener
- mouseReleased → XMouseListener
- keyReleased → XKeyListener
- focusLost → XFocusListener
- keyPressed → XKeyListener
- mouseExited → XMouseListener
- itemStateChanged → XItemListener
- focusGained → XFocusListener
- actionPerformed → XActionListener
(Alternatively, examine xmloff/source/forms/formevents.cxx in the Libre source to see the available methods and listeners.)
Set .ScriptCode to the subroutine or method in your code you want to run, i.e.,
(If the routine is in a different module, or you’ve renamed “Module1”, adjust as needed.)
.Script is just the string “Script”, and .AddListenerParam is usually an empty string (unless you need to pass parameters to the routine, beyond the scope of this discussion).
Unfortunately, the form object references event routines by index, not name, and for some reason index is not a property for a form control, so you can’t use oForm.getByName(“Your Control Name”) and expect to be able to work with the events for that control.
Instead, you have to iterate all the controls with oForm.getByIndex and then look for the one whose Name property matches the one you’re looking for, giving you the desired index, or value for “i”.
You also have to make sure the event method isn’t already declared, so once you have “i”, you use oForm.getScriptEvents(i) to get an array of all events defined for that control, with each array element being a descriptor as described above.
If the upper bounds of the array returned by oForm.getScriptEvents(i) is less than zero, there are currently no events associated with the control, so you can simply use oForm.registerScriptEvent(i, descriptor).
If there are events, though, you need to iterate through them (using “j” from above), looking for the one whose .EventMethod property matches whatever action you’re trying to affect (mousePressed, keyPressed, etc.)
If you do find a match, you can’t just update the .ScriptCode property. You need to remove, then re-add the event.
Removal is a little silly. You’d think you could just reference i and j, but no, the removal method needs a lot more: form.revokeScriptEvent(i, events(j).ListenerType, events(j).EventMethod, events(j).AddListenerParam)
Not sure why it needs all that. Seems “i” and “j” should have been enough, unless, again, different controls act differently, but I can’t quite see how you could have multiple routines assigned to the same .EventMethod
Once it’s gone, just add it back in with oForm.registerScriptEvent(i, descriptor), then exit the iterator as there’s no need to examine the other events.
After leaving the events iterator, you should still check to make sure you actually updated one. Inside your “j” iterator, use a boolean that gets set to true if you found an event to update and done the revoke / register. After the iterator, if the boolean is still false, that means there was no event associated to whatever .EventMethod you were trying to assign, so you simply do oForm.registerScriptEvent(i, descriptor)
Whew! Now you have programmatically assigned a routine to a control based on its event method.
It would be super nice if this was a little more streamlined, first by giving the control itself access to its events rather than having to go through the form object. The register method is pretty straightforward, but revoking seems to require unneccesary parameters. You should also be able to update an event, rather than having to destroy and then re-add it.
Lastly, the descriptor seems to have redundant information as well. One would think you could get away with just having .EventMethod rather than also needing to set .ListenerType. Since the combinations are all unique, the register method should be able to figure out which listener type to use based on the event method. (Perhaps with other controls this is not the case. Again, we’re only talking about pushbuttons here.)
So, adding some methods to a form control object, such as:
- oControl.getEvent([in] string descriptor.EventMethod)
- oControl.SetEvent([in] struct descriptor)
- oControl.SetEvents([in] array[struct] aDescriptorArray)
- oControl.RemoveEvent([in] struct descriptor)
would IMHO make working with them a lot easier. SetEvent and SetEvents would do double-duty, first ensuring there was not already an event matching the descriptor’s .EventMethod, and either update or insert as needed. All the info it would need would be in the descriptor.
Thanks again for the clue, Mauricio!