Phone: 905 409-1589
Email: info@penproductions.ca
RSS LinkedIn Twitter Twitter
DotNet Treeview
Navigate:
Creating Control:
We will start this tutorial just the same as the listView control by creating the treeView control using the "system.windows.forms" name space as defined at MSDN. We will also add the testButton so we can format the properties, methods and events to the listener so we can see what possible options we have to work with. For now we will not need an initialize function as we will just work with it as it is for now.

 

Code:
--Destroy dialog if it already exists.
try(destroyDialog theRollout)catch()

--Create a rollout
rollout theRollout "The Rollout" width:300
(
	--Create the dotNet treeView control
	dotNetControl tv "system.windows.forms.treeView" height:200
	
	--Create a button for testing. 
	button testButton "Test"
	on testButton pressed do
	(
		clearListener()		--Clear the listener.
		format "Props\n"	--Format a header so we know what we are looking at below. 
		showProperties tv	--Show the properties of the treeView control.
		format "\nMethods\n"	--Format a header so we know what we are looking at below. 
		showMethods tv		--Show the properties of the treeView control.
		format "\nEvents\n"	--Format a header so we know what we are looking at below. 
		showEvents tv		--Show the properties of the treeView control.
	)

	on theRollout open do
	(
		
	)
)
createDialog theRollout									

Adding Tree Nodes:
To get started with adding nodes to the treeView will need need some objects in Max to display. Create several objects and place them in a hierarchy about three or four branches deep so we have something to work with. Have a couple of root nodes as well so you can see the difference.

We will need a populateTreeView function that will get the process started but first we need to find out how to add nodes to treeView. Use the test button and in the properties we can see a .nodes property. If you check the methods of tv.nodes there is a .add method.

Listener:
  .<System.Int32>Add <System.Windows.Forms.TreeNode>node

Just like in the listView we will need to create a dotNetObject but this time it is a treeNode and apply a string value to it that will be what shows up in the treeView. Create the function below and then call it from the on rollout open event handler.

Code:
	--Adds root nodes to treeView.
	fn populateTreeView theTv=
	(
		--Loop through all the objects in the scene. 
		for x in objects do 
		(
			--Create a treeViewNode and add it to the treeView control
			theTv.nodes.add (dotNetObject "System.Windows.Forms.TreeNode" x.name) 
		)
	)
	
	on theRollout open do
	(
		populateTreeView tv
	)
									

Notice that in the result we are not seeing the hierarchy of objects that we expect. Each node is a root node and their relationship is not respected. Next we will correct that.

Result:
Recursive Functions:
To be able to add nodes to treeView in their hierarchy we will need a recursive function. I expect that any one that is starting to work with dotNet should already be at a point of understanding recursion. If not I will give a brief description of it first but I will not cover it in depth.

A recursive function is one that calls it self. In the case of the tool that we are developing we need to be able to follow the hierarchy of nodes in the scene from the root down through all the children. Since we don't know how many children there are, and how many children each child has we need recursion. For each child the function will be called again, if that child has children it will call it self for each of those children as well and so on down the hierarchy.

To get started with this we will need a variable to store all the root nodes of the scene in and a function to find them all. This simply checks to see if the object has a parent in the scene and if not it stores that object in rootObjs array that we define at the top of the rollout.

Code:
	local rootObjs=#()		--Will hold all the root nodes found in the scene. 
									
	--Find all the root objects in the scene and append them to rootObjs array
	fn findRootObjs =
	(
		rootObjs=#()
		for x in objects do 
		(
			if x.parent==undefined then append rootObjs x
		)
	)
									

Next we will need the recursive function to loop down through all the children and add the nodes to treeView. Since we already tried adding nodes directly to treeView and saw that it didn't work we will need to add treeNodes to treeNodes. If you create a "System.Windows.Forms.TreeNode" in the listener and check it's properties you can see that it also has a nodes property and the nodes property has a .add method. Each time the function calls it self it passing to the new instance of the function the node that it is working on and the treeNode that it added to treeView.

Code:
	--Recurse hierarchy and add treeview nodes.
	fn recurseHierarchy obj theTvNode =
	(
		for i = 1 to obj.children.count do		--Loop through each of the children
		(
			n=(dotNetObject "System.Windows.Forms.TreeNode" obj.children[i].name)
			theTvNode.nodes.add n
			recurseHierarchy obj.children[i] n	--Call recursion on each of the children. 
		)
	)
									

I have changed the populateTreeView function so that it first calls the findRootObjs function and then only loops through the root nodes of the scene. For each of the root nodes it adds a node to the root of treeView and calls the recurseHierarchy function and passes the object it is working on and the treeNode that it created for that object.

Code:
	--Adds root nodes to treeView.
	fn populateTreeView theTv=
	(
		findRootObjs()		--Collect all the root objects.
		
		--Loop through all the objects in the scene. 
		for x in rootObjs do 
		(
			--Create a treeViewNode and add it to the treeView control
			n=(dotNetObject "System.Windows.Forms.TreeNode" x.name) 
			theTv.nodes.add n
			recurseHierarchy x n		--Call recursive function on each of the root nodes. 
		)
	)
									
Result:
Event Handlers:
Event handlers for treeView are much the same as listView. Here we are using the mouseDown event to catch the mouse being pressed. We are taking a short cut to get the node that is under the mouse by using a different property of the event argument. In the listView example we used the .X and .Y properties of the argument to create a "system.drawing.point" object to get the node, the .location property returns a "system.drawing.point" for you so you don't have to create it. Also for some reason instead of the method hitTest they changed it to GetNodeAt, but other then that it is just the same.

 

