Binding to Custom Component Methods

It is mentioned here this is not possible with the expression function runScript(). Has that changed?

Also, there is a hint of possibly using expression binding using a custom module here. I did not see any good examples that show how this works for expression binding to custom component methods, including passing other bound properties as arguments (or accessing them within the function via access to self). Does anyone have success with this and are there some examples that could be shared?

Yes, it is possible to use runScript with custom functions (in certain circumstances). Mirroring what was described in your first link, I added a text field that accepts *args, and just returns them as a string:


Then, I added a runScript binding using self:
runScript("self.printArgs", 0, {Root Container.Text Field.text}, {Root Container.Text Field 1.text}, {Root Container.Text Field 2.text})

1 Like

What are the certain circumstances restrictions? Or conversely, what are the generally cases when this is expected to function?

It should work fine from components/vision, but you may run into unexpected behavior in templates (where things are nested in a non-intuitive way) and other contexts (such as expression tags/client tags, where a self argument wouldn’t make sense).

I am trying to use this in a template, in effort to keep the code that performs the complex calculations packaged with the template. I’ll use the script functions to set internal parameters on the root and bind internal template components on those parameters to eliminate the nesting unpredictability. Any further words of caution?

Note that when you get down in the weeds, various Swing methods that might yield a component for you will deliver it shorn of its custom methods and custom properties. The generic solution is presumably planned for a future version of Ignition, but the current version of Simulation Aids installs the necessary shim to prevent this from happening. See the discussion here:

It seems to be working for the most part, but I am having some performance issues. I found an article here that says that referring to UDTs directly are heavy. This template is doing just that, extracting multiple fields from a UDT that it took as a parameter (of the UDT data type). This window has been slow and demands a lot of client-side CPU. Now the template itself demands more CPU than reasonable. Unless I have done other things wrong this appears to be the root of the performance problems unless someone can provide input that UDT references have been optimized. Is passing the string path and then gathering the required data using system.tag.readAll() still the recommended method?

1 Like

Personally, I would never use parameters of type UDTType, for all of the reasons in my post you linked to. Not sure if I understand your reference to the Python expression, but for the Window component bindings, you would use the expression language rather than Python to read the tag values, e.g.:
tag({RootContainer.DevicePath} + ‘/Temperature/PV’)

I found this eliminated all of my performance issues, once I moved away from UDTType parameters and started using this method above instead.

Just my 2c

One more optimization tip: never use the tag() expression function. Wherever you think you need it, move its result to an indirect tag binding, on a custom property if necessary, and use that property’s value instead.

The tag() expression function is a backwards-compatibility function with less than ideal behavior.

Interesting, why do you say that?
In any case, it’s far more efficient than directly binding UDTType properties.
I’ve been using this method for my projects, and most of my customers using Ignition are wineries (think 30 tanks per page with maybe 10-20 bindings per tank) with no issues. That being said, the server CPU usage at one site has fairly high usage that I’ve been trying to diagnose…

When you use the tag() expression, it has to go get that value for any re-evaluation of that expression, including an actual tag value change. When you break it out to an indirect binding, that binding will only make a request when the path changes, and sits waiting just for tag changes otherwise. The expression will re-evaluate on the new tag property change, but other reevaluations will only grab the subscribed tag value. Much more efficient.

1 Like

Thanks for the tips. I also need some parameters that are on the UDT tag for some calculations. Currently, I use a script that extracts the ExtendedProperties as follows:

path = "%s.ExtendedProperties" % udtPath
props = system.tag.read(path)
if props.value is not None:
for prop in props.value:
if prop.getProperty().name.lower() == parameterName.lower():
return prop.value
break

Anyone know how I can get these directly from indirect binding? The only idea I can think of is to get all of them in a dataset custom parameter and then reference it when needed.

Also, what is the syntax to indent my code in a code block?

The above solution (reading the ExtendedProperties and storing in a dataset) is the solution I implemented. Instead of using multiple rows in the dataset, I chose to use a single-row, multiple column format. Once the data was in the dataset, I was able to use the column name directly from the Expression language including a fail-over value.

To populate the dataset and provide a fail-over empty dataset to the custom parameter:

toDataset(
    runscript("self.getUdtParameters",
    {ManualTestTemplate.ScriptPollRate},
    {ManualTestTemplate.BaseTagPath})
,runScript('system.dataset.toDataSet([],[])'))

The script as attached to the Template:

def getUdtParameters(self, udtPath):
udtPath=str(udtPath) or ''
headers=
values=
try:
path = "%s.ExtendedProperties" % udtPath
props = system.tag.read(path)
if props.value is not None:
for prop in props.value:
headers.append(prop.getProperty().name)
values.append(prop.value)
except:
return None
ds=system.dataset.toDataSet(headers, [values])
return ds


Typical expression binding using the dataset of extended properties:

toInt({ManualTestTemplate.UdtParameters}[0,'WarningDays'],0)
,0)```

Not bullet-proof, but seems to be reasonably robust.  No errors when binding hasn't been fully updated on initial load of the windows.  Using the UDT tagPath as string and indirect references has lightened the load tremendously.  The as-packaged template is loading quickly and responds well even when on a window with many controls and 8 of these templates viewed remotely through an encrypted connection.

Thanks to all that provided guidance.

Long time since you posted this, but @pturmel , how do you handle tags that can be disabled or missing depending on the device, without using tag()?

I would usually use something like this (i've never really liked the double read...):

if(toStr(tag("tagPath.quality")) != 'Disabled'
   , tag("tagPath")
   , None
)

Using your method, I could indirectly bind to tagPath.quality and also tagPath, however I wouldn't want to be reading the tagPath to a custom property on the root of the template, as then i'd get overlays displayed when it isn't valid. Is this what you do?

Just check the opt-out of overlay.

1 Like

Of course, I always forget about that… Cheers!

See also more recent rant about tag() expression performance implications: