Down Load: Source Code
--Destroy dialog if it already exists. try(destroyDialog theRollout)catch() --Create a rollout rollout theRollout "The Rollout" width:300 ( --Create the dotNet listview control dotNetControl lv "system.windows.forms.listView" ) --Create a dialog and assign the rollout to it. createDialog theRollout
Result:

Notice that this doesn't look much like a fancy list box. There is a big difference between the Max Script listBox and the dotNet listView. The Max list box doesn't have many options, you can really only list strings in a very simple list and there are only a couple of properties and two events. The multiListBox as a few more options but again you can't do all that much with it.
With the dotNet listView there are so many options that we need to set it up before we can even start to display any information in it. It is best to do this with an initLv function where we can have all the display defaults listed so we can use the presets for other listViews
I have also added an on open event handler for the rollout that calls an initLv function. The initLv function is where we will set up all of the default properties for the look and feel of the listView.
Once the dialog is open press the test button and have a look in the listener at all the possible
properties a listView has. We are only interested in four of them at this point. Three are very obvious
as to how to set them up. For instance ".FullRowSelect :
The one property that isn't as obvious is ".View :
showProperties (dotNetClass "system.windows.forms.view")
and press enter and you can see the possible properties for the "system.windows.forms.view" object. in our case we are looking for .Details. At least one of these needs to be supplied for the listView to show anything at all.
--Destroy dialog if it already exists. try(destroyDialog theRollout)catch() --Create a rollout rollout theRollout "The Rollout" width:300 ( --Create the dotNet listview control dotNetControl lv "system.windows.forms.listView" height:200 --Create a button for testing. button testButton "Test" on testButton pressed do ( clearListener() --Clear the listener. format "Props\n" --Format a header so we know what we are looking at below. showProperties lv --Show the properties of the listView control. ) fn initLv theLv= ( --Setup the forms view theLv.view=(dotNetClass "system.windows.forms.view").details theLv.FullRowSelect=true --Set so full width of listView is selected and not just first column. theLv.GridLines=true --Show lines between the items. theLv.MultiSelect=true --Allow for multiple selections. ) on theRollout open do ( initLv lv ) ) --Create a dialog and assign the rollout to it. createDialog theRolloutResult:
on testButton pressed do ( clearListener() --Clear the listener. format "Props\n" --Format a header so we know what we are looking at below. showProperties lv.Columns --Show the properties of the listView control. format "\nMethods\n" --Format a header so we know what we are looking at below. showMethods lv.Columns --Show the properties of the listView control. )
When looking through the methods of the columns property we can see that there is a .add method. This method will allow us to add column headers, name them and even define the width. There are several ways the add method can be used so it is some trial and error is needed to see what will work the way that you need.
.<System.Windows.Forms.ColumnHeader>Add <System.String>text <System.Int32>width
Next we can write a function that will will add the column headers. I have added two arguments to it, first takes the listview control and the second is an array or string values that will be the names of the columns. The first line in the function calculates the width of the columns so that it fills the full width of the control. You could also pass an array of integer values to the function that would define a width for each column.
--Add columns. fn addColumns theLv columnsAr= ( w=(theLv.width/columnsAr.count)-1 for x in columnsAr do ( theLv.columns.add x w ) )
Then we need to call the function, pass in the control that we want to add columns to with a list of column names.
on theRollout open do
(
initLv lv
addColumns lv #("Object","Class","Wire Color")
)
Result:
on testButton pressed do ( clearListener() --Clear the listener. format "Props\n" --Format a header so we know what we are looking at below. showProperties lv.items --Show the properties of the listView control. format "\nMethods\n" --Format a header so we know what we are looking at below. showMethods lv.items --Show the properties of the listView control. )
The items property will print out a line that looks like this. Again what it is looking for is a new dotNet object to be created and placed in each row. The items property it self is read only but it has methods that are used to populate the row of data.
.Items : <System.Windows.Forms.ListView+ListViewItemCollection>, read-only
The methods for the items property has again many possible options, the second from the top is this one and allows for creating a new row object that will store the string that will show in the first column. "System.Windows.Forms.ListViewItem" object will need to be created and then we can check the properties, methods and events on it.
.<System.Windows.Forms.ListViewItem>Add <System.String>text
Here is the function that will create ListViewItem objects and add them to the listView control. Since the tool that we are developing will list all the objects in the scene it is important that you create some objects or you will get nothing in the listView.
- First we create an empty array that will temporarily store all the ListViewItem row objects.
- Second loop through all the objects in the scene and create a new ListViewItem object with the name of the current object as it's property.
- Append the newly created ListViewItem object to the rows array.
- Add all the ListViewItem row objects at once to the listView control. To do this we will use..
Listener:.AddRange <System.Windows.Forms.ListView+ListViewItemCollection>items
--Adds rows of data to the listView fn populateList theLv= ( rows=#() --Empty array to collect rows of data for x in objects do --Loop through all the objects in the scene. ( li=dotNetObject "System.Windows.Forms.ListViewItem" x.name --Create a listViewItem object and name it. append rows li --Added the listViewItem to the rows array ) theLv.items.addRange rows --Add the array of rows to the listView control. )
We can then call this function as soon as the dialog opens in the on open event handler. Note that you should try and avoid writing all the code into the open event handler or even in event handlers for UI control items. The reason for this is you might need to call these functions from other ares of the code. Writing every thing into small functions allows for easier maintenance and the ability to call them when needed.
on theRollout open do
(
initLv lv
addColumns lv #("Object","Class","Wire Color")
populateList lv
)
Result:
li=dotNetObject "System.Windows.Forms.ListViewItem" "test" showProperties li
We can then check on the methods for the subItems property and find that it also has a add method.
showMethods li.subItems
Because of the way this works, each column is actually a child of the first column in an array and not directly accessible from the lv control it self. You need to access the row and then the column from that. We will do this later in the tutorial.
So adding data to the second and third columns will look like this. The order the data is in is the order that you add it. Remember that all data that is added must be a string so when we add the class and wire color we have to convert it.
--Adds rows of data to the listView fn populateList theLv= ( rows=#() --Empty array to collect rows of data for x in objects do --Loop through all the objects in the scene. ( li=dotNetObject "System.Windows.Forms.ListViewItem" x.name --Create a listViewItem object and name it. li.subitems.add ((classOf x) as string) --Add data to the second column. li.subitems.add (((x.wireColor) as point3) as string) --Add data to the third column. append rows li --Added the listViewItem to the rows array ) theLv.items.addRange rows --Add the array of rows to the listView control. )
Result:
on testButton pressed do ( clearListener() --Clear the listener. format "Props\n" --Format a header so we know what we are looking at below. showProperties lv --Show the properties of the listView control. format "\nMethods\n" --Format a header so we know what we are looking at below. showMethods lv --Show the properties of the listView control. format "\mEvents\n" --Format a header so we know what we are looking at below. showEvents lv --Show the properties of the listView control. )
All the events are passed a MouseEventArgs that can be used to access information about the event. Note that different events will return different MouseEventArgs so you might need to deal with them differently.
onMouseDown <System.Windows.Forms.MouseEventArgs>e do ( ... )
In this case if we do a showProperties on the mouseEventArgs will will see seven options. Click on any one of the list items to display them in the listener.
on lv mouseDown arg do ( clearListener() showProperties arg )
.Button : <System.Windows.Forms.MouseButtons>, read-only .Clicks : <System.Int32>, read-only .Delta : <System.Int32>, read-only .Location : <System.Drawing.Point>, read-only .X : <System.Int32>, read-only .Y : <System.Int32>, read-only .Empty : <System.EventArgs>, read-only, static
It is interesting to note that none of the returned values are the actual listViewItem of the row that we selected. Instead what we do get are the .X and .Y positions of the mouse in the listView. From the position of the mouse we can get the row item and the sub items. This feels like a bit of a hack but it is just the way it is.
In the code below I first showProperties on the mouseEventArgs and find there is a .HitTest property. The hit test is looking for a "system.drawing.point" object to be passed to it. The point object needs and X and Y property set and we can use the ones that we get from the mouseEventArgs. We will assign the return value to a local variable called hit.
Next do a showProperties on the hitTest and there are many more properties, .item is the most obvious as we are looking for the item that we have clicked on. If we then check the properties on hit.item we can get the text that is shown in the listViews first column.
The .item property also have a .subItems property that holds an array of all the columns starting at the first. Note that just about all other languages use arrays that start with the index 0, this is refereed to as 0 based arrays. If we check the properties of .subItems we find a .item property that holds the array and also a .count so if we are doing a search and we don't necessarily know how many columns there are we can find out. Again, make sure your indexed for loop starts at 0 and goes to .count-1.
Some error checking will also be needed to ensure the user is not clicking on a part of the list that doesn't have a an item under it. You can test this now and see that an error is thrown as there is no .item property for undefined.
on lv mouseDown arg do ( clearListener() showProperties arg hit=(lv.HitTest (dotNetObject "System.Drawing.Point" arg.x arg.y)) showProperties hit showProperties hit.item print hit.item.text showProperties hit.item.subItems print hit.item.subItems.count print hit.item.subItems.item[1].text )