Module Binding Configuration

Hello,

I am experiencing a strange issue with my component. I am inheriting from an abstract vision component. My module component has a BeanInfo counterpart. Everything is working as expected until now. My module component lives in a popup window, and I pass information from the root container to my module component perfectly. However, when I try to tie a native vision component to my custom component, the binding fires once and never changes again on the native component. In this case, I would like to control a native component's visibility property by binding it to one of my component's custom properties.

I have loggers and have attached the IntelliJ debugger to my designer. I can see my program triggering the property changes, but the native component doesn't continuously react to my component's property changes.

Are you referring to changing the visibility of the native component you are extending, or another component? If the latter, why are you doing that in your module? (That is a designer task.)

(You probably need to share enough of your code so we can see what you are doing.)

I want the visibility property of the native component to be bound to a property on my custom component. My component bindings have been working perfectly for receiving information from native components. The issue arises when I bind a native component to my custom components property. I can see my custom component property is firing but the native component only reacts to the original binding state and never changes again. I can't post all the code but I can post relevant code.
Bean Info Example



import com.inductiveautomation.factorypmi.designer.property.customizers.DynamicPropertyProviderCustomizer;
import com.inductiveautomation.factorypmi.designer.property.customizers.StyleCustomizer;
import com.inductiveautomation.vision.api.designer.beans.CommonBeanInfo;
import com.inductiveautomation.vision.api.designer.beans.VisionBeanDescriptor;
import com.client.components.DeviceControl.AnalogControlPane;
import com.client.components.DeviceControl.UpperAnalogControlPane;

import java.beans.IntrospectionException;

public class UpperAnalogControlPaneBeanInfo extends CommonBeanInfo {

    public UpperAnalogControlPaneBeanInfo()
    {
        super(UpperAnalogControlPane.class, DynamicPropertyProviderCustomizer.VALUE_DESCRIPTOR, StyleCustomizer.VALUE_DESCRIPTOR);
    }

    @Override
    protected void initProperties() throws IntrospectionException
    {
        super.initProperties();
        addProp("tagPath", "Tag Path", "Device Tag Path", "Setup", BOUND_MASK);
        addProp("deviceDescriptionText", "Device Description Text", "Device Description Text", "Setup", BOUND_MASK);
        addProp("statusTagPath", "Status Tag Path", "The Status Path for Device", "Setup", BOUND_MASK);
        addProp("controlGroupActionPath", "Control Group Action Path", "The Tag Path for the Desired Action", "Setup",BOUND_MASK);
        addProp("currentIndex", "Current Index","The current device being controlled", "Setup", BOUND_MASK);
        addProp("controlGroupRootPath", "Control Group Root Path","Path To UDT", "Setup", BOUND_MASK);
        addProp("controlSelectorValue", "Control Selector Value","Button Control Coordination", "Setup", BOUND_MASK);
    }

    @Override
    protected void initDesc()
    {
        VisionBeanDescriptor analogBean = getBeanDescriptor();
        analogBean.setName("Upper Analog Control Panel");
        analogBean.setDisplayName("Upper Analog Pane");
        analogBean.setShortDescription("Control Analog Devices that contain Discrete Attributes");
    }
}

An example of the methods in the view class for property changes:

 public void actionPerformed(ActionEvent buttonClicked) {
if (buttonClicked.getActionCommand().equals("DISCRETE") || buttonClicked.getActionCommand().equals("ANALOG"))
        {
            this.controlSelectorValue = this.deviceControlButton.isSelected();
            logger.info("Value of selector button {}", this.controlSelectorValue);
            logger.info("Value of Control button {}", deviceControlButton.isSelected());
            discretePaneDisabled(this.controlSelectorValue);
            setControlSelectorValue(this.controlSelectorValue);

        }
}


    public Boolean getControlSelectorValue() {
        return controlSelectorValue;
    }
    public void setControlSelectorValue(Boolean value)
    {
        Boolean oldValue = this.controlSelectorValue;
        logger.info("Changing the control selector value {}", value);
        this.controlSelectorValue = value;
        propertyChangeSupport.firePropertyChange("controlSelectorValue", oldValue, controlSelectorValue);
    }


