Custom Vision Component Event Handler Information

I'm trying to create a very simple Vision component with some event scripting.

I've got the component working but am trying to figure out how to populate the Event Description and Event Object Properties at the bottom of the scripting window.

image

I'm using an ActionAdapter to try to help me out by setting up a default script and populating the metadata, and I think what I'm probably missing is the ActionAdapter.setBuilderInfo()) method, but since it just takes a Map<String, Object> I'm really not sure what I need to supply to it to make it work as expected, or if this is even the right approach.

Anyone have any insight to offer to help me get this populated with information that would help my end users know what they're getting with these events?

Here is a snippet from my Batik SVG component's BeanInfo that shows what you should be doing:

	protected void initEventSets() throws IntrospectionException {
		super.initEventSets();
		addEventSet(ScalableVectorCanvas.class, "SVGDocumentLoader",
				SVGDocumentLoaderListener.class, SVGDocumentLoaderEvent.class,
				new String[] { "documentLoadingStarted", "documentLoadingCompleted",
						"documentLoadingCancelled", "documentLoadingFailed" });
		addEventSet(ScalableVectorCanvas.class, "GVTTreeBuilder",
				GVTTreeBuilderListener.class, GVTTreeBuilderEvent.class,
				new String[] { "gvtBuildStarted", "gvtBuildCompleted",
						"gvtBuildCancelled", "gvtBuildFailed" });
		addEventSet(ScalableVectorCanvas.class, "linkActivation",
				LinkActivationListener.class, "linkActivated");
		addEventSet(ScalableVectorCanvas.class, "SVGLoadEventDispatcher",
				SVGLoadEventDispatcherListener.class, SVGLoadEventDispatcherEvent.class,
				new String[] { "svgLoadEventDispatchStarted", "svgLoadEventDispatchCompleted",
						"svgLoadEventDispatchCancelled", "svgLoadEventDispatchFailed" });
		addEventSet(ScalableVectorCanvas.class, "updateManager",
				UpdateManagerListener.class, UpdateManagerEvent.class,
				new String[] { "managerStarted", "managerSuspended",
						"managerResumed", "managerStopped",
						"updateStarted", "updateCompleted", "updateFailed" });
	}

Start with the javadoc for addEventSet().

Mine is pretty similar, or at least I can't see the difference, but how does it get the event description and event object parameters? Mine are still empty as in the screenshot.

    @Override
    protected void initEventSets() throws IntrospectionException {
        super.initEventSets();
        this.addEventSet(
                LiftEventComponent.class,
                "eventReceived",
                LiftEventListener.class,
                LiftEventObject.class,
                new String[]{"onEventReceived"});

Is it using the values from a property bundle perhaps? I don't have one of those wired up for these classes yet. Maybe that's the issue?

Good question. Not sure. It definitely uses introspection somehow.

So one key difference is that I diverted from the standard convention for the add/remove/get listener methods. After reviewing the docs I saw an overload that takes those method names, so I've changed it to this, but still no dice.

    @Override
    protected void initEventSets() throws IntrospectionException {
        super.initEventSets();
        this.addEventSet(
                LiftEventComponent.class,
                "eventReceived",
                LiftEventListener.class,
                LiftEventObject.class,
                new String[]{"onEventReceived"},
                "addEventReceivedListener",
                "removeEventReceivedListener",
                "getEventReceivedListeners");
    }

Adding a properties file didn't seem to help, assuming I did it correctly.

It looks like you could, in theory, populate a custom event description with a well formatted bundle key:

String eventSetName = node.getParent().getEventSet().getName();
String descString = BundleUtil.get().getString(
    "events." + eventSetName + "." + node.getMethodDescriptor().getName(),
    "<i>(Documentation Missing)");
eventDesc.setText("<html>" + descString);

So something like events.eventReceived.onEventReceived=Your event description.
Again, in theory.

1 Like

Is it possible to add another "events" bundle to merge with IA's bundle?

Would I add that to the properties file of the BeanInfo class or the component class?

Or am I even more confused that I think I am? :grin:

I think you could do something like:

  1. Add an "xyz.properties" file to the root of your module's resources.
  2. Add keys following the events.eventReceived.onEventReceived format above.
  3. Register your bundle in the "common" scope in your designer hook: BundleUtil.get().addCommonBundle("xyz")
2 Likes

That worked! Thanks so much!

1 Like

So that worked for the event description, and I thought I could easily figure out the parameter section from there, but I've tried a couple things and it's not obvious to me.

Do you know what magic key might fill out the Event Object Properties?

:grimacing:
There's no dynamic way to add new event object properties, in a supported manner.

If you hold your nose...
com.inductiveautomation.factorypmi.designer.eventhandling.ActionConfigPanel#EVENT_OBJECTS is a package-private hashmap String to String[].
If you got a reference to that field, you could add your own key/value (e.g. eventReceived as the key, your list of possible parameters for all of these events as the value). Then add keys in the format
eventobj.eventReceived.property=My event object parameter, and things, again, might work. Your different events will all be assumed to receive the same parameters.

1 Like

Well, obviously it would be nice to have a supported way to do this to create an ideal user experience for our custom component.

However, the "hold your nose" approach does appear to work.

1 Like

Agreed. I could put it on the books, but honestly I don't think it's going to buy you much compared to the "hold your nose" approach. I would consider your approach "safe" for the foreseeable future; we're pretty unlikely to refactor something this fundamental to Vision at this stage of the game; if we ever do, we'll surely replace it with an actual API.

1 Like

Totally understand. Thanks again.

For those who may be interested in this topic, here's the relevant code to do this unsupported thing that you should only do as a last resort.

This goes in the DesignerHook class; I put it in the onStartup() method. This implies that you've declared a dependency on the Vision module..

 var visionSdk = (VisionDesignerInterface) context.getModule(VisionDesignerInterface.VISION_MODULE_ID);

  // If the Vision module is installed, add our component to the palette
  if (visionSdk != null) {
      var palette = visionSdk.getPalette();

      var group = palette.addGroup("Your Custom Components");
      group.addPaletteItem(new JavaBeanPaletteItem(YourComponent.class));

      
      try {
          var actionConfigPanelClass = Class.forName("com.inductiveautomation.factorypmi.designer.eventhandling.ActionConfigPanel");
          var eventObjectsField = actionConfigPanelClass.getDeclaredField("EVENT_OBJECTS");
          boolean accessibility = eventObjectsField.canAccess(null);
          eventObjectsField.setAccessible(true);

          var eventObjects = (Map<String, String[]>) eventObjectsField.get(actionConfigPanelClass);
          if (!eventObjects.containsKey("eventReceived")) {
              eventObjects.put("eventReceived", new String[]{"source", "eventName", "eventData"});
          }

          eventObjectsField.setAccessible(accessibility);
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
2 Likes