Property Categories of AbstractVisionComponent Incorrect on Module Import

Hi everyone,

I’m working on a custom Ignition Vision component that extends AbstractVisionComponent in Ignition 8.0.17, and I'm running into some issues with how properties are displayed in the Designer Property Editor. Specifically, all of my custom properties, as well as inherited ones from AbstractVisionComponent and JComponent, are being lumped into the Misc category, regardless of how I try to organize them in my BeanInfo class.

Current Setup

Here’s an outline of my current setup:

  • My component class, ControlPanel, extends AbstractVisionComponent.
  • I’ve created a BeanInfo class (ControlPanelBeanInfo) to define the properties and their categories.
  • I use addProp() in the BeanInfo class to define properties and specify their categories (e.g., "Data", "Appearance", etc.).

The Problem

Even though I define categories for my properties in the BeanInfo class, when I drop the component onto a Vision window in the Ignition Designer, all the properties show up under the Misc category instead of being organized into the categories I specified (e.g., Data, Appearance, etc.).

Additionally, all the properties that AbstractVisionComponent inherits from JComponent are automatically included in the Misc category. This makes the property list cluttered and difficult to navigate.

What I’ve Tried

  1. Categorizing Properties in BeanInfo:
    I’ve used the addProp() method in my BeanInfo class to assign properties to categories like "Data" and "Appearance". Here’s an example of what my code looks like:

    addProp("TagPath", "Tag Path", "The tag path for the component", "Data", PREFERRED_MASK | BOUND_MASK);
    addProp("NumVal", "Numeric Value", "A numeric value for display", "Data", PREFERRED_MASK | BOUND_MASK);
    

    However, despite this, all properties still show up under Misc in the Designer.

  2. Hiding Unwanted Inherited Properties:
    I tried using removeProp() to hide some of the inherited properties that are cluttering the Misc category, but this didn't seem to have any effect either.

Desired Outcome

What I would like to achieve is:

  • Custom properties grouped under the appropriate categories (e.g., Data, Appearance, Behavior) as defined in the BeanInfo class.
  • Reduce the number of inherited properties from JComponent that appear in the Property Editor, or at least hide the irrelevant ones.
  • Ensure that properties added to the component are displayed in a clean, organized way in the Designer.

Questions

  1. How can I ensure that properties show up in the correct categories in the Property Editor?
  2. Is there a way to prevent inherited properties from JComponent from being automatically included under Misc?
  3. Are there any alternative approaches or best practices for handling property organization in Ignition Vision components?

Any help or suggestions would be greatly appreciated!

Thanks!

Sounds like the designer is not even finding your BeanInfo class. Did you add your bean info package name to the search path (as shown in the Vision component example) ?

2 Likes

Literally just found that solution about 3 minutes ago. I have been fighting this for 2 days. Thank you for your prompt response.

Side tangent, we're about to release 8.3; 8.1 has been out since November 2020. You should really upgrade as soon as possible.

I am a system integrator working with some old Ignition projects. Customers don't always like to change things with a working system. I wholeheartedly agree that these projects should be upgraded to the most recent version, but the trick for me is convincing them to shell out the money/downtime for it.

As a side note, I am also trying to pre-bind a property to a tag in the module. Is there a simple way to do this, or is it pretty involved?

It's involved, and a disaster for the poor soul who has to deal with this later. A 3rd party module should not be wiring up the data flow of a project. Just make the functionality possible.

Our goal is to provide control over a specific machine to the user, without letting the user mess with the functionality too much. Instead of directly creating the binding, could I create a tag change listener and populate the value of the property with the new value of the tag? If so, how would I do this? The examples are a little unclear about tag change listeners. This is the preliminary code I have:

private void bindTagToNumVal(String tagPath) throws IOException {
    // Parse the tag path correctly
    TagPath path = TagPathParser.parse(tagPath);

    // Create a listener for tag changes
    TagChangeListener listener = event -> {
        // When the tag value changes, handle the new value
        QualifiedValue newValue = event.getValue();
        handleTagChange(newValue);  // Call the method to handle the new tag value
    };

    // Subscribe to tag changes using the tag path and listener
    tagManager.subscribeAsync(path, listener);
}

// Handle the tag change event
private void handleTagChange(QualifiedValue newValue) {
    if (newValue.getQuality().isGood()) {
        Double newTagValue = newValue.getValue() instanceof Double ? (Double) newValue.getValue() : 0.0;
        setNumVal(newTagValue);  // Update NumVal property
    }
}

@Override
    protected void onStartup() {
        super.onStartup();
        try {
            bindTagToNumVal("[VTISControl]VTIS1/Phase Control/Phase1_Ster/Status/Phase Status");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Yes, a tag change listener is an option if the tag path is hard coded into your component (Ewww!). If not hard coded, just let the designer do it.

(Be sure that your listener delegates component property writes to the Event Dispatch Thread.)

The tag path will not be hardcoded, but couldn't I reasonably instantiate and remove listeners for that tag every time the tag path changes? If so, should I be doing this in the GatewayHook or on the component file itself? Or do I create the TagChangeListener class (found at this forum post) in the Common directory and use it where it is needed?

You should do this in the component startup and shutdown events.

This is what I have:

    // Listener class to update numVal
private class TagValueChangeListener implements TagChangeListener {
    @Override
    public void tagChanged(TagChangeEvent event) {
        QualifiedValue qv = event.getValue();
        if (qv.getValue() instanceof Double) {
            setNumVal((Double) qv.getValue());
        }
    }
}

private void listen(String tagPath){
    listener = new TagValueChangeListener();
    try {
        context.getTagManager().subscribeAsync(TagPathParser.parse(tagPath), listener);
    } catch (IOException e) {
        logger.error(String.valueOf(e));
        throw new RuntimeException(e);
    }
}

@Override
protected void onStartup() {
    super.onStartup();
    listen(tagPath);
}

@Override
protected void onShutdown() {
    if (tagPath != null) {
        try {
            context.getTagManager().unsubscribeAsync(TagPathParser.parse(tagPath), listener);
        } catch (IOException e) {
            logger.error(String.valueOf(e));
            throw new RuntimeException(e);
        }
    }
    super.onShutdown();
}

public String getTagPath() {
    return tagPath;
}

public void setTagPath(String tagPath) throws IOException {
    this.tagPath = tagPath;
    listen(this.tagPath);
}

public Double getNumVal() {
    return numVal;
}

public void setNumVal(Double numVal) {
    Double old = this.numVal;
    this.numVal = numVal;
    firePropertyChange("numVal", old, numVal);
    if (numTest != null) {
        numTest.setValue(this.numVal);
    }
    repaint();
}

This does nothing in the Designer when changing the tagPath property. What am I missing?

Not sure, but you are definitely leaking listeners when you change the tag path (you need to unsub the prior tagpath before subscribing the new). You are also assigning to a component property directly in your listener. Expect this to eventually crash your client and/or designer.

I think you are on your own. You are on a path that I disagree with, and I therefore have insufficient motivation to study your code.