I have created a button template in Vision that differs from the normal button. It is a rectangle with a label on top.
I have used this button template and assigned a mouseClick event to the template but this event is not being handled at the top level.
I see that from other posts it is because due to Java Swing the innermost component handles the event, in this case, the rectangle. So, that is consuming the event even though that event handler is empty.
Is there anyway to stop this? I can’t find a definitive answer on the forums.
No, there has to be no handler at all on an inner component for Swing to pass it outward. Some components add their own event handlers into their stack, and consume events even if you don't add one deliberately.
The closest you can come is to place a deliberate event handler that that re-fires the event on parent component. You will need to carefully construct that new event to get coordinates and source object information correct.
Ok, I understand. It seems a rather convoluted approach when you want to create templates. I guess, if it is a restriction of Swing then there isn’t much Ignition can do about it.
It would be nice if you could remove unwanted event handlers for child components instead of being forced to have them.
Another question, how would I pass this event to the parent? Should I pass the parent in as a template property? I can’t see how I can do this. The template is going to be used in multiple windows so it can’t cater for all of these.
Alternatively, if this is not possible, would it be possible to remove the unwanted event handlers from the lower components to force Swing to apply the event outward?
from java.awt.event import MouseEvent
from com.inductiveautomation.ignition.client.util.gui import IgnitionSwingUtilities
from com.inductiveautomation.vision.api.client.components.model import TopLevelContainer
def mouseEventToTop(event,startSource = None):
"""Sends a mouse event to the top-level component in a template
This is used for embedded templates and object that have mouse over text. If an object has a mouse over text, then the moues event is bound to that object.
So if the user clicks on that object, then there is no mouse even associated.
This allows a single main mouseEvent script on the template root (typically) and then this function is called in the sub objects.
Args:
event (java.awt.event.MouseEvent): Mouse event to pass along
startSource (object,optional): The starting source object to start traversing from. If this is a template embedded in another template, it might be required
to get the outer, or parent, template rather than the child template. So this would be the child template. This isn't typical.
Returns:
Nothing
"""
relX = 0
relY = 0
try:
#If no start source is provided, then set the startsource to the event.source
startSource = event.source if startSource is None else startSource
parent = IgnitionSwingUtilities.getAncestorOfClass(TopLevelContainer, startSource.parent)
while parent == startSource:
#Offset the relativeX and Y to this object
relX += startSource.parent.x
relY += startSource.parent.y
#Traverse up starting at this startSource parent
parent = IgnitionSwingUtilities.getAncestorOfClass(TopLevelContainer,startSource.parent)
#Set the x and y for the event
evtX = relX + event.x + event.source.x
evtY = relY + event.y + event.source.y
#Create the MouseEvent
evt = MouseEvent(
parent,
event.getID(),
event.getWhen(),
event.getModifiers(),
evtX,
evtY,
event.clickCount,
event.popupTrigger,
event.button
)
#Send the mouse event up to the the parent that was found
parent.dispatchEvent(evt)
Edit - FYI there is a try but missing the hanging except. We have custom code in there to handle exceptions, so you will need to add that part yourself.
Understand that anything that has a tooltip text, an event assigned etc... is considered an inner mouse event.
We have the events we want on the main outer template. And then on the inner objects on the event we want to pass up, say mouse released, we call that script like this:
For another approach, you can lean on our built in EventDelegateDispatchers class to automatically forward events (of certain types/all types) from a child component to a parent component provided by reference, i.e.
from com.inductiveautomation.ignition.client.util.gui import IgnitionSwingUtilities
from com.inductiveautomation.vision.api.client.components.model import TopLevelContainer
from com.inductiveautomation.factorypmi.application.components.util import EventDelegateDispatcher
KEY_EVENT_MASK = EventDelegateDispatcher.KEY_EVENT_MASK
MOUSE_EVENT_MASK = EventDelegateDispatcher.MOUSE_EVENT_MASK
FOCUS_EVENT_MASK = EventDelegateDispatcher.FOCUS_EVENT_MASK
ALL_EVENTS_MASK = EventDelegateDispatcher.ALL_EVENTS_MASK
def addForwardingEventListener(event, startSource = None, mask = ALL_EVENTS_MASK):
startSource = event.source if startSource is None else startSource
parent = IgnitionSwingUtilities.getAncestorOfClass(TopLevelContainer, startSource.parent)
EventDelegateDispatcher.initializeDispatchers(startSource, parent, mask)
The class internally does the event cloning + adjusts cursor points as necessary.
For a smaller refactor, you could also probably use something from Java's SwingUtilities to ease the mouse event translation, e.g: