Dropdown List Events

I'm trying to setup a dropdown list that will update another dropdown list upon the selection in the first one. For my initial testing, I simply setup a dropdown with a few items and added a propertyChange event listener, as below:

if event.propertyName == "selectedStringValue":
	print "Selected value: " + event.source.getSelectedStringValue()

This works as I would expect it to when using the mouse, firing after you click an option in the list. However, when using the keyboard, it fires every single time you go up or down in the list. I only want this to fire when the selection is finalized with the Enter/Return key. Having it run every time a new row is passed through on your way to what you actually want to select seems horribly inefficient. Is there any way around this?

Does the dropdown list implement onActionPerformed? I'm pretty sure it does, and also pretty sure it will only fire when a selection is fully confirmed, but not at a computer where I can readily check.

Nope. I would have used it if it did. I'm still trying to figure out why it doesn't, honestly. I know Java dropdowns (ComboBox) have it, which I thought these were based on...


I even checked to see if any other event types fire when you hit enter after reaching your desired selection and did not see a single one fire! They fire when moving your selection with the arrow keys, but not a single event when pressing "Enter". Correction: Not a single propertyChange event... key events do fire (now that I actually tested it...)

So it looks like the only way to do what I want, currently, is to put the same code into keyReleased (for enter/space keyboard selection) and propertyChange (for mouse selection) while filtering out the unwanted events/sources...

I recommend either setting up a custom method or a library script to avoid redundancy.

Example:
image
image

Then on your propertyChange event handler, call the method anytime a selection change closes the dropdown [mouse selection]

# handle slection changes that are triggered by the mouse
if event.propertyName == 'selectedStringValue' and not event.source.popupVisible:
	event.source.filterNextDropdown()

For the key released event handler the code would look something like this:

if event.keyCode == event.VK_ENTER or event.keyCode == event.VK_SPACE:
	event.source.filterNextDropdown()

...and it's probable that you should also consider focusLost for when people click away.

...or another option would be to simply add a custom listener to detect when the dropdown menu closes, and perform the filtering actions directly from there.
Example:

# Written for the propertyChange event handler of a dropdown component

# Only runs one time at initialization
# Won't run in the designer unless preview mode is active prior to the window being opened
if event.propertyName == 'componentRunning':
	from javax.swing.event import PopupMenuListener
	class PopupCloseListener(PopupMenuListener):
		def popupMenuCanceled(self, event):
			pass  # No action needed for this event
		
		def popupMenuWillBecomeVisible(self, event):
			pass  # No action needed for this event
		
		def popupMenuWillBecomeInvisible(self, event):
			print event.source.selectedStringValue
			# perform the filtering actions here
	
	event.source.addPopupMenuListener(PopupCloseListener())
2 Likes

Always. But I still need to do my event filtering inside of the property change events before calling said method.

Now this looks interesting. I'll have to give this a try and see how it goes. I keep forgetting you can work with the underlying Java. Thank you!

1 Like

Quick little note on this... rename your "event" parameter within the new popupListeners to "subEvent" to make life easier if you're trying to call back to the original event. Not doing so makes calls like event.source.filterNextDropdown() throw the error below:

Exception in thread "AWT-EventQueue-0" Traceback (most recent call last):
  File "<event:propertyChange>", line 13, in popupMenuWillBecomeInvisible
AttributeError: 'com.inductiveautomation.factorypmi.application.com' object has no attribute filterNextDropdown()

So change:
def popupMenuWillBecomeInvisible(self, event):
to:
def popupMenuWillBecomeInvisible(self, subEvent):

and then the call will stop throwing the error.

Just for completeness, what you're experiencing here is the result of the dropdown not having its PyComponentWrapper when it's retrieved from inside the listener. The PyComponentWrapper is where all of the custom properties and custom methods live, and I've found that there are quite a few scripting scenarios that can cause a component to loose its wrapper.

In this case, I would say that the way you have approached the problem is correct, but if you needed to, you could also put the wrapper back on the component and call its custom method in this way:

# Import the PyComponentWrapper class
from com.inductiveautomation.factorypmi.application.script import PyComponentWrapper

# Rewrap the component, and call its custom method or property
PyComponentWrapper(subEvent.source).filterNextDropdown()

There is also a module that Phil Turmel made called Integration Toolkit, that patches the underlying cause of the PyComponent wrapper being lost in Ignition, so this problem never occurs in the first place.

Edit: changed event to subEvent per the OP's post for clarity

1 Like

I think your listener is just shadowing the event param delivered by the outer scope inside your listener function implementations.

1 Like

FWIW, I did test the code before I posted, just to be sure. Rewrapping the component allowed the custom method to be called directly from the listener's event.

Interesting. Was not aware of the ability to rewrap the event.

All that was clear was that there were 2 different variables called "event" and the code was simply accessing the last one to be initialized, where I was accessing it. I'm still learning python/jython, so I wasn't entirely sure if the variable was being overwritten or just a new instance was declared in the child and I had to somehow drill up to get to the parent version (which I suspected and appears to be the case). Regardless, the simplest way to alleviate the issue was to simply rename the variable and perform the single method call I needed on the parent event. I can see cases where rewrapping it could be the preferred route to go, though, so thank you for adding that info!

The way your code is written, subEvent.source and event.source are the same thing, but subEvent.source will fail when calling a custom method because of the reason I listed above; the component loses its wrapper when referenced from the custom listener.

The most common place I encounter this issue is when I obtain a component using SwingUtilities.getAncestorOfClass instead of using a recursive function. See this forum post for reference:
Why can't custom properties and methods be accest from a component that is obtained using SwingUtilities.getAncestorOfClass

Agreed.

1 Like