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

The second part of the tutorial will demonstrate how to customize the TreeView ActiveX Control defined in Part One to include icons by object class, display wireframe colors and control the hidden state of scene nodes.

Note:

ActiveX Controls have been deprecated by Microsoft in the latest versions of the Windows operating system in favor of the DotNet framework and its controls.

While MAXScript still supports ActiveX controls, these have to be installed and registered on the system to be accessible to MAXScript.

As a replacement of ActiveX controls, MAXScript supports DotNet controls in 3ds Max 9 and higher.

Please see the topic Converting ActiveX TreeView Control to DotNet TreeView Control

Related topics:

ActiveX Controls in MAXScript Rollouts

TreeView ActiveX Control

NATURAL LANGUAGE

Extend the existing macroScript.

Define a function to grab a single icon from the shipping icon files of 3ds Max

Extend the initTreeView function to generate icons for most object types

Assign an icon to each node created for each scene node

Set the color of each node to the wireframe color of the scene node

Set the checked state of each node to the opposite of the hidden state of the scene node

Enable checkboxes in the TreeView

Define another ActiveX control to hold and manage the icons to be displayed

Add an event handler to hide/unhide objects when the corresponding checkboxes are unchecked/checked

SCRIPT:

   macroScript SceneTreeView category: "HowTo"
   (
   rollout treeview_rollout "TreeView Scene Browser"
   (
   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"
   )

   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 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 fillInTreeView tv =
   (
    theRoot = tv.Nodes.add()
    theRoot.image = 1
    theRoot.text ="WORLD ROOT"
    rootNodes = for o in objects where o.parent == undefined collect o
    addChildren tv theRoot rootNodes
   ) 

   activeXControl tv "MSComctlLib.TreeCtrl"width:290 height:490 align:#center
   activeXControl ilTv "MSComctlLib.ImageListCtrl" height:0 width:0
   on tv NodeCheck theNode do
   (
    theSceneNode = (getNodeByName theNode.text)
    if theSceneNode != undefined do
     theSceneNode.isHidden = not theNode.checked
   ) 

   on tv nodeClick theNode do try(select (getNodeByName theNode.text))catch()
   on treeview_rollout open do
   (
    initTreeView tv ilTv
    fillInTreeView tv
   ) 
   )
   try(destroyDialog treeview_rollout)catch()
   createDialog treeview_rollout 300 500
   )

RESULT:

Step-By-Step

