Phone: 905 409-1589
Email: info@penproductions.ca
RSS LinkedIn Twitter Twitter
Dotnet Listview
Navigate:
Creating Control:
First we will create a list view control in a rollout and use createDialog to launch the rollout. We need to use the "system.windows.forms" name space and get the control that we want to display. Use the MSDN web site to find other controls that you can display here or the direct link to the listView control here

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

Setting up Properties:
I have made several additions to the script at this point. First off I have added a testButton so we can print out properties and other items of interest to see what is going on.

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 : " This property requires a Boolean value to be passed to it. It is requesting a "System.Boolean" which is a class in dotNet, how ever Max script is designed to work directly with some of the dotNet data types so we can just pass true or false as we would in regular Max script boolean properties.

The one property that isn't as obvious is ".View : " There is no direct solution in Max for what this property is requesting. "System.Windows.Forms.View" is a class object that has properties of its own. In the Max script listener type

Listener:
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.

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" 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 theRollout
								
Result:
Adding Columns:
To add columns we need to dig a bit to find what we need. It isn't always obvious but in this case it makes sense. First I used the test button to look through the properties and methods of the listview control to see what I could find. The columns property looked like a good place to start, I then checked what properties and methods the columns property has and found many. Note that dotNet is truly object oriented, so every level deep that you go into a dotNet control will possibly expose more objects with more properties, methods and events.

Code:
	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.

Listener:
.<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.

Code:
	--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.

Code:
	on theRollout open do
	(
		initLv lv
		addColumns lv #("Object","Class","Wire Color")
	)
								

Result:

Populating Data:
Now we need to discover how to add rows of data to the columns. In searching the properties you can find the items property. I changed the on testButton event handler to read.

Code:
	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.

Listener:
.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.

Listener:
.<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.

 

  1. First we create an empty array that will temporarily store all the ListViewItem row objects.
  2. 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.
  3. Append the newly created ListViewItem object to the rows array.
  4. 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

Code:
	--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.

Code:
	on theRollout open do
	(
		initLv lv
		addColumns lv #("Object","Class","Wire Color")
		populateList lv
	)
								

Result:

Adding Column Data:
listViewItems are an object, and object have properties of their own so if we check the properties of li in the populateList function you will find that it has a subItems property. An easy way to do this is to use the listener and temporarily create a listViewItem and check to see what the properties are.

Listener:
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.

Code:
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.

Code:
	--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:

Event Handlers:
To find all the event handlers, and there are many, we are going to add showEvents to the On testButton Pressed handler. Looking in the listener you can see all the available events, the one that we are interested in is the on mouseDown event.

Code:
	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.

Listener:
   on  MouseDown <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.

Code:
	on lv mouseDown arg do
	(
		clearListener()
		showProperties arg
	)
									
Listener:
  .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.

Code:
	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
	)