Dropdown isOpen

I have a dropdown that partially overlaps another component that can also be clicked on. I would like to disable the component below the dropdown box if the dropdown box is open.

Just off-hand I think I could achieve this with the onClick and focusLost extension functions on the dropdown, but is there a way to just check if the dropdown is "open"?

edit: focus is not lost when the clicking off of the component. I was thinking that would work for the case where the user clicks any space outside of the dropdown box to close the dropdown.

PMIComboBox extends JComboBox, which defines an isPopupVisible() method:

So, yes - either reference.isPopupVisible() or reference.popupVisible.

Tried this on propertyChange, but that didn't work (didn't really expect it to, since popupVisible is not bindable). Where would I implement this?

if event.propertyName == 'popupVisible':
	if event.newValue:
		en = False
	else:
		en = True

I could do it on propertyChange without checking the property name, but that is not really good practice.

I'll just us a timer's propertyChange script on the value to check it every 500 ms.

if event.propertyName == 'value':
	dropdown = event.source.parent.getComponent('Dropdown')
	if dropdown.popupVisible:
		en = False
	else:
		en = True
		
	...

Better would be to create and add a custom PopupMenuListener:

For example:

3 Likes

Listeners are new to me, I tried this:

if event.propertyName == 'componentRunning':
	from javax.swing.event import PopupMenuListener
	class PopupCloseListener(PopupMenuListener):
		en = True
		def popupMenuCanceled(self, subEvent, en):
			en = True
		
		def popupMenuWillBecomeVisible(self, subEvent, en):
			en = False
		
		def popupMenuWillBecomeInvisible(self, subEvent, en):
			en = True
			
		event.source.parent.getComponent('Switch 2POS Buttons').enabled = en
	
	event.source.addPopupMenuListener(PopupCloseListener())

Did not produce the desired results

Try this:

if event.propertyName == 'componentRunning':
   from javax.swing.event import PopupMenuListener
   class PopupCloseListener(PopupMenuListener):
      def __init__(self, target):
         self.target = target
      def popupMenuCanceled(self, event):
         self.target.enabled = True
      
      def popupMenuWillBecomeVisible(self, event):
         self.target.enabled = False
      
      def popupMenuWillBecomeInvisible(self, event):
         self.target.enabled = True
   
   listener = PopupCloseListener(event.source.parent.getComponent('Switch 2POS Buttons'))
   event.source.addPopupMenuListener(listener)

Switch 2POS Buttons is not the dropdown, it is the component that I want to disable when the dropdown is open and re-enable when it is closed

Ewww! import, class, and def directly in an event script. :frowning_face:

Ya, would probably be better to pass the listener the event and a list of properties to set to false and make this a more generic library script.

2 Likes

Yeah, you're sending it to the custom listener as the target.

Yeah, definitely store the class definition in a project library script or something. Just trying to demonstrate the concept.

Here's the same example but commented to explain what's going on in more detail:

if event.propertyName == 'componentRunning':

   from javax.swing.event import PopupMenuListener

   # define a class that extends from the Java interface we care about
   class PopupCloseListener(PopupMenuListener):
      # give this class a custom constructor that takes one argument, 'target'
      def __init__(self, target):
         # store the target parameter we get on construction into a class-internal field, `self.target`
         self.target = target
      
      # to override a Java interface method, you must match the signature of the Java method exactly
      # (plus the `self` parameter, because Python)
      # Java has strict overloading, so adding parameters and stuff doesn't work - this is why your first
      # example didn't work
      def popupMenuCanceled(self, event):
         # this will be called automatically because we're providing a listener
         # so when the popup menu is canceled, we modify the enabled property of our self.target field
         self.target.enabled = True
      
      def popupMenuWillBecomeVisible(self, event):
         self.target.enabled = False
      
      def popupMenuWillBecomeInvisible(self, event):
         self.target.enabled = True
   
   # construct an instance of our listener class, passing in our 'target' component as a reference
   # this is what will get stored in the 'target' field inside the class
   listener = PopupCloseListener(event.source.parent.getComponent('Switch 2POS Buttons'))
   # then add our instance to the actual class we care about
   event.source.addPopupMenuListener(listener)

It's making a little more sense.

If I wanted to make this generic and have a dropdownDisableOtherComponent() library script like

