# Custom Vision Component

**URL:** <https://forum.inductiveautomation.com/t/custom-vision-component/76609>\
**Category:** Module Development\
**Tags:** vision\
**Created:** [July 19, 2023, 7:52pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609 "2023-07-19T19:52:55Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Asjal\_Ahmad](https://avatars.discourse-cdn.com/v4/letter/a/9e8a1a/32.png) [@Asjal\_Ahmad](https://forum.inductiveautomation.com/u/Asjal_Ahmad)\
**Post date:** [July 19, 2023, 7:52pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/1 "2023-07-19T19:52:56Z")

</div>

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:

```auto
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

```auto

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.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/1/61cc46d18b9403e1323fa6959991548938656b91.png)

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

```auto
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!

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [July 19, 2023, 7:58pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/2 "2023-07-19T19:58:19Z")

</div>

> [@Asjal\_Ahmad](#):
>
> ```auto
> bindingAdapter = new SimpleBoundTagAdapter();
> bindingAdapter.setTarget(valueLabel);
> bindingAdapter.setTargetPropertyName("value");
> bindingAdapter.setTagPathString(this.tagPath);
> bindingAdapter.startup();
> 
> ```

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`.

---

<div class="post-metadata">

**Author:** ![Porter\_Ellis](https://avatars.discourse-cdn.com/v4/letter/p/ed8c4c/32.png) [@Porter\_Ellis](https://forum.inductiveautomation.com/u/Porter_Ellis)\
**Post date:** [October 29, 2024, 3:16pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/3 "2024-10-29T15:16:37Z")

</div>

But why is binding in a module a problem for Ignition? Does it cause unexpected behavior or instability?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [October 29, 2024, 3:23pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/4 "2024-10-29T15:23:04Z")

</div>

The simplest answer is that we never designed the binding system in Vision to be directly usable by third parties; we want it to remain exclusively the domain of the end user(s) working out of the designer. Modeling your components as simple collections of properties and allowing end users flexibility is, in our official opinion, strictly better than the alternative.

Leaving Perspective out of the equation, where things are fundamentally a bit different, even in Vision you _constantly_ have users asking for customization of the more "all in one" components like the easy chart, alarm status table, user management, etc. Inevitably, no matter how well constrained you _think_ your problem is, someone will come up with some edge case. If your module delivers a component that contains all the business logic internally, then that one-off case requires an entire new module build, install, replacement loop. If it's a "dumb" bag of properties, it's easy to override behavior in that one-off case.

There are still opportunities to encapsulate business logic _within_ expected bounds, such as adding system functions to scripting, custom expression functions, etc - which will naturally tie into the rest of Ignition much better. You _can_ also add new binding types to Vision, to some extent, though I'll be honest and say I don't know if anyone ever actually exercised that capability.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [October 29, 2024, 3:26pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/5 "2024-10-29T15:26:06Z")

</div>

> [@paul-griffith](#):
>
> You _can_ also add new binding types to Vision, to some extent

I investigated that at one point, but found that extension point no better than expression functions, and limited to Vision. 🤷‍♂️

I **would** like a platform-wide extension point for **async** binding types (like tag and query bindings).

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [October 29, 2024, 3:31pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/6 "2024-10-29T15:31:46Z")

</div>

> [@paul-griffith](#):
>
> You _can_ also add new binding types to Vision, to some extent, though I'll be honest and say I don't know if anyone ever actually exercised that capability.

Yeah I tried this once too, wouldn't recommended it.  
Was looking to bind against a custom resource type, and ended up just using expression functions since it was easier and more flexible from every angle.

---

<div class="post-metadata">

**Author:** ![Porter\_Ellis](https://avatars.discourse-cdn.com/v4/letter/p/ed8c4c/32.png) [@Porter\_Ellis](https://forum.inductiveautomation.com/u/Porter_Ellis)\
**Post date:** [October 29, 2024, 3:32pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/7 "2024-10-29T15:32:07Z")

</div>

Even though it was not _designed_ to be used that way, can it still be used that way? The only reason we are doing this is to deliver a completely functional (no need to be modified) controller of a highly complex liquid processing machine. We want to use all the wonderful abilities of Ignition to represent and build this controller, but not require the user to understand the inner workings of the machine or Ignition project. This project is, in the purest form, a turnkey controller that should not be configured by the user.

That being said, we don't want to create any custom properties or binding types. We only want to use the bindings natively found in Ignition, but within a module.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [October 29, 2024, 3:36pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/8 "2024-10-29T15:36:57Z")

</div>

🤷‍♂️

It's just code, anything is _possible_. As Phil will attest, Vision's binding system is both complicated and fragile, and you have close to zero insight on how it works as a third party. Even with full access to the source code, it's hard to understand.

I would recommend a bit of an end-run - instead of trying to encapsulate everything inside a component, have your module do the also unsupported but much more feasible thing and drop an inheritable project on the filesystem when you install it. Design your windows/templates/whatever using regular Ignition tools, then mark them as 'Protected' (to _minimize_ the ability of downstream users to change them), then export the project. Then wrap that export into your module in some way, splat it onto the filesystem, and train your end users to use that 'parent' project as the basis for their individual projects.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [October 29, 2024, 3:37pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/9 "2024-10-29T15:37:29Z")

</div>

> [@Porter\_Ellis](#):
>
> This project is, in the purest form, a turnkey controller that should not be configured by the user.

Then deliver the Ignition Server fully populated with the desired project. 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![Porter\_Ellis](https://avatars.discourse-cdn.com/v4/letter/p/ed8c4c/32.png) [@Porter\_Ellis](https://forum.inductiveautomation.com/u/Porter_Ellis)\
**Post date:** [October 29, 2024, 3:47pm UTC](https://forum.inductiveautomation.com/t/custom-vision-component/76609/10 "2024-10-29T15:47:56Z")

</div>

I understand and appreciate that the binding system is fragile and complicated and concede to your way of thinking. With the solution you provided to OP, is there also ways to program in this way with expressions, queries, etc? Can you point me to documentation for creating these types of pseudo-bindings inside a module? If the documentation is unavailable, could you provide short snippets of code for each pseudo-binding type (perhaps in a DM)? We have utilized almost every native binding type in Ignition to create this controller. Although simple and elegant, dropping the entire 'protected' project file onto the filesystem simply is not secure enough for our situation.