public void propertyChange(PropertyChangeEvent event) {
        String propertyName = event.getPropertyName();
        Object inputValue = event.getNewValue();
        logger.info("Property change event has been called " + inputValue);
        if (propertyName.equals("tagPath") && inputValue != null && !inputValue.toString().isEmpty())
        {
            if (subscribedFlag == 0)
            {
                if(aMediator != null)
                {
                    this.aMediator.unsubscribeTags();
                    subscribeStatus();
                    subscribedFlag = 1;
                }else
                {
                    logger.info("Still Null - TAG PATH");
                }

            }

        } else if (propertyName.equals("controlGroupRootPath") && inputValue != null && !inputValue.toString().isEmpty())
        {
            if (aMediator != null) {
                this.aMediator.setRootPath(controlGroupRootPath);
            }else {
                logger.info("Still Null - Control Group Path");
            }
        }

    }

The property attribute I chose to share from the view class is ControlSelectorValue. I am purposely not doing anything when this property changes. Sorry I have to be kind of vague but I will try to share as much as I can. The property changes I am catching work perfectly. These properties are bound to the root container. The issue I am having is when I try to bind a native component's property to a property on my component. In the designer the binding connects normally. However the native component will only change from the original binding connection any further property changes my component has are ignored. I see my logger messages in the designer so my component property changes are firing.

You aren't showing how you are binding to the native component. (That you are doing so in a custom module is unwise, but not forbidden. It is just hard to get right, and really fragile.)

If the end result must have this combination, then you should be composing your component to include the native component, and setting that nested component's properties directly.

I am using the binding system in designer to bind to my custom components property like this:

This is the customs component's binding to the root container:

I did change the name of that property in the module code. I haven't compiled it to a module yet so the name in the designer is mismatched

I see that you are using a propertyChangeSupport object. Show how you are setting that up. (Or delete it and use the component's .firePropertyChange() method directly.)

I instantiate that object in the views Constructor. This is not the entire constructor I think I posted the relevant parts. Please let me know if I am missing something.

public class UpperAnalogControlPane extends AbstractVisionComponent implements ActionListener, StatusListener, PropertyChangeListener {
 private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    



    private ArrayList<PMIButton> paneButtons = new ArrayList<>();



    private GenericDevice device = new GenericDevice(logger);
    private MediatorInterface aMediator;

    private GridBagConstraints layoutBehavior = new GridBagConstraints();
    private GridBagLayout layoutManager = new GridBagLayout();

    public UpperAnalogControlPane()
    {
        propertyChangeSupport.addPropertyChangeListener(this);
        logger.info("Inside Constructor for discrete pane -> Context: ");
        this.setBackground(lightGrey);
        this.setOpaque(true);

        this.setLayout(layoutManager);
}
}

I don't ever use PropertyChangeSupport, but that doesn't look right.

Consider not using it at all.

Where you want to expose inner component properties to the rest of Ignition, implement the appropriate getters and setters on your outer component. (Make sure the setters perform .firePropertyChange after passing inward.)

Do you think this would cause my binding to be one sided somehow? Native components like the root container can pass my component information from the bindings. I can see the property changes being triggered from my logger, any insight why a native component wouldn't respond? There's no error just ignored functionality.

Your symptoms indicate you aren't firing property changes correctly.

Okay, are there resources for firing property changes correctly?

Just include a call to this.firePropertyChange() in all of your setters.

I will give that a whirl. Be back in bit.

Your BeanInfo implementation should mark properties as BINDABLE if and only if they fire property changes.

Is BINDABLE an argument that is passed in here?

 addProp("controlGroupRootPath", "Control Group Root Path","Path To UDT", "Setup", BOUND_MASK);

That's what BOUND_MASK is for. Forgot the proper constant name.

Okay, I am going to try removing the propertyChangeSupport.firePropertyChange() and replacing it with this.firePropertyChange(). It'll take a little bit because I am in a meeting and this class is kind of large. I will get back to you soon. Thanks for everything so far

Phil, your troubleshooting skills are immaculate. this.firePropertyChange() Fixed everything. I didn't have to change anything else. I am a little confused as to what was happening in the backend but it worked.

1 Like

For posterity, PropertyChangeSupport is only needed/should only be used if you are trying to add property change firing/event passing to a component that doesn't already have it.

That is, inside your SomeOtherTypeOfComponent, you instantiate a PCS object in a field, passing this as the bean.

Then you manually create the appropriate methods on your SomeOtherTypeOfComponent (because there's no interface for addPropertyChangeListener([name], listener)/removePropertyChangeListener(listener)) and then, from whatever bean property setters you have on your component, you do whatever setter logic and call pcs.firePropertyChange("name", oldValue, newValue).

All the PCS object really does is hold the map of listeners and handle firing the events for you.

But if you already have a component that extends some base swing component with its own property change setup, there's no real need to add it unless you want an ideological separation.

5 Likes