Why does the Indirect Tag binding never resolve the canvas property?
Here is the chain. The value that moves is a canvas custom property, PropX. Something has to carry it into each dynamically created template instance, and the thing that receives it there is an internal property, PropY. The attempted link was an Indirect Tag binding on the template, using the path {1}.parent.parent.parent.parent.PropY with {1} = Template.Name. That link can never close.
An Indirect Tag binding builds a string and resolves it against the tag providers. .parent.parent.parent.parent.PropY is a Jython attribute walk through the Swing component tree. The script interpreter understands it, which is why the same expression works inside an event script. The tag subsystem does not. It looks for a tag at that literal path, finds nothing, and the binding shows bad quality. Reformatting the string does not help, because a tag binding cannot address a component property.
A property binding cannot close the link either. A template is sealed: bindings inside it can reach its own parameters, its internal properties, and tags. They cannot reach the container it sits in. A Client Tag works because both sides can reach it. It is also global to the client session, so every canvas on every open window shares that one value. That breaks the goal of a self-contained canvas you can drop onto several windows.
The bidirectional link therefore has to be scripted:
- The canvas
propertyChangeevent acts as the controller for the canvas-to-template direction. - Each template's
propertyChangeevent acts as the controller for the template-to-canvas direction. - A custom method on the canvas pushes the value to every instance.
The checks below find which part of that loop is broken.
Check 1: Is PropY a template parameter or an internal property?
Open the template in the Designer and look at where PropY is defined.
-
Template parameter. The Template Canvas can seed it for each instance at creation time, from the parameter data in its Templates dataset. The population script can write the current
PropXinto that data. New instances then start with the correct value and need no extra push. -
Internal property. The canvas cannot seed it at creation time. Every instance starts at the template default. A script has to push
PropXin after the instances exist.
In both cases, setPropertyValue() on a live instance writes the property. Runtime sync works either way. What differs is initial state.
Decision: If new instances show the default value until someone changes PropX, either convert PropY to a template parameter or add a post-population call to the fan-out method (see the procedure). Then go to Check 2.
Check 2: Which direction is actually failing?
Look at the trend first. Before editing any script, put a label on the window next to the canvas and bind it to canvas PropX. Inside the template, put a small label that shows PropY. Launch a client (or Preview mode), then:
- Change
PropXfrom the window. - Click an instance so that it changes its own
PropY.
Watch which labels move. Tuning does not fix wiring, so identify the broken leg before touching logic.
| Signal | Source | Wrong-value symptom | Points to |
|---|---|---|---|
Canvas PropX
|
Window binding, or a write from a template script | Window label changes, template labels do not | Canvas propertyChange filter or the fan-out method (Check 3) |
Template PropY (clicked instance) |
User action inside the template | Only the clicked instance changes; PropX label stays put |
Missing or failing template-to-canvas write (parent hop count) |
| Instance name | Name column of the Templates dataset | Exactly one instance follows PropX, the others do not |
Name filter in the fan-out matching a unique name (Check 3) |
| Container class-name strings | Internal Swing hierarchy of the canvas | Nothing updates after a platform upgrade that previously worked | Class-name match no longer hits (Check 3) |
Initial PropY
|
Template default or seeded parameter | New instances show the default until PropX changes |
No initial sync (Check 1) |
| Indirect Tag path string | {1}.parent...PropY |
Binding shows bad quality or an error overlay | Component path used where a tag path is required |
Instance PropY after rebuild |
Templates dataset rewritten | Values snap back to defaults at random-looking moments | Population script re-running (Check 4) |
Check 3: Does the fan-out method reach every instance?
The canvas does not keep its instances as direct children. The working drill-down descends four levels:
- An inner scroll container whose class name ends in
TemplateCanvas$1. - A
JViewport. - A
LayoutPanel. - The template instances.
That depth matches the four .parent hops that work from a template script. It also tells you the hop count to use going up.
Two defects appear in the fan-out as first written.
-
Name filter versus intent. The docstring says it updates all templates of the same type. The loop sets the property only where
temp2.name == templateName, and that compares the instance name from the Templates dataset. If the population script gives each row a unique name, only one instance ever updates. Decision: if every instance in the canvas uses the same template, drop the filter. If the canvas mixes template types, filter on something that identifies the type, not the instance name. A consistent naming prefix written by the population script is one option. -
Brittle class-name matching.
TemplateCanvas$1is an anonymous inner class, which is an implementation detail. A platform upgrade can rename it or restructure the hierarchy, and the nestedendswith()tests then fail silently: no error, no update. Search for theLayoutPanelrecursively instead of hard-coding every level. Some releases also expose a canvas scripting function that returns the instances directly. Check the Template Canvas scripting functions in the user manual for your installed version. If that function exists, use it and drop the tree walk.
Next: if the method now reaches every instance but values still misbehave, go to Check 4.
Check 4: Is the value bouncing or being overwritten?
A two-way scripted link is a closed loop, and each pass through it goes like this:
- A template writes
PropX. - The canvas
propertyChangefires. - The fan-out writes
PropYon every instance, including the one that started the change. - Each template's
propertyChangefires again.
Java bean property-change events are normally suppressed when the new value equals the old one, so a clean integer or string loop settles after one pass. It can keep re-firing in two cases:
- The values do not compare equal after conversion, for example float rounding or a string-versus-number mismatch.
- The property type holds a new object on every write, such as a dataset.
Put an explicit equality guard in both directions so the loop stops regardless of property type.
The second failure is overwriting. When the population script writes the Templates dataset, the canvas rebuilds its instances. Any value pushed by setPropertyValue() is lost, and internal properties return to template defaults. If the population script is triggered by something that changes periodically, such as a polled query or a tag change, script-set values revert at intervals that look random. Decision: gate the population script so it rewrites the dataset only when the instance list actually changes. Re-push PropX after every rebuild.
Designer behavior differs from a client. In the template editor the template has no canvas above it, so four .parent hops land on the wrong object or on None. Guard the upward write so it does nothing when the target does not have PropX.
What does the resolving implementation look like?
This branch assumes all instances in the canvas use the same template, so the fan-out has no name filter. Add the type filter from Check 3 if the canvas mixes templates.
- Add the custom property
PropXto the Template Canvas, if it is not already there. - On the canvas, add a custom method that searches the tree for the
LayoutPaneland writes only when the value differs:def UpdateComponents(self, propertyName, newValue): # Walk down until the LayoutPanel that holds the template instances def walk(comp): for child in comp.getComponents(): if str(child.getClass()).endswith("LayoutPanel'>"): for tmpl in child.getComponents(): if tmpl.getPropertyValue(propertyName) != newValue: tmpl.setPropertyValue(propertyName, newValue) else: walk(child) walk(self) - In the canvas
propertyChangeevent, fan out only on the property you care about. This event fires for every property on the component, including the Templates dataset.if event.propertyName == 'PropX': event.source.UpdateComponents('PropY', event.newValue) - In the template's root
propertyChangeevent, push local changes up to the canvas, with a guard for the Designer and for a non-canvas parent:if event.propertyName == 'PropY': canvas = event.source.parent.parent.parent.parent if canvas is not None and hasattr(canvas, 'PropX'): if canvas.PropX != event.newValue: canvas.PropX = event.newValue - In the population script, do one of the following right after writing the Templates dataset:
- If
PropYis a template parameter, write the currentPropXinto each row's parameter data. - If
PropYis internal, callUpdateComponents('PropY', canvas.PropX). The instances are built after the dataset write, so defer this call to the next event-dispatch cycle with the client's invoke-later utility. Called in the same cycle, it finds an empty or stale panel.
- If
- Remove the Indirect Tag binding from
PropY. Left in place, it overwrites script-set values whenever its quality changes. - Remove the Client Tag from the design, so no hidden cross-window coupling remains.
How do you verify the loop before reusing the canvas?
Keep the diagnostic labels from Check 2 in place and run these tests in a launched client, not the Designer.
-
Canvas to templates: change
PropXfrom the window. Every instance label must match within one repaint. -
Template to all: click one instance. The window
PropXlabel and every other instance must follow. -
Loop settling: add a temporary print or logger call to both
propertyChangehandlers. One click should produce one template event, one canvas event, and no repeating pairs. -
Initial state: close and reopen the window with
PropXat a non-default value. New instances must start at that value. - Rebuild: force the population script to run again. Values must survive, or be re-pushed immediately.
- Isolation: place a second copy of the canvas on the same or another open window. Changing one must not move the other. This is the property the Client Tag approach lacked.
- Upgrade check: after any platform upgrade, repeat step 1 first. A silent no-update means the class-name search no longer matches the hierarchy.
If test 6 fails, some shared path still exists, usually a leftover tag binding.
FAQ
What happens if I put the same Template Canvas on two windows with the scripted sync?
Each canvas carries its own PropX and fans out only to its own instances, so the two stay independent. A Client Tag behaves differently: every canvas in the client session shares its value.
What happens if the population script rewrites the Templates dataset after values were synced?
The canvas rebuilds its instances, and any PropY set by setPropertyValue() reverts to the template default. Gate the rewrite so it runs only when the instance list changes. After each rebuild, either seed PropX through the parameter data or re-run the fan-out on a deferred call.
What happens if I use an Indirect Tag binding with a .parent path inside a template?
The binding resolves the string as a tag path in a tag provider, not as a component reference, so it shows bad quality and never reads the canvas property. Component paths such as .parent.parent.parent.parent work only inside event scripts and custom methods.
When should I stop debugging the Template Canvas script and contact support?
Escalate if the four-level walk finds no LayoutPanel at all on your installed version, or if setPropertyValue() on an instance raises an error even though the property exists on the template. Escalate also if propertyChange keeps firing with equal old and new values after you add the guard. Send Inductive Automation support your exact platform version, the client log excerpt, and a minimal exported window and template that reproduce the behavior.