def dropdownDisableOtherComponent(componentProp):
	'''
		Args
			componentProp	:	componentProperty to toggle True/False
	'''

How would I just pass the componentProp to the listener, instead of the entire component? That way I could even use this for visibility, etc.

You would change the listener definition (it would really only work "well" with binary values).

if event.propertyName == 'componentRunning':
   from javax.swing.event import PopupMenuListener
   class PopupCloseListener(PopupMenuListener):
      def __init__(self, target,attrs):
         self.target = target
         self.attrs = attrs
      def popupMenuCanceled(self, event):
         for attr in attrs:
              setattr(self.target,attr,True)
      
      def popupMenuWillBecomeVisible(self, event):
         for attr in attrs:
              setattr(self.target,attr,False)
      
      def popupMenuWillBecomeInvisible(self, event):
         for attr in attrs:
              setattr(self.target,attr,True)
   
   listener = PopupCloseListener(event.source.parent.getComponent('Switch 2POS Buttons'),["Enabled","Visible"])
   event.source.addPopupMenuListener(listener)

yep, binary is what I want. It can be transformed after the fact if needed, but I just need it for enable/disable for now.

I'll give this a try.

My example was for multiple properties in a single component, you cans modify it appropriately for multiple components single attribute, or multiple of both.

You could go fully functional - accept a function during construction of your listener. Call that function inside the listener with true or false to represent the state of the boolean.
Where you actually use the listener, also define the callback to modify the enabled, visible, other properties. Slightly more brain breaking, but a lot more flexible.

3 Likes

Something like this, to get maximally silly with it:

# in project library
class PopupMenuAdapter(PopupMenuListener):
    def __init__(self, callback):
        self.callback = callback

    def popupMenuCanceled(self, event):
        self.callback(event.source.popupVisible)

    def popupMenuWillBecomeVisible(self, event):
        self.callback(event.source.popupVisible)

    def popupMenuWillBecomeInvisible(self, event):
        self.callback(event.source.popupVisible)

##############################

# in component event
if event.propertyName == 'componentRunning':
    # define the function to execute when the popup menu is opened or closed
    # the popupVisible argument will reflect the current state and can be used to drive other logic
    def callback(popupVisible):
        # we can access event.source here because the callback is defined in the same scope
        event.source.parent.getComponent('Switch 2POS Buttons').visible = not popupVisible
    
    listener = PopupMenuAdapter(callback)
    event.source.addPopupMenuListener(listener)
1 Like

I know I've written this a 100 times in the forum, but it's something I need to go back through and correct. The componentRunning property is a bool, and it's propertyChange event fires twice during the normal life cycle of the component. Once at initialization when the property becomes True and once at window close when the property becomes False.

For this reason, I now write it this way:

# Only occurs once at initialization
if event.propertyName == 'componentRunning' and event.newValue:

If the initialization script schedules anything with invokeLater or makes any asynchronous calls, null pointer exceptions and other unexpected behaviors can occur. Overall, I'd say that 99% of the time it probably doesn't matter if an initialization script runs at window close, but it's still an unnecessary inefficiency that should be corrected.

6 Likes

I would assume I need to somehow pass a list of components and a list of lists of properties (one list for each component), but I'm not quite sure how to make that change with the self.target and self.attrs

Something like this?

if event.propertyName == 'componentRunning' and event.newValue:
   from javax.swing.event import PopupMenuListener
   class PopupCloseListener(PopupMenuListener):
      def __init__(self, target,attrs):
         self.target = target
         self.attrs = attrs
      def popupMenuCanceled(self, event):
         for i in range(0,len(target)):
            for attr in attrs:
                setattr(self.target[i],attr,True)
      
      def popupMenuWillBecomeVisible(self, event):
         for i in range(0,len(target)):
            for attr in attrs:
                setattr(self.target[i],attr,False)
      
      def popupMenuWillBecomeInvisible(self, event):
         for i in range(0,len(target)):
            for attr in attrs:
                setattr(self.target[i],attr,True)
   
   listener = PopupCloseListener([event.source.parent.getComponent('Switch 2POS Buttons')],[["Enabled","Visible"]])
   event.source.addPopupMenuListener(listener)

(still just doing this in the component script for now, until I get it working)