Function has different results when searching for template

I’ve been using this function for a long time and now I seem to have found a bug in it. When called with the name of a template instance, it returns correctly but none of the custom properties or methods are available.

def traverse_components( obj, visit ):
	##
	## recursively visit all components contained by obj
	##
	if obj is not None:
		visit( obj )

		try:
			c = obj.getComponents()
		except AttributeError:
			pass
		else:
			for x in c:
				traverse_components( x, visit )
				
				
				
def find_component( root, objname ):
	##
	## finds the 'first' occurence of an object named objname.  
	##	'first' as defined by the .getComponents function
	##
	class found: pass
	found.obj = None
				
	def finder( s ):
		def __finder( obj ):
			if found.obj is None and hasattr( obj, 'name' ) and obj.name == s:
				found.obj = obj
		return __finder
		
	project.util.traverse_components( root, finder( objname ))
	
	## could be None
	return found.obj

so this sequence produces identical printed output:

obj = window.rootContainer.getComponent( 'Template Instance' )
print( repr( obj ))

obj2 = project.util.find_component( window.rootContainer, 'Template Instance' )
print( repr( obj2 ))

However, if ‘Template Instance’ has a property named ‘Value’ and a method named ‘set_value’, these work:

obj.Value = 100
obj.set_value( 100 )

and these do not:

obj2.Value = 200
obj2.set_value( 200 )

In both cases the error is “object has no attribute ‘set_value’”

Any ideas? I use this function all over the place to avoid the issue of control name/paths changing when grouping and ungrouping objects.

ed: Some extra info. I lied about repr() returning identical results. There is a slight difference in the obj2 output:

[obj] com.inductiveautomation.factorypmi.application.components.template.TemplateHolder[Test,130,170,180x120,layout=com.inductiveautomation.factorypmi.application.components.template.TemplateHolder$1,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=]

[obj2] com.inductiveautomation.factorypmi.application.components.template.TemplateHolder[Test,130,170,180x120,invalid,layout=com.inductiveautomation.factorypmi.application.components.template.TemplateHolder$1,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=]

That invalid there. No idea what it is, but it’s different.

Okay I fixed it. Thanks for being here to give me a different perspective :slight_smile: Seems there was some object copying going on – at least that’s how I’m logging it.

def traverse_components( obj, visit ):
	##
	## recursively visit all components contained by obj
	##
	if obj:
		ans = visit( obj )
		if ans:
			return ans

		try:
			c = obj.getComponents()
		except AttributeError:
			pass
		else:
			for x in c:
				ans = traverse_components( x, visit )
				if ans:
					return ans				

	return None			


def find_component( root, name ):
	##
	## finds the 'first' occurence of an object named objname.  
	##	'first' as defined by the .getComponents function
	##
	def finder( name ):
		def __finder( obj ):
			if obj.name == name:
				return obj
			return None
		return __finder
		
	return project.util.traverse_components( root, finder( name ))

Jython wrapping of Swing components requires some customization to have dynamic properties and custom methods show up in the result. This normally only happens correctly in certain system.* script functions and container methods. .getComponents() is not one of them. See this topic, especially the end, for enhancements to this operation.