Need a Dropdown Component Trigger off of completed selection

I am looking for a bindable event for the dropdown component on a scripting function.

Current issue is that the GUI method of entry that I am pleased with is having a dropdown component trigger a script that will populate other components from a SQL query.

While I can bind it off a propertyChange script bound to a selectedID/selectedValue, these bindings evaluate immediately and trigger a SQL query with every change. A user that has a dropdown selected and holds down on the keyboard will trigger a great amount of requests.

My current musings on how to tackle the problem are to bind a timer component to the dropdown and only count when the mouse cursor is over the component and then reset back to 0 when the mouse moves off the dropdown, delaying any writes until the timer resets/goes to 0.

ā€¦but currently it seems a bit convoluted.

Iā€™ve tried using invokeLater and invokeAsynchronous to better control the process from activation to finish, but the problem always goes back to ā€œHow do I define when a user is done with this component?ā€

the dropdown component is my least favorite input component, however if I were to use one I think I would Place the scripting on the ā€˜Focus Gainedā€™ and ā€˜Focus Lostā€™ actions.

When do the queries actually need to execute?

Why not add a script that executes the queries once time when the window is opened? This can be done in the visionWindowOpened event on the window.

Sometimes the cleanest solution is a separate ā€œContinueā€ button that takes a page full of selections and moves the user to the next page, suitably filled in.

Aye, and thatā€™s my backup plan. The cleanliness of a dropdown component and itā€™s space saving properties are what keep me trying to make it work.:smiley:

They should only execute when the operator has ā€œfinishedā€ with the dropdown component. Normally this would be off of focusLost, but that trigger depends on another component stealing focus.

Iā€™ve achieved a level of functionality I am happy with looking for the ā€œEnterā€ key press but ideally if the focusLost triggered a bit differently it would suffice.

You can create a custom property that is updated after a selection is made.
Use the following:

from javax.swing.event import PopupMenuListener

class myPopUpMenuListener(PopupMenuListener):
	def __init__(self, myDropDown):
		self.dropDown = myDropDown
	def popupMenuWillBecomeInvisible(self, e):
		print self.dropDown.selectedValue, self.dropDown.selectedStringValue
		self.dropDown.myCustomProperty = self.dropDown.selectedValue

myDropDown = event.source.parent.getComponent('Dropdown')
dd.addPopupMenuListener(myPopUpMenuListener(myDropDown))
2 Likes

Thanks! Works great!

Thank you for your copious contributions. Pls go easy on me, I'm a novice programmer. I've tried adding your code as a "Custom Method" of the dropdown component. I was unable to figure out how to incorporate scripting with a "Custom Property". I added pseudo code comments to help me think through what is happening as follows (Am I even close?):

def DropdownSelectInvis(self):
	'''
	Some words about arguments, and not including self 
as a redundant argument
	'''
	
	#Import the PopupMenuListener java class in order to subclass it's features
	#for the dropdown list/box.
	from javax.swing.event import PopupMenuListener
	
	#Create python class with functions for handling initiation and to get 
	#invisibility property and set it to the invisible state.
	class myPopUpMenuListener(PopupMenuListener):
		def __init__(self, myDropDown):
			self.dropDown = myDropDown
		def popupMenuWillBecomeInvisible(self, e):
			print self.dropDown.selectedValue, self.dropDown.selectedStringValue
			self.dropDown.myCustomProperty = self.dropDown.selectedValue
	
	#pass the dropdown component to the method for adding it to the subclassed 
	#listener.
	myDropDown = event.source.parent.getComponent('Dropdown')
	dd.addPopupMenuListener(myPopUpMenuListener(myDropDown))

This works in the designer but not in my remotely deployed client. Can you help me understand how to understand/resolve this behavior? I feel like I still need to pipe/connect some things up to my actual components/properties...

Again, much appreciated.

Dan

Hi Sera,
Iā€™ve added a custom method called ā€œdropFocusā€ on the dropdown in question, and placed the above code in itā€¦
Did you put the code from jpark somewhere else?

Iā€™m not seeing the values printed to the console indicating that the second function within the class is not being called.

Iā€™ve created a custom property for the dropdown in question called "myCustomProperty " to see if it gets populated with
the selectedValue string value, but it doesnā€™t when I change the selected value.

Did you bind the ā€œmyCustomPropertyā€ on the dropdown component somehow?

What is the ā€œeā€ argument that is passed to the popupMenuWillBecomeInvisible function?

Waitā€¦I think you guys are leveraging off the code already in the focusLost methodā€¦updating the custom property forces focus to be lost then??? Giving it a shot. Sorry for the detour.

Your core issue is probably that youā€™re adding this as a custom method.
Custom methods are just functions available from the component; theyā€™re not necessarily being called unless you specifically use them in the script. You could add Jaeā€™s code to the propertyChange event handler, and have it run once (note: will only work in a client) using this:

if event.propertyName == 'componentRunning':
	from javax.swing.event import PopupMenuListener

	class myPopUpMenuListener(PopupMenuListener):
		def __init__(self, myDropDown):
			self.dropDown = myDropDown
		def popupMenuWillBecomeInvisible(self, e):
			print self.dropDown.selectedValue, self.dropDown.selectedStringValue
			self.dropDown.myCustomProperty = self.dropDown.selectedValue

	myDropDown = event.source.parent.getComponent('Dropdown')
	dd.addPopupMenuListener(myPopUpMenuListener(myDropDown))

