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

new.gif NEW Tutorial:

This second tutorial will demonstrate how to customize the Listview ActiveX Control defined in Part One and update the display automatically when the selection set changes.

Related topics:

ActiveX Controls in MAXScript Rollouts

ListView ActiveX Control

Listview ActiveX functions

ListView ActiveX Control Example

General Event Callback Mechanism

Callbacks.removeScripts

 

Natural Language

MAXScript

macroScript SceneListView category:"HowTo"

(

global listview_rollout

rollout listview_rollout "ListView Selected"

(

fn initListView lv =

(

lv.gridLines = true  

lv.View = #lvwReport  

lv.fullRowSelect = true 

 

 lv.backColor = color 225 215 210

 lv.checkboxes = true

 

 

layout_def = #(#("On",28), #("Object Name",120), #("Object Class",80), #("Verts",45), #("Faces",45), #("Material",120))

 

for i in layout_def do

(

column = lv.ColumnHeaders.add()

column.text = i[1]

)

 

 LV_FIRST = 0x1000

 LV_SETCOLUMNWIDTH = (LV_FIRST + 30)

 for i = 0 to layout_def.count-1 do

  windows.sendMessage lv.hwnd LV_SETCOLUMNWIDTH i layout_def[1+i][2]

 

fn fillInSpreadSheet lv =

(

 lv.ListItems.clear()

for o in selection do

(

li = lv.ListItems.add()

li.checked = o.Renderable

sub_li = li.ListSubItems.add()

sub_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

on listview_rollout close do callbacks.removeScripts #selectionSetChanged id:#SceneListView

)

try(destroyDialog listview_rollout)catch()

createDialog listview_rollout 500 200

callbacks.addScript #selectionSetChanged "listview_rollout.fillInSpreadSheet listview_rollout.lv_objects" id:#SceneListView

)

 

Result

ListViewSelected_Part2.gif

 

Step-By-Step

macroScript SceneListView category:"HowTo"

(

global listview_rollout

In order to be able to access the rollout from outside of the macroScript scope (in our case a callback script), we have to define the rollout variable as global.

 

rollout listview_rollout "ListView Selected"

(

fn initListView lv =

(

lv.gridLines = true  

lv.View = #lvwReport  

lv.fullRowSelect = true 

 

lv.backColor = color 225 215 210

We assign a new color to the backColor property of the Listview.

NOTE

Microsoft color values are expected as BGR (Blue/Green/Red) and NOT RGB as 3ds Max and MAXScript provide them. In MAXScript, the above color would appear as pale pink. You can visualize these colors by using something like

display ( bitmap 128 128 color:(color 225 215 210) ) --pale pink

display ( bitmap 128 128 color:(color 215 210 225) ) --pale blue

 

 

 

lv.checkboxes = true

We will also enable the built-in checkboxes of the Listview control and use them to represent the renderable node-level property.

 

layout_def = #(#("On",28), #("Object Name",120), #("Object Class",80), #("Verts",45), #("Faces",45), #("Material",120))

We will extend the existing version of the layout definition array. Instead of storing only the names of the colums, we will store sub-arrays containing both the name and the width of the column. In the future, you could store any additional per-column data like bold text Boolean flag, foreground color of the text etc.)

 `

for i in layout_def do

(

column = lv.ColumnHeaders.add()

column.text = i[1]

)

 

LV_FIRST = 0x1000

LV_SETCOLUMNWIDTH = (LV_FIRST + 30)

We define two user variables which will contain the first column's address and the message id to be sent to the windows handle of the Listview control responsible for setting the column width. This is "esoteric knowledge" available from literature on general Microsoft Windows and ActiveX Controls programming. Just use it the way it is provided here.

for i = 0 to layout_def.count-1 do

windows.sendMessage lv.hwnd LV_SETCOLUMNWIDTH i layout_def[1+i][2]

Now we loop from 0 to the number of columns minus one. This is because the windows.sendMessage method expects the index of the column to be set as a 0-based index, but MAXScript array indices are 1-based.

The windows.sendMessage method expects the windows handle of the ActiveX control (lv.hwnd), the message to be sent, the index of the column to set the width and the width value (which is stored in the 1-based array at index (i+1), in the second item of the sub-array)

 

fn fillInSpreadSheet lv =

(

lv.ListItems.clear()

Before we fill in the data into the Listview, we should make sure the list is emptied first. This is because this time around, the list will be updated on the fly without closing and reopening the dialog!

 

for o in selection do

(

li = lv.ListItems.add()

 

li.checked = o.Renderable

The first column will contain only the checkbox. We set the .checked property to the boolean value returned by the node-level .renderable property of the current object.

sub_li = li.ListSubItems.add()

The former first column with the name of the object is now the second column, so we have to create a sub list item for it.

sub_li.text = o.name

We set the .text property of the sub list item to the name of the object just like before.

 

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

on listview_rollout close do callbacks.removeScripts #selectionSetChanged id:#SceneListView

 

Callbacks.removeScripts

Whenever the user closes the dialog by either pressing the [X] button in the upper right corner of the titlebar or by calling the MacroScript again, we will have to make sure that the callback we will register to update the display when the selection set changes is removed. Otherwise, selecting objects in the scene would cause the callback to try to access an already closed rollout and throw an error!

)

try(destroyDialog listview_rollout)catch()

createDialog listview_rollout 500 200

callbacks.addScript #selectionSetChanged "listview_rollout.fillInSpreadSheet listview_rollout.lv_objects" id:#SceneListView

Finally, we register the callback to update the Listview whenever the user changes the selection set in the scene.

The callbacks.addScript tells MAXScript that we are registering a new callback.

#selectionSetChanged is the name of the notification message broadcast by 3ds Max whenever the selection changes.

The string contains the actual script executed when the callback is activated, it accesses the Listview as a property of the now global rollout definition and calls the fillInSpreadSheet function we defined.

The id:#SceneListView is a user-defined name to be able to affect only this special callback (for example in the callbacks.removeScripts call above) without affecting other callbacks defined by other developers or the shipping 3ds Max version itself.

 

)

 

Using the Script

After evaluating the script, the SceneListView ActionItem in the "HowTo" category defined by the first part of the tutorial will be updated.

Press the button/select the menu item or press the keyboard shortcut corresponding to the SceneListView script - the dialog with the listView ActiveX control should appear. If there are no objects selected, the list will be empty.

Now select some object and watch the list updating automatically. Change the selection set again and the list will update dynamically! Select an object, right-click, go to Properties... and uncheck the Renderable checkbox. Note that the checkbox in the Listview will be unchecked, too!

 

Where to go from here?

The next logical step would be to add an even handler to link the checkbox in the Listview bi-directionally to the .Renderable property, so changing the state in the Listview would affect the node property, too!

Other things to try include adding more column definitions to the layout array to show other object properties you might be interested in.

 

See also

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

"How To" Tutorials Index Page