How To ... Develop a Selected Objects Inspector using ListView ActiveX Control - Part One

This tutorial will demonstrate how to set up an ActiveX Control in a MAXScript rollout to display details about selected scene objects.

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 ListView Control to DotNet ListView Control

Related topics:

ActiveX Controls in MAXScript Rollouts

ListView ActiveX Control

Listview ActiveX functions

ListView ActiveX Control Example

NATURAL LANGUAGE

Create a simple macroScript.

Define a rollout which will be used to create a floating dialog.

The only UI control in the rollout will be a Listview ActiveX Control.

Create a function to define the layout and appearance of the Listview.

Create another function to fill in the data of selected objects into the list

The two functions will be called by the on open event handler whenever the dialog is created (and thus the rollout is opened).

SCRIPT:

   macroScript SceneListView category:"HowTo"
   (
   rollout listview_rollout "ListView Selected"
   (
   fn initListView lv =
   (
   lv.gridLines = true  
   lv.View = #lvwReport  
   lv.fullRowSelect = true 

   layout_def = #("Object Name","Object Class","Verts","Faces","Material")

   for i in layout_def do
   (
   column = lv.ColumnHeaders.add()
   column.text = I
   ) 
   ) 

   fn fillInSpreadSheet lv =
   (
   for o in selection do
   (
   li = lv.ListItems.add()
   li.text = o.name
   sub_li = li.ListSubItems.add()
   sub_li.text = (classof o) as string
   sub_li = li.ListSubItems.add()
   sub_li.text =try((o.mesh.numverts) as string)catch("--")
   sub_li = li.ListSubItems.add()
   sub_li.text =try((o.mesh.numfaces) as string)catch("--")
   sub_li = li.ListSubItems.add()
   sub_li.text = (o.material)as string
   )
   ) 

   activeXControl lv_objects "MSComctlLib.ListViewCtrl" width:490 height:190 align:#center
   on listview_rollout open do
   (
   initListView lv_objects
   fillInSpreadSheet lv_objects
   ) 
   )
   createDialog listview_rollout500 200
   )

RESULT:

Step-By-Step

macroScript SceneListView category:"HowTo" (

We start by defining a simple MacroScript with the name SceneListview which will appear in the category "HowTo". Simple (old-style) macroScripts do not have on execute do or on isEnabled event handlers. The code inside the MacroScript definition is executed immediately when the ActionItem (button, menu item or keyboard shortcut) representing the MacroScript is activated.

rollout listview_rollout "ListView Selected" (

This is the rollout to be created when the MacroScript is executed. It will display the title "ListView Selected". The variable listview_rollout will be local to the macroScript and will be used later on to create a dialog out of the rollout definition.

fn initListView lv = (

This is the function which will perform the initialization of the ListView ActiveX control we will create. The control itself will be passed as argument to the function.

lv.gridLines = true

We want the ListViewControl to display grid lines.

lv.View = #lvwReport

We also want to see the column headers, so we define the view type as lvwReport.

lv.fullRowSelect = true 

By setting this property to true, we ensure that selecting a row will select across all columns.

layout_def = #("Object Name","Object Class","Verts","Faces","Material")

This user variable is assigned an array containing the names of the columns to be created.

for i in layout_def do (

The i loop will go through all elements of the array defined above. Inside the loop, a new column will be created for each entry in the array.

column = lv.ColumnHeaders.add()

By calling the function add() in the ColumnHeaders property of the listview, we add a new column to the list. A reference to the column is returned and written in the user variable "column", so we can now access properties of the column.

column.text =i 

For example, we can set the text in the column header to the string stored in the array we are looping through.

)

This is the end of the i loop..

)

This is the end of the function.

fn fillInSpreadSheet lv =
(

This is the function which will fill in the data of the selected scene objects into the Listview ActiveX control. The control itself will be passed as argument to the function.

for o in selection do
(

This loop will iterate through all objects currently selected in the scene. The variable o will contain one object at a time, the loop will repeat as ofter as there are objects in the selection set.

li = lv.ListItems.add()

We add a new row to the Listview.

li.text = o.name

Then we set the text displayed in the first column of the list to the name of the object.

sub_li = li.ListSubItems.add()

Now we add a new sub-item (which is basically the following column) by calling the .add() method in the ListSubItems property of the current listItem (which represents the first column). The result is written into the user variable sub_li

sub_li.text = (classof o) as string

We can now set the .text property of the new column to the class of the current object. Since the result of classof() is not a string, we have to explicitly convert it to string before assigning it to the .text property.

sub_li = li.ListSubItems.add()
sub_li.text = try((o.mesh.numverts) as string)catch("--")

We create a third column the same way as above and assign the number of vertices in the mesh of the current object to the .text property of the new column. Since the number of vertices is returned as an integer, we have to convert it to a string before assigning it to the .text property.

Some of the selected objects might not have a .mesh property (for example lights, helpers, cameras, targetObjects etc.) In these cases, trying to access o.mesh.numverts would cause an error. For these cases, we put the try()catch() error trap. If the attempt to read the vertex count fails, we assign the string "--" to the .text property of the sub-list item instead.

sub_li = li.ListSubItems.add()
sub_li.text = try((o.mesh.numfaces) as string)catch("--")

We create another column the same way as above and assign the number of f aces in the mesh of the current object to the .text property of the new column. Since the number of faces is returned as an integer, we have to convert it to a string before assigning it to the .text property.

Some of the selected objects might not have a .mesh property (for example lights, helpers, cameras, targetObjects etc.) In these cases, trying to access o.mesh.numfaces would cause an error. For these cases, we put the try()catch() error trap. If the attempt to read the face count fails, we assign the string "--" to the .text property of the sub-list item instead.

sub_li = li.ListSubItems.add()
sub_li.text = (o.material) as string

Finally, we create another column the same way as above and assign the material of the current object to the .text property of the new column. Objects without a material will display "undefined" in the column.

) 
)

activeXControl lv_objects "MSComctlLib.ListViewCtrl" width:490 height:190 align:#center

This is it - the ActiveX control we have been processing in the above functions! It is the only user interface element in the rollout. It is centered to the rollout and slightly smaller than the dialog dimensions.

on listview_rollout open do
(

When the rollout is opened for display as a dialog, this event handler will be called.

initListView lv_objects
fillInSpreadSheet lv_objects

We can use the event to both initialize and fill in the listview with the currently selected objects' data.

) 

This is the end of the on open event handler.

)

This is the end of the rollout definition.

try(destroyDialog listview_rollout)catch()

Before creating the dialog, we try to close it. Since the variable listview_rollout is local to the MacroScript, calling the MacroScript multiple times will always access the same variable and will be able to close a previously opened dialog before creating a new one.

createDialog listview_rollout 500 200

At the end, we create the dialog using the rollout we defined above.

)

Using the Script

After evaluating the script, you will find a new ActionItem in the "HowTo" category of the Customize User Interface dialog. Assign it to a toolbar, menu, QuadMenu or keyboard shortcut to be able to access it quickly.

Select some scene objects and press the button/select the menu item or press the keyboard shortcut - a new dialog with a listView ActiveX control displaying the name, class, vertex and face count and assigned material should appear.

Select some other object and call the script again - the old dialog will be closed, a new one will be opened and will display the data of the new selection.

What's Next?

This is a very basic version of the script. To see how it can be further improved and customized, see How To ... Develop a Selected Objects Inspector using ListView ActiveX Control - Part Two