Converting ActiveX TreeView Control to DotNet TreeView Control

new.gif NEW in 3ds Max 9: 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:

macroScript SceneTreeView category:"DotNet"

(

rollout treeview_rollout "TreeView Scene Browser"

(

 

/* ActiveX Version:

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.

)

 

/* ActiveX Version:

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 the TreeView

)

 

/* 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 the TreeView 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_rollout 200 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 - Part Two . 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.

 

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) + "\\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

)

 

 

See also

DotNet In MAXScript

How To ... Develop a Scene Browser using TreeView ActiveX Control - Part One

How To ... Develop a Scene Browser using TreeView ActiveX Control - Part Two