macroScript SceneTreeView category:"HowTo"
(
rollout treeview_rollout "TreeView Scene Browser"
( fn getIconFromBitmap thePath number = (

This new function will let us grab and reuse the icons shipping with 3ds Max. The arguments passed to the function are the path of the icons bitmap file and the icon number to get. We assume that we will use the 16x15 small icons only. The function could be changed to support the 24x24 version of the icons, but the TreeView wouldn't look very nice with icons of that size!

tempBmp = openBitmap thePath

First, we open the bitmap containing the path to the icons file. Icon files can contain an arbitrary number of icons. The small, 16x15 pixels icon files have a height of 15 pixels and width equal to the number of icons multiplied by 16.

iconBmp = bitmap 16 15

Next, we create an empty bitmap with the size of a single 16x15 pixels icon

for v = 0 to 14 do

The loop will go from 0 to 14 since pixel access in bitmaps is 0-based, and we have to go from 0 to 15 minus one.

setPixels iconBmp [0,v] (getPixels tempBmp [(number-1)*16, v] 16)

For each row in the icon, we use getPixels to grab the 16 pixels starting at the beginning of the icon.(Number-1)*16 returns 0 for the first icon, 16 for the second icon, 32 for the third and so on. Then we assign the whole row of 16 pixels to the iconBmp bitmap defined before, starting at the left-most pixel (0) and going through the same number of lines (v). The result is the icon with the requested number being copied into the new empty bitmap!

iconBmp.filename =GetDir #image +"/_temp.bmp"

Now we set the name of the new single icon bitmap to a remporary bitmap file in the \Images sub-directory of 3ds Max. This is necessary because the loadPicture method converting a bitmap to an OLE-capable icon requires a saved .BMP or .ICO file, and only bitmaps with the .filename property set can be saved to disk.

save iconBmp

Since we have the file name defined, we can save the single icon to disk.

close iconBmp close tempBmp

Then we close both the original multi-icon file and the temporary single icon file.

GetDir #image +"/_temp.bmp"

As a result of the function, we return the filename of the new single icon.

 )

fn initTreeView tv ilTv = (

This is the existing TreeView initialization function, but this time it gets a second argument, which will be the ImageListCtrl ActiveX control responsible for the icon management.

tv.Indentation = 28*15
tv.LineStyle = #tvwRootLines

tv.checkboxes = true

This time, we will want to have checkboxes in front of every node. They will be used to display and control the Hidden state of the scene nodes.

tv.sorted = true

We also want the tree to be alphabetically sorted.

ilTv.imagewidth = 16 ilTv.imageheight = 15

We define the size of the icon image to be the same as the icon we are going to grab from the icons file.

iconDir = (getDir #ui) + "\\icons"

This is the default location of the shipping icon files.

ilTv.listImages.add 1 #root (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 2))

We call the .add() method of the .listImages property of the ImageListCtrl which is used to manage the icons. The first image we add will be used for the root node only. We call the getIconFromBitmap function we defined earlier and pass the icon file containing Standard geometry primitives and the icon number 2 (Sphere). The resulting path is passed to the loadPicture method which opens the temporary icon .bmp file and assigns the resulting OLE-capable icon image to the ImageListCtrl.

ilTv.listImages.add 2 #geomnode (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 1))

We repeat the whole again, but this time we create the image number 2 which will be used for geometry nodes. We grab icon number 1 (Box).

ilTv.listImages.add 3 #light (loadPicture (getIconFromBitmap (iconDir + "\\Lights_16i.bmp") 3))

Same for the third image which will be used for light, but we ask for the third icon of the Lights icons file (Omni).

ilTv.listImages.add 4 #camera (loadPicture (getIconFromBitmap (iconDir + "\\Cameras_16i.bmp") 2))

The camera will be image number 4. We grab the second icon from the Cameras icons library.

ilTv.listImages.add 5 #helper (loadPicture (getIconFromBitmap (iconDir + "\\Helpers_16i.bmp") 1))

Finally, we define a fifth image which grabs the first icon from the Helpers icons file (Dummy).

There are some more classes that were not defined yet (Shapes, Space Warps), you can try to implement your own icons in the same way by extending the above code!

tv.imageList = ilTv 

At the end, we assign the ImageListCtrl Control to the .imageList property of the TreeView. This makes all the above images available as icons in the TreeView control!

) 

fn addChildren tv theNode theChildren =
(
for c in theChildren do
( theIcon = case superclassof c of 

Now that we have icons in the control, we can check the superclass of the 3ds Max scene node being processed by the function and define the right icon based on the type. The variable theIcon will contain an integer corresponding to the icon to be used.

 ( GeometryClass: 2

Geometry objects will use image 2 as icon,

Light: 3

Lights will use image 3,

Camera: 4

Cameras will use image 4,

Helper: 5

Helpers will use image 6,

Default:2

All others will use the Geometry icon until they get their own icons.

) newNode = tv.Nodes.add theNode.index 4 "" c.name theIcon

Instead of setting the icon index to 0 as we did in Part One, we set the icon to the value defined above...

theNode.sorted = true

We wanted all levels of the hierarchy to be sorted alphabetically.

newNode.checked = not c.isHidden

The checked state of the node will be the inverse of the .isHidden property (which is true when the node is hidden). Since we want to check the box when the node is NOT hidden, we tell exactly this...

newNode.forecolor = color c.wirecolor.b c.wirecolor.g c.wirecolor.r

Since we have complete control over the color of each node in the TreeView, why not grab the color of the wireframe display and assign to the .forecolor property? This gives us a colorful reference to the color of the scene objects!

You could enhance this to grab the diffuse color of the material in the cases where the object has a Standard material assigned, or use the color to represent some other property... In addition, you could also set the .bold property of the TreeView node to true to represent some other property of the 3ds Max objects...

addChildren tv newNode c.children
)
)

fn fillInTreeView tv =
(
theRoot = tv.Nodes.add()

theRoot.image = 1

We already defined the first image of the ImageListCtrl to show a sphere icon. We will use it to represent the root node.

theRoot.text = "WORLD ROOT"
rootNodes = for o in objects where o.parent == undefined collect o
addChildren tv theRoot rootNodes
) 

activeXControl tv "MSComctlLib.TreeCtrl" width:290 height:490 align:#center activeXControl ilTv "MSComctlLib.ImageListCtrl "height:0 width:0

This is the ImageList ActiveX Control used by the TreeView to manage its icons. It needs no rollout representation - we set the height and width to 0.

on tv NodeCheck theNode do (

If the user changed the state of the checkbox of a node...

theSceneNode = (getNodeByName theNode.text)

...we try to figure out which scene node it represents by converting the text label to the actual object name...

if theSceneNode != undefined do

...and if the attempt was successful,

theSceneNode.isHidden = not theNode.checked

...we set the .isHidden property of the scene node to the inverted value of checked state of the TreeView node. If the user unchecked the checkbox, theNode.checked will return false, which will be converted to true by not and assigned to the hidden property, effectively hiding the node!

) 

on tv nodeClick theNode do try(select (getNodeByName theNode.text))catch()
on treeview_rollout open do
(
initTreeView tv ilTv
fillInTreeView tv
)
)
try(destroyDialog treeview_rollout)catch()
createDialog treeview_rollout 300 500
)