Code:
	on tv mouseDown arg do
	(
		showProperties arg	--Show the properties for the argument
		print arg.location	--Instead of using the X and Y value to create a point the location returns on. 
		print (tv.GetNodeAt arg.location) --Get the node under the mouse
		showProperties (tv.GetNodeAt arg.location)
		print (tv.GetNodeAt arg.location).text	--Get the text
		print (tv.GetNodeAt arg.location).Index	--The index of the node.
	)
									

To make this useful we will want a way to be able to access the object in Max from the treeView, currently we don't have have any way other then the name of the node to connect the node in the tree to the object in Max. We can use the index property of the node that we select. This might not work as you might expect as it would in a listView or listBox. In a list it is a simple case of the order of the list from top to bottom. In a tree each branch of the tree is a new list. In the example above when you select a node in the tree it will print out the index of the node. Note that again all arrays in dotNet are 0 based.

Building Struct Tree:

What we need to do is also build a tree to store all the objects that we are showing in treeView. It is safer to do this then accessing the order of the nodes in the scene since that could change from the last time the treeView was created. We will use a struct and a variable to build the object tree at the same time that we build the node tree. We still start with a struct definition and an array to store the root objects. The struct has two members, obj where an instance of the object will be stored and children where structs that contain the children objects will be stored.

 

Code:
	struct objectNode (obj,children=#())
	local objectTree=#()
									

We need to make changes to the populateTreeView function so that it creates an instance of the struct and adds the object to the obj member and appends it to the objectTree array. The struct instance also needs to be passed to the recurseHierarchy function.

 

Code:
	--Adds root nodes to treeView.
	fn populateTreeView theTv=
	(
		findRootObjs()		--Collect all the root objects.
		
		--Loop through all the objects in the scene. 
		for x in rootObjs do 
		(
			--Create a treeViewNode and add it to the treeView control
			n=(dotNetObject "System.Windows.Forms.TreeNode" x.name) 
			theTv.nodes.add n
			
			objNode=objectNode()		--Create instance of the struct
			objNode.obj=x			--Set the object member
			append objectTree objNode	--Append the objectTree array with the instance of the struct.
			
			recurseHierarchy x n objNode	--Call recursive function on each of the root nodes. 
		)
	)
									

The recurseHierarchy function also needs to create instances of the struct and append the current object to it. The difference is the new struct instance is appended to the children member of the struct instance that was passed into the function. This will build a tree of instances of the objectNode struct that we can traverse to find the object that we want to work with.

 

Code:
	--Recurse hierarchy and add treeview nodes.
	fn recurseHierarchy obj theTvNode objNode=
	(
		for i = 1 to obj.children.count do		--Loop through each of the children
		(
			n=(dotNetObject "System.Windows.Forms.TreeNode" obj.children[i].name)
			theTvNode.nodes.add n
			
			objChildNode=objectNode()	--Create instance of the struct
			objChildNode.obj=obj.children[i]	--Set the object member
			append objNode.children objChildNode	--Append the struct instance with the child struct instance. 
			
			recurseHierarchy obj.children[i] n objChildNode	--Call recursion on each of the children. 
		)
	)
									

Selecting Objects with Treeview:
Now that we have a tree of nodes to select and a tree of structs that match we can use the index of the selected node in the tree to find the object in the struct tree. First we need to create a function called selectObject with one parameter that will take an instance of the treeView node. To know what node we have selected we will need to walk backwards out of the tree collecting all the index values for each node in the branch leading to the one that we have selected. To check the result format the array of index value to the listener. To run the function call it from the treeView on mouseDown event handler and pass in the selected treeView node.

 

Code:
	fn selectObject theTvNode=
	(
		local indexPath=#()	--Array to hold the index of each node in the path.
		while theTvNode!=undefined do	--While loop to walk backwards from the selected node to root. 
		(
			insertItem theTvNode.index indexPath 1	--Insert the index of each node into array at at start.
			theTvNode=theTvNode.parent	--Set theTvNode to the parent node. 
		)
		format "%\n" indexPath
	)
								

 

Listener:
#(0, 0, 0, 2, 0, 1)

The next stage of the selectObject function is to walk back down the objectTree array and find the object that is selected in the treeView. Note that dotNet arrays are 0 based so we will have to add one to each or we will get an error in Max.

First get the root node in objectTree that we are going to start with then loop through all the treeView index values and set ot to the index of the child in the children member of the struct. Once the for loop is complete the remaining struct in ot is the one that holds the object that is selected in treeView. All we have to do is select the object in the obj member of the struct. I have added some error checking around the select just incase the object has been deleted since the tree was created.

 

Code:
	--Select objects function. 
	fn selectObject theTvNode=
	(
		local indexPath=#()	--Array to hold the index of each node in the path.
		while theTvNode!=undefined do	--While loop to walk backwards from the selected node to root. 
		(
			insertItem theTvNode.index indexPath 1	--Insert the index of each node into array at at start.
			theTvNode=theTvNode.parent	--Set theTvNode to the parent node. 
		)
		
		ot=objectTree[indexPath[1]+1]	--Get the root node in objectTree to start with
		for i = 2 to indexPath.count do	--Loop trough all the index values accept the first.
		(
			ot=ot.children[indexPath[i]+1]	--Set ot to each of the structs by index.
		)
		if isValidNode ot.obj	then select ot.obj--Select the object that is left. 
	)