Passing scroll events from child repeater to parent canvas

Hi, I'm trying to create a vertical scrollable template canvas in ignition (vision module) that contains multiple instances of a certain custom template called collapsablePanel. this Panel contains a template repeater. When the mouse pointer is on top of the repeater, it blocks the user from being able to scroll on the canvas. I'm trying to figure out how to pass the scrolling from the repeater on to the canvas. Below you can find the code I cooked up trying to pass the scroll events, but it doesn't work as expected. To show the hierarchy, I represented them with some variables. If you are wondering what the reason is behind all the "else None"s, it is sometimes possible that the hierarchy of the components isn't loaded yet during runtime.

from java.awt.event import MouseWheelEvent, MouseWheelListener

# Get the Template Repeater component
templateRepeater = event.source

# Traverse the component hierarchy safely
collapsablePanelTemplate = templateRepeater.getParent() if templateRepeater else None
layoutPanel = collapsablePanelTemplate .getParent() if collapsablePanelTemplate else None
jViewport = layoutPanel.getParent() if layoutPanel else None
templateCanvas1 = jViewport.getParent() if jViewport else None
templateCanvas = templateCanvas1.getParent() if templateCanvas1 else None

# Check if the target component (templateCanvas) is not None
if templateCanvas is None:
    print("templateCanvas is None. Component hierarchy is not intact.")
else:
    print("templateCanvas found, proceeding with listener registration.")
    
    # Define a custom MouseWheelListener to forward the scroll events to the templateCanvas
    class CustomMouseWheelListener(MouseWheelListener):
        def mouseWheelMoved(self, e):
            print("Mouse wheel moved event captured.")  # Debug line to confirm event capture
            
            # Create a new MouseWheelEvent for the correct parent component (templateCanvas)
            newEvent = MouseWheelEvent(
                templateCanvas,  # Forward to the templateCanvas
                e.getID(), 
                e.getWhen(), 
                e.getModifiers(), 
                e.getX(), 
                e.getY(), 
                e.getClickCount(), 
                e.isPopupTrigger(), 
                e.getScrollType(), 
                e.getScrollAmount(), 
                e.getWheelRotation()
            )
            print("Dispatching new mouse wheel event to templateCanvas.")  # Debug line to confirm event dispatch
            
            # Dispatch the new event to the templateCanvas
            templateCanvas.dispatchEvent(newEvent)

    # Remove any existing MouseWheelListener (to avoid duplicates)
    for listener in templateRepeater.getMouseWheelListeners():
        templateRepeater.removeMouseWheelListener(listener)
        print("Removed an existing MouseWheelListener from templateRepeater.")  # Debug line to confirm listener removal

    # Add the custom MouseWheelListener to the Template Repeater
    templateRepeater.addMouseWheelListener(CustomMouseWheelListener())
    print("Added custom MouseWheelListener to templateRepeater.")  # Debug line to confirm listener addition

Any help would be greatly appreciated, Thanks!
Kevin

You don't want to be able to scroll the internal repeater? I don't feel like passing a mouse event is needed here. I would look at either disabling the scrollbar on the internal repeater or removing the repeater's mouse listener at initialization using the componentRunning event handler.

I previously attempted to disable the scrollbar with the following code:

from javax.swing import JScrollPane
templateRepeater = event.source
scrollPane = templateRepeater.getComponent(1)
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER)

However, this didn’t resolve my issue. The main problem isn’t the scrolling within the repeater itself. The templates inside the repeater don’t occupy the full space provided by the container, so a scrollbar doesn’t appear.

The real issue is that the area of the repeater prevents users from scrolling through the canvas. When the mouse pointer hovers over the repeater, scrolling stops. Users have to move their pointer below the repeater to continue scrolling on the canvas.

A fundamental design factor in java Swing is that events are consumed at the innermost component that has an appropriate handler. The template repeater naturally has a scroll handler, even when the scroll bar is disabled.

You will need to define a scroll wheel handler on your template repeater that "re-fires" the event on the outer component.

2 Likes

A possible alternate solution (no idea if it would work in the initialization order of things) would be to disable wheel event listening for the component entirely:
https://docs.oracle.com/javase/8/docs/api/java/awt/Component.html#disableEvents-long-

Where the mask you disable is composed of bitwise values from this list:
https://docs.oracle.com/javase/8/docs/api/constant-values.html#java.awt.AWTEvent.MOUSE_WHEEL_EVENT_MASK

1 Like

To play around with this, I set up a template canvas where one of the templates had a template repeater that didn't need scrolling. This script produced the desired result from the nested template repeater's propertyChange event handler:

# Written for the propertyChange event handler of the TEMPLATE REPEATER
# Won't run in designer unless preview mode is on prior to opening the window
if event.propertyName == 'componentRunning':
	
	# Nested function that gets all components of a given class within a given container
	# Recommend relocating this to a library script because it is quite handy
	def getAllComponentsOfClass(container, className):
		foundComponents = []
		for component in container.components:
			if component.__class__.__name__ == className:
				foundComponents.append(component)
			else:
				foundComponents.extend(getAllComponentsOfClass(component, 'JideScrollPane'))
		return foundComponents
	
	# Get all of the JideScrollPanes in the source repeater
	scrollPanes = getAllComponentsOfClass(event.source, 'JideScrollPane')
	
	# Iterate through the scroll panes and remove all mouse listeners and mouse wheel listeners,
	# ...so a parent canvas's scroll wheel event is not consumed by these inner components
	for scrollPane in scrollPanes:
		for listener in scrollPane.mouseWheelListeners:
			scrollPane.removeMouseWheelListener(listener)
		for listener in scrollPane.mouseListeners:
			scrollPane.remove(listener)

At initialization, it simply locates all of the JideScrollPanes within a given repeater and removes the listeners that can consume a mouse wheel event.

4 Likes