How To > Use ActiveX Controls > Develop a Scene Browser - 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.
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
ActiveX Controls in MAXScript Rollouts
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!
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.
Next, we create an empty bitmap with the size of a single 16x15 pixels icon
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.
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!
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.
Since we have the file name defined, we can save the single icon to disk.
Then we close both the original multi-icon file and the temporary single icon file.
As a result of the function, we return the filename of the new single icon.
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.
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.
We also want the tree to be alphabetically sorted.
We define the size of the icon image to be the same as the icon we are going to grab from the icons file.
This is the default location of the shipping icon files.
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).
Same for the third image which will be used for light, but we ask for the third icon of the Lights icons file (Omni).
The camera will be image number 4. We grab the second icon from the Cameras icons library.
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!
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.
Geometry objects will use image 2 as icon,
All others will use the Geometry icon until they get their own icons.
Instead of setting the icon index to 0 as we did in Part One, we set the icon to the value defined above...
We wanted all levels of the hierarchy to be sorted alphabetically.
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...
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.
If the user changed the state of the checkbox of a node...
...we try to figure out which scene node it represents by converting the text label to the actual object name...
...and if the attempt was successful,
...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!