Custom Vision Component

I am trying to create a Vision Component that has a Numeric Label inside it. The component itself has two custom properties: tagPath and horizontalAlign. I am having trouble binding the tag value at tagPath to the value of the numeric label. This is the Java file for the component:

package com.inductiveautomation.ignition.examples.ce.components;

import com.inductiveautomation.factorypmi.application.binding.SimpleBoundTagAdapter;
import com.inductiveautomation.factorypmi.application.components.PMINumericLabel;
import com.inductiveautomation.vision.api.client.components.model.AbstractVisionComponent;

import java.awt.*;


public class AnalogValueComponent extends AbstractVisionComponent {

    private PMINumericLabel valueLabel;
    private String tagPath;
    private int horizontalAlign;
    private SimpleBoundTagAdapter bindingAdapter;

    public AnalogValueComponent() {
        setLayout(new BorderLayout());
        valueLabel = new PMINumericLabel();
        add(valueLabel, BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        new AnalogValueComponent();
    }

    public String getTagPath() {
        return this.tagPath;
    }

    public void setTagPath(String newTagPath) {
        String old = this.tagPath;
        this.tagPath = newTagPath;
        firePropertyChange("tagPath", old, newTagPath);
    }

    public int getHorizontalAlign() {
        return this.horizontalAlign;
    }

    public void setHorizontalAlign(int newHorizontalAlign) {
        int old = this.horizontalAlign;
        this.horizontalAlign = newHorizontalAlign;
        firePropertyChange("horizontalAlign", old, newHorizontalAlign);
    }

    @Override
    protected void onStartup() {
        super.onStartup();
        bindingAdapter = new SimpleBoundTagAdapter();
        bindingAdapter.setTarget(valueLabel);
        bindingAdapter.setTargetPropertyName("value");
        bindingAdapter.setTagPathString(this.tagPath);
        bindingAdapter.startup();
    }

    @Override
    protected void onShutdown() {
        super.onShutdown();
        bindingAdapter.shutdown();
    }
}

BeanInfo file


package com.inductiveautomation.ignition.examples.ce.beaninfos;

import com.inductiveautomation.factorypmi.designer.property.customizers.DynamicPropertyProviderCustomizer;
import com.inductiveautomation.factorypmi.designer.property.customizers.StyleCustomizer;
import com.inductiveautomation.ignition.examples.ce.components.AnalogValueComponent;
import com.inductiveautomation.vision.api.designer.beans.CommonBeanInfo;
import com.inductiveautomation.vision.api.designer.beans.VisionBeanDescriptor;

import java.beans.IntrospectionException;

public class AnalogValueComponentBeanInfo extends CommonBeanInfo {

    public AnalogValueComponentBeanInfo() {
        super(AnalogValueComponent.class, DynamicPropertyProviderCustomizer.VALUE_DESCRIPTOR, StyleCustomizer.VALUE_DESCRIPTOR);
    }

    @Override
    public void initProperties() throws IntrospectionException {
        super.initProperties();
        removeProp("opaque");
        removeProp("font");
        removeProp("foreground");
        removeProp("background");

        addProp("tagPath", "tagPath", "Tag Path for template component bindings", "Custom",
                PREFERRED_MASK | BOUND_MASK);
        addProp("horizontalAlign", "horizontalAlign", "horizontalAlign for template component " +
                "bindings", "Custom", PREFERRED_MASK | BOUND_MASK);

    }

    @Override
    protected void initDesc() {
        VisionBeanDescriptor bean = getBeanDescriptor();
        bean.setDisplayName("Analog Value Component");
        bean.setShortDescription("Analog Value component");
        bean.setValue(CommonBeanInfo.TERM_FINDER_CLASS, AnalogValueComponentTermFinder.class);
    }

}

The value doesn't appear in the component.

I also get an error in the console whenever I create a new instance of the component:

ERROR com.inductiveautomation.ignition.examples.ce.components.AnalogValueComponent - Error starting up "AnalogValueComponent"
java.lang.NullPointerException: null
	at com.inductiveautomation.factorypmi.application.binding.SimpleBoundTagAdapter.startup(SimpleBoundTagAdapter.java:154)
	at com.inductiveautomation.ignition.examples.ce.components.AnalogValueComponent.onStartup(AnalogValueComponent.java:55)
	at com.inductiveautomation.vision.api.client.components.model.AbstractVisionComponent.startupComponent(AbstractVisionComponent.java:191)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor$StartupVisitor.visit(ComponentVisitor.java:344)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:95)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:73)
	at com.inductiveautomation.factorypmi.application.FPMIWindow.startup(FPMIWindow.java:347)
	at com.inductiveautomation.factorypmi.designer.workspace.WindowWorkspace$DesigntimeWindowOpener.openWindow(WindowWorkspace.java:3880)
	at com.inductiveautomation.factorypmi.designer.workspace.WindowWorkspace.openWindow(WindowWorkspace.java:1528)
	at com.inductiveautomation.factorypmi.designer.model.navtree.WindowNode.open(WindowNode.java:260)
	at com.inductiveautomation.ignition.designer.navtree.model.AbstractResourceNavTreeNode.onDoubleClick(AbstractResourceNavTreeNode.java:428)
	at com.inductiveautomation.ignition.designer.navtree.NavTreePanel$MouseListener.lambda$mousePressed$0(NavTreePanel.java:716)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.run(Unknown Source)

Any help on how to fix this would be appreciated!

Don't do that. Bindings should be created by your users, not your component.

If you want to read a tag based on an input tagpath, you're eschewing the binding system and should go directly to the tag system:
com.inductiveautomation.ignition.common.tags.model.TagManager#subscribeAsync(com.inductiveautomation.ignition.common.tags.model.TagPath, com.inductiveautomation.ignition.common.tags.model.event.TagChangeListener)
Where you likely want to make your component itself a TagChangeListener.

3 Likes