The ā€˜eā€™ argument of popupMenuWillBecomeInvisible is the ā€˜eventā€™ object thatā€™s automatically passed into the function by Swing when the event happens.

1 Like

Hi Paul. Thx for your reply.

Iā€™ve tried placing jaeā€™s ā€œnakedā€ class in the existing focusLost method (testing my theory that the focusLost event handler has something built-in that jae was leveraging), and commented out the code in the custom method
I created. (maybe i need to delete it altogether?) but received a name error pointing to ā€œddā€ (the last line of code of the class in question) I assumed this was ā€œbuilt-inā€ to the listener but I havenā€™t read the docs yet, despite my better judgement telling me I should.

The same name error is raised when I deploy the class in the propertyChange method from the client. Curiously, the designer client is behaving as desired (dropdown is retracting when an item is selected) in preview mode-just as it was when the class was placed in the custom method I added. (Thinking I need to check that behavior with all scripting removed now.)

However, if I open or close the window containing the dropdown box while in preview mode, the name error is thrown, indicating at least that the property change event filtered for ā€œcomponentRunningā€ is workingā€¦however, Iā€™ve not seen the console receive any printed values nor has the custom property I created ā€œmyCustomPropertyā€ been written to with the selectedValue as the function suggests it might.

Thanks for coming along the ride Paul.

Name Error as follows:

  File "<event:propertyChange>", line 12, in <module>
NameError: name 'dd' is not defined

	at org.python.core.Py.NameError(Py.java:260)
	at org.python.core.PyFrame.getname(PyFrame.java:257)
	at org.python.pycode._pyx43.f$0(<event:propertyChange>:12)
	at org.python.pycode._pyx43.call_function(<event:propertyChange>)
	at org.python.core.PyTableCode.call(PyTableCode.java:165)
	at org.python.core.PyCode.call(PyCode.java:18)
	at org.python.core.Py.runCode(Py.java:1275)
	at com.inductiveautomation.ignition.common.script.ScriptManager.runCode(ScriptManager.java:626)
	at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.runActions(ActionAdapter.java:168)
	at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.access$000(ActionAdapter.java:40)
	at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter$ActionRunner.run(ActionAdapter.java:280)
	at java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.awt.EventQueue.access$500(Unknown Source)
	at java.awt.EventQueue$3.run(Unknown Source)
	at java.awt.EventQueue$3.run(Unknown Source)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.run(Unknown Source)

Ignition v7.8.4 (b2016082217)
Java: Oracle Corporation 1.8.0_131

Try replacing the DD with a reference to the DropDown Object.

Let me see if I can find an example.

#Pull a Reference to the DropDown Object you want to 
YourOwnDD = event.source.parent.getComponent('dd_Filter')


#Then branch from there
#If you call from an event generator on the dropdown
event.source.addPopupMenuListener(myPopUpMenuListener(YourOwnDD))

#If you call from anywhere else.
YourOwnDD.addPopupMenuListener(myPopUpMenuListener(YourOwnDD))

#If you call from a a custom method.
self.addPopupMenuListener(myPopUpMenuListener(YourOwnDD))
if event.propertyName == 'componentRunning':
	from javax.swing.event import PopupMenuListener

	class myPopUpMenuListener(PopupMenuListener):
		def __init__(self, myDropDown):
			self.dropDown = myDropDown
		def popupMenuWillBecomeInvisible(self, e):
			print self.dropDown.selectedValue, self.dropDown.selectedStringValue
			self.dropDown.myCustomProperty = self.dropDown.selectedValue

	myDropDown = event.source
	myDropDown.addPopupMenuListener(myPopUpMenuListener(myDropDown))

Minor change; I hadnā€™t paid enough attention to what Jae was doing.

Breaking it down: Basically, once the component is initialized it will fire this code once.
This code creates a custom ā€˜PopupMenuListenerā€™ (a default Java object) that fires the custom popupMenuWillBecomeInvisible function that sets the custom property value. It holds a reference back to the original dropdown as the second argument to the __init__ function.

1 Like

Iā€™m remiss in not mentioning that I have the dropdown in ā€œTableā€ display mode (vs. ā€œListā€). Since this seems like it might be a low level event or item listener issue, might this be a factor? (this is me flexing some new knowledge from reading some of the docsā€¦Iā€™m not usually this java lingo savy.)

The most recent code posted should work fine with the list or dropdown renderers.

Thanks Seraā€¦your initial comment lead me to look at the ā€œpopupTriggerā€ listed as an event object property under the mouseReleased event handlerā€¦Iā€™ll explore your suggestion shortly, but Paul is bring some code suggestions too. Let me see what heā€™s doing.

Hi Paul. Tried the updated code in the propertyChange event handler. Still no dice getting the table to disappear once an item is selected. However, Iā€™ve changed the display mode to ā€œListā€ from ā€œTableā€ and the list now disappears when an item is selected, which means users will need to know which city is associated with a particular airport codeā€¦

Since the dropdown (JComboBox) is a combination of many components, perhaps the invisible property of the embedded table should be the target of our bit flipping adventure?

awesome. Thanks Sera. Appreciate your input.

I was able to accomplish what I believe you were wanting with this script:
if event.newValue != event.oldValue:
if event.propertyName == ā€œselectedStringValueā€:
ā€¦ (execute your function here)

2 Likes