TreeView Controls are among the most widely used ActiveX controls in scripted 3ds Max tools. Converting such scripts to use DotNet TreeView controls is a relatively straightforward process.
Let's take as the base for our conversion example the simple script available in the topic How To ... Develop a Scene Browser using TreeView ActiveX Control - Part One
In the following script, you can compare the original ActiveX code which is now remarked and in green to the new commented DotNet code.
CONVERSION OF THE SCRIPT - PART ONE
macroScript SceneTreeView category:"DotNet" ( rollout treeview_rollout "TreeView Scene Browser" ( /* ActiveXVersion: fn initTreeView tv = ( tv.Indentation = 28*15 tv.LineStyle = #tvwRootLines ) */ fn initTreeView tv = ( tv.Indent= 28 --property name is different and value is in pixels --the default style already has root lines. ) /* ActiveXVersion: fn addChildren tv theNode theChildren = ( for c in theChildren do ( newNode = tv.Nodes.add theNode.index 4 "" c.name 0 addChildren tv newNode c.children ) ) */ fn addChildren theNode theChildren = ( for c in theChildren do ( newNode = theNode.Nodes.add c.name --add to the parent! newNode.tag = dotNetMXSValue c --.tag can contain a MXS value addChildren newNode c.children --recursive call for new node ) ) /* ActiveX Version: fn fillInTreeView tv = ( theRoot = tv.Nodes.add() theRoot.text = "WORLD ROOT" rootNodes = for o in objects where o.parent == undefined collect o addChildren tv theRoot rootNodes ) */ fn fillInTreeView tv = ( theRoot = tv.Nodes.add "WORLD ROOT" --add parent node rootNodes = for o in objects where o.parent == undefined collect o addChildren theRoot rootNodes --no need to pass theTreeView ) /*ActiveX Version: activeXControl tv "MSComctlLib.TreeCtrl" width:190 height:290 align:#center */ dotNetControl tv "TreeView" width: 190 height:290 align:#center spinner spn_indent "Indentation" range:[0,100,28] type:#integer fieldwidth:40 /*ActiveX Version: on tv nodeClick theNode do try(select (getNodeByName theNode.text))catch() */ on tv Click arg do ( --First get theTreeView node below the mouse cursor --The arg argument has properties .x and .y with the current pos. --Use showProperties arg to see what is available... --We use the TreeView method GetNodeAt to see what was clicked: hitNode = tv.GetNodeAt (dotNetObject "System.Drawing.Point" arg.x arg.y) if hitNode != undefined do --if a TreeView node was clicked, try(select hitNode.tag.value)catch(max select none) --...we try to select the object stored as value in the .tag ) --We change the .indentation to .indent: on spn_indent changed val do tv.indent = val --The rest of the script does not require changes: on treeview_rollout open do ( initTreeView tv fillInTreeView tv ) ) try(destroyDialog treeview_rollout)catch() createDialog treeview_rollout200 320 )
Now let's take a look at the second part of the TreeView example found in the topic How To ... Develop a Scene Browser using TreeView ActiveX Control - PartTwo. In this example, we will convert the ActiveX ImageList control to a DotNet ImageList object, will add icons to the TreeView, set the node's color to the wireframe color of the scene object and also add checkboxes to control the hidden state of the objects.
CONVERSION OF THE SCRIPT - PART TWO
macroScript SceneTreeView category:"DotNet" ( --We create an ImageList object to hold all icons for the TreeView --in the local scope of the MacroScript. In he case of ActiveX, --the ImageList had to be a UI control inside the rollout. --It is possible to create a local DotNet object inside the rollout --and pass the ImageList as parameter to the functions, but it is --more convoluted. In this version, all functions "see" the --ImageList DotNet object in the higher scope: ilTv = dotNetObject "System.Windows.Forms.ImageList" ilTv.imageSize = dotNetObject "System.Drawing.Size" 16 15 rollout treeview_rollout "TreeView Scene Browser" ( /* ActiveX Version: fn getIconFromBitmap thePath number = ( tempBmp = openBitmap thePath iconBmp = bitmap 16 15 for v = 0 to 14 do setPixels iconBmp [0,v] (getPixels tempBmp [(number-1)*16, v] 16) iconBmp.filename = getDir #image +"/_temp.bmp" save iconBmp close iconBmp close tempBmp getDir #image +"/_temp.bmp" ) */ --When creating icons for DotNet, it is necessary to create --one bitmap for each icon, because the DotNet image list will --lock the file for the duration of the script execution. --The original script used a single file to write temporarily --all icon bitmaps. This version writes a separate bitmap with the --name passed as third argument. The function only creates bitmaps --if they do not exist yet to avoid bitmap access errors. fn getIconFromBitmap thePath number iconFileName = ( theFileName = getDir #image +"\\icon_"+ iconFileName +".bmp" if not doesFileExist theFileName do ( tempBmp = openBitmap thePath iconBmp = bitmap 16 15 for v = 0 to 14 do setPixels iconBmp [0,v] (getPixels tempBmp [(number-1)*16, v] 16) iconBmp.filename = theFileName save iconBmp close iconBmp close tempBmp ) img = dotNetClass "System.Drawing.Image" --create an image ilTv.images.add (img.fromFile theFileName) --add to the list ) /* ActiveX Version: fn initTreeView tv ilTv = ( tv.Indentation = 28*15 tv.LineStyle = #tvwRootLines tv.checkboxes = true tv.sorted = true ilTv.imagewidth = 16 ilTv.imageheight = 15 iconDir = (getDir #ui) + "\\icons" ilTv.listImages.add 1 #root (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 2)) ilTv.listImages.add 2 #geomnode (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 1)) ilTv.listImages.add 3 #light (loadPicture (getIconFromBitmap (iconDir + "\\Lights_16i.bmp") 3)) ilTv.listImages.add 4 #camera (loadPicture (getIconFromBitmap (iconDir + "\\Cameras_16i.bmp") 2)) ilTv.listImages.add 5 #helper (loadPicture (getIconFromBitmap (iconDir + "\\Helpers_16i.bmp") 1)) tv.imageList = ilTv ) */ fn initTreeView tv = ( tv.Indent= 28 tv.CheckBoxes = true --same as in ActiveX --This is the icons directory. --We could also use the new colorman.resolveIconFolder() method --to get the path to the actual system icons... iconDir = (getDir #ui_ln) + "icons\\" --We call our function for each icon, this time also passing a --third argument with the icon name suffix. getIconFromBitmap (iconDir + "Standard_16i.bmp") 2 "Sphere" getIconFromBitmap (iconDir + "Standard_16i.bmp") 1 "Box" getIconFromBitmap (iconDir + "Lights_16i.bmp") 3 "Light" getIconFromBitmap (iconDir + "Cameras_16i.bmp") 2 "Camera" getIconFromBitmap (iconDir + "Helpers_16i.bmp") 1 "Helper" --At the end, we assign the ImageList to the TreeView. tv.imageList = ilTv ) /* ActiveX Version: fn addChildren tv theNode theChildren = ( for c in theChildren do ( theIcon = case superclassof c of ( GeometryClass: 2 Light: 3 Camera: 4 Helper: 5 Default:2 ) newNode = tv.Nodes.add theNode.index 4 "" c.name theIcon theNode.sorted = true newNode.checked = not c.isHidden newNode.forecolor = color c.wirecolor.b c.wirecolor.g c.wirecolor.r addChildren tv newNode c.children ) ) */ fn addChildren theNode theChildren = ( for c in theChildren do ( newNode = theNode.Nodes.add c.name newNode.tag = dotNetMXSValue c --By default, all nodes will use icon 0 (the first one) unless --specified otherwise via the .iconIndex and .selectedIconIndex --properties.We set both of them to the icon corresponding to --the superclass of the scene object: newNode.imageIndex = newNode.selectedImageIndex = case superclassof c of ( Default: 1 Light: 2 Camera: 3 Helper: 4 ) newNode.checked = not c.isHidden--same as in ActiveX --For the color, we create a DotNet color class from the --wirecolor of the object and assign to the .forecolor of --the TreeView node: newNode.forecolor = (dotNetClass "System.Drawing.Color").fromARGB c.wirecolor.r c.wirecolor.g c.wirecolor.b addChildren newNode c.children ) ) --Since every node uses icon with index 0 unless specified otherwise --the Root Node will use the first icon by default. fn fillInTreeView tv = ( theRoot = tv.Nodes.add "WORLD ROOT" rootNodes =for o in objects where o.parent == undefined collect o addChildren theRoot rootNodes ) dotNetControl tv "TreeView" width:290 height:490 align:#center on tv Click arg do ( hitNode = tv.GetNodeAt (dotNetObject "System.Drawing.Point" arg.x arg.y) if hitNode != undefined do try(select hitNode.tag.value)catch(max select none) ) /* on tv NodeCheck theNode do ( theSceneNode = (getNodeByName theNode.text) if theSceneNode != undefined do theSceneNode.isHidden = not theNode.checked ) */ --NOTE that in the case of ActiveX controls, we were using the name --displayed by the TreeView node. In the case of DotNet, we are --using the actual MAXScript value pointing at the scene object. --The arg argument provides a .node property containing the node --that was checked. We use the .tag property to access the --DotNetMXSValue stored in the TreeView node and then the .value to --get the MAXScript value stored in it. Then we set the isHidden --property of the scene object to the inverse of the checked state --of the TreeView node: on tv AfterCheck arg do try (arg.node.tag.value.isHidden = not arg.node.checked)catch() on treeview_rollout open do ( initTreeView tv fillInTreeView tv ) ) try (destroyDialog treeview_rollout)catch() createDialog treeview_rollout 300 500 )