Template Canvas Property Change

Hi Team,

I am new to Template canvas component in vision,
I would like to make the below circle as 'red' on startup,
when user done partial entries it should be 'yellow' when all entries made, it should be 'green'.
image

This is how i got the user values via save button,

templateData1 = event.source.parent.getComponent('Template Canvas')
templateList1 = templateData1.getAllTemplates()
userInput1 = []

# Collect paramValues for the first Template Canvas
for template in templateList1:
	userInput1.append(str(template.paramValue))
#	system.gui.messageBox(	str(template.paramName)+":"+str(template.paramValue))

My template canvas data looks like

On startup part is done, but how to get the userinput even before hitting the save button?

It looks like you are only painting one template? Is the circle contained within the template that is being painted, or is it separate from that? if the circle is within the template, then the color could be controlled from within the template itself independent of the canvas.

could, and probably should be.

1 Like

No , I have 5 template canvas,
and i have 5 circles at the bottom of the screen (not within template canvas),
If no entry made ->red
if partial entry made -> yellow
if full entry made-> green
(before hitting the savebutton)

So each template has three text fields, or is each textfield/label an independent template?

...Also, are they text fields or numeric text fields?

I have 6 different templates like text, num entry, checkbox etc,
then i am constructing the templates from db for each template canvas (combination of templates),
template looks like:

label -> paramName : component (text field/checkbox etc) -> paramValue

The first idea that popped into my head is to create a custom method on the template canvas, and call it recursively from the templates on the paramValue propertyChange event.

Here was the result in my test environment:

Here is the custom method on the template canvas:

def setCircleColor(self):
	"""
	Arguments:
		self: A reference to the component instance this method is invoked on. This argument
		  is automatic and should not be specified when invoking this method.
	"""
	# Create a list of parameter values from each painted template, converting each value to string
	parameterList = [str(template.paramValue) for template in self.allTemplates]
	
	# Get the 'Circle' component
	# Change the name and path as needed
	circle = self.parent.getComponent('Circle')
	
	# Define a list of incomplete test values for various component types
	testList = ['0', '', 'False']
	
	# If all values match, set circle color to red
	if all(value in testList for value in parameterList):
		circle.fillPaint = system.gui.color('red')
		
	# If any value matches, set circle color to yellow
	elif any(value in testList for value in parameterList):
	    circle.fillPaint = system.gui.color('yellow')
	
	# If no values match the testList, set circle color to green
	else:
	    circle.fillPaint = system.gui.color('green')

Here is the property change event script for the templates:

# Written for the template's propertyChange event handler
from com.inductiveautomation.factorypmi.application.components import TemplateCanvas

# This function recursively retrieves the TemplateCanvas of a given source
def getTemplateCanvas(source):
	# Assign the parent of the source to the canvas
	canvas = source.parent
	
	# If the canvas is a TemplateCanvas, return it
	if isinstance(canvas, TemplateCanvas):
		return canvas
	else:
		# If the canvas is not a TemplateCanvas, call the function recursively
		canvas = getTemplateCanvas(canvas)
		
		# If we find a TemplateCanvas up the hierarchy, return it
		if canvas is not None:
			return canvas
	
# Listen for property changes on the 'paramValue' property
if event.propertyName == 'paramValue':
	# Get the TemplateCanvas of the source of the event
	canvas = getTemplateCanvas(event.source)

	# If a TemplateCanvas was found, call its 'setCircleColor' method
	if canvas is not None:
		canvas.setCircleColor()

You will probably want to move the template scripts into a single function in your script library to eliminate redundancy.

Edit: Accidently pasted the setCircleColor method twice, and made correction to the first return statement in the property change script

Subsequent Edit: Corrected copy/past indention error

2 Likes

Thanks for the help,

i am confused with

# Define a list of incomplete test values for various component types
	testList = ['0', '', 'False']

The script i used in the project:
LogBookCanvas:

def generate_templates(category, enable_value, batch, process, offset_value=0):

	columnNames = ["name", "template", "parameters", "x", "y", "width", "height", "layout", "z"]
	new_tmp = []
	
	fields_query = """
	    SELECT f.FieldName as ParamName, ft.field as Template, c.Category as Category
	    FROM tbl_fieldmaster f
	    JOIN tbl_fieldtypemaster ft ON ft.ID = f.FieldType
	    JOIN tbl_categorymaster c ON c.ID = f.Category
	    WHERE f.ProcessArea = ? AND f.Category = ?
	    ORDER BY _id;
	"""
	
	fields = system.db.runPrepQuery(fields_query, [process, category], "db")
	
	for row in range(fields.getRowCount()):
	    paramName = fields.getValueAt(row, "ParamName")
	    template = fields.getValueAt(row, "Template")
	    
	    value_query = """
	        SELECT l.ObservedValue as Value, f.FieldName as ParamName
	        FROM tbl_logbookentry l
	        JOIN tbl_fieldmaster f ON f._id = l.Parameter
	        JOIN tbl_fieldtypemaster ft ON ft.ID = f.FieldType
	        JOIN tbl_categorymaster c ON c.ID = f.Category
	        WHERE f.ProcessArea = ? AND f.Category = ?
	        AND l.OrderNumber = ?
	    """
	    value_result = system.db.runPrepQuery(value_query, [process, category, batch], "MES_CDCMS")
	    paramValue = ""
	    for row in range(value_result.getRowCount()):
	        if value_result.getValueAt(row, "ParamName") == paramName:
	            paramValue = value_result.getValueAt(row, "Value")
	           
	            break
	    
	    enable = 1
	    xPosition = 10
	    yPosition = (row * 25) + 10
	    width = 230
	    height = 25
	    
	    newRow = [
	        paramName,  # name
	        "LogBook/" + template,  # template
	        '{"paramName":"' + paramName + '","paramValue":"' + paramValue + '","enable":"' + str(enable) + '"}',  # parameters
	        xPosition,  # x
	        yPosition,  # y
	        width,  # width
	        height,  # height
	        "n/a",  # layout
	        0  # z
	    ]
	    new_tmp.append(newRow)
	
	new_tmp2 = system.dataset.toDataSet(columnNames, new_tmp)
	return new_tmp2

And on vision opened event:

iProcess = system.gui.getParentWindow(event).getComponentForPath('Root Container').iProcess


iBatch = system.gui.getParentWindow(event).getComponentForPath('Root Container').iBatchNo

new_tmp1 = LogBookCanvas.generate_templates(1, 1, iBatch, iProcess)  # Category 1
new_tmp2 = LogBookCanvas.generate_templates(2, 1, iBatch, iProcess)  # Category 2
new_tmp3 = LogBookCanvas.generate_templates(3, 1, iBatch, iProcess)  # Category 3

system.gui.getParentWindow(event).getComponentForPath('Root Container.Template Canvas_1').templates = new_tmp1
system.gui.getParentWindow(event).getComponentForPath('Root Container.Template Canvas_2').templates = new_tmp2
system.gui.getParentWindow(event).getComponentForPath('Root Container.Template Canvas_3').templates = new_tmp3

Note:

Since a checkbox was in your list, this implied that your paramValue custom property could end up being 'False' instead of 0 when cast to a string. I also included '' for no value present at all.

1 Like

But this list should be dynamic right as i am constructing for each canvas from db

No. The testList is all encompassing, so regardless of the resulting template type, that list should be able to evaluate the relevant '0' condition.

Yes understood,

I have included the given script as below,


But the propertychange event is not triggered. What am i doing wrong?

• Make sure the template's paramValue custom property is bidirectionally bound to the relevant component property, so when the value changes, it gets written back to the custom property triggering the propertyChange event.

Yes it is bidirectional already.
i have templates like:
Textfield, Multistate, NumericEntry
and same set parameters like,
image

image

Has the code been applied? Does this star go away?

Yes gone
image

Is it correct now?
I tried this no change so far.

## Written for the template's propertyChange event handler
from com.inductiveautomation.factorypmi.application.components import TemplateCanvas

# This function recursively retrieves the TemplateCanvas of a given source
def getTemplateCanvas(source):
	# Assign the parent of the source to the canvas
	canvas = source.parent
	
	# If the canvas is a TemplateCanvas, return it
	if isinstance(canvas, TemplateCanvas):
		return canvas
	else:
		# If the canvas is not a TemplateCanvas, call the function recursively
		canvas = getTemplateCanvas(canvas)
		
		# If we find a TemplateCanvas up the hierarchy, return it
		if canvas is not None:
			return canvas
	
# Listen for property changes on the 'paramValue' property
if event.propertyName == 'paramValue':
	# Get the TemplateCanvas of the source of the event
	canvas = getTemplateCanvas(event.source)

# If a TemplateCanvas was found, call its 'setCircleColor' method
if canvas is not None:
	canvas.setCircleColor()

I got below error
NameError: name 'canvas' is not defined

First, create a project library function that each of the templates can call to find their respective parent canvases and a project library function that will allow the canvases to set the color of the circle that is in their parent containers:
image

# Written for the template's propertyChange event handler
# Import necessary classes here, so they don't have to be imported every time the functions are called
from com.inductiveautomation.factorypmi.application.components import TemplateCanvas
from javax.swing import SwingUtilities

def getTemplateCanvas(source):
	"""
	Returns the nearest ancestor TemplateCanvas of the given source component.
	
	Arguments:
		source: The component for which the ancestor is to be found.
	
	Returns:
		TemplateCanvas if an ancestor exists, or None.
	"""
	return SwingUtilities.getAncestorOfClass(TemplateCanvas, source)
def setCircleColor(canvas):
	"""
	Sets the color of the circle component that is in the parent container with the template canvas
		Green = No zero, False, or Empty template 'paramValue' custom parameters
		Yellow = Some but not all zero, False, or Empty template 'paramValue' custom parameters
		Red = Zero, False, or Empty values for ALL template 'paramValue' custom parameters
	Arguments:
		canvas: A template canvas in a container with a circle component
	
	Returns:
		None
	"""
	# Create a list of parameter values from each painted template, converting each value to string
	parameterList = [str(template.paramValue) for template in canvas.allTemplates]
	
	# Get the 'Circle' component
	# Assumes that each canvas and circle are contained in separate containers
	circle = canvas.parent.getComponent('Circle')
	
	# Define a list of incomplete test values for various component types
	testList = ['0', '', 'False']
	
	# If all values match, set circle color to red
	if all(value in testList for value in parameterList):
		circle.fillPaint = system.gui.color('red')
		
	# If any value matches, set circle color to yellow
	elif any(value in testList for value in parameterList):
	    circle.fillPaint = system.gui.color('yellow')
	
	# If no values match the testList, set circle color to green
	else:
	    circle.fillPaint = system.gui.color('green')

Then, call the project library scripts whenever the paramValue property change event occurs:

# Written for each template's propertyChange event handler
# Listen for property changes on the 'paramValue' property
if event.propertyName == 'paramValue':
	# Get the TemplateCanvas of the source of the event
	canvas = libraryScripts.getTemplateCanvas(event.source)

	# If a TemplateCanvas was found, call the 'setCircleColor' shared library function
	if canvas is not None:
		libraryScripts.setCircleColor(canvas)

Here is the result:

Edit: Corrected bad practices, and removed previous answers that contained bad practices

I created one dummy method as

def setcolor(self):
	"""
	Arguments:
		self: A reference to the component instance this method is invoked on. This argument
		  is automatic and should not be specified when invoking this method.
	"""
	# Change the name and path as needed
	circle =self.parent.getComponent('Circle')

	circle.fillPaint = system.gui.color('yellow')

And called it before

event.source.setcolor()
# Written for the template's propertyChange event handler
from com.inductiveautomation.factorypmi.application.components import TemplateCanvas

This worked
But i dont know whats the issue here

@justinedwards.jle I found out the issue,
Property change script should be written on each template
it is working as expected, Thanks a lot!.