There are advantages and disadvantages to using a Dotnet Form and an all Dotnet UI. Disadvantages are mostly complexity of code as you will have to manage much more of it your self, registering event handlers, placement on the dialog and many other steps will need to be taken to creating and working with Forms. Advantages to using Forms is that you can do just about anything and there are less restrictions on what is possible. Even when using a Dotnet UI item like a listview in a rollout you will end up with certain limitations that are hard or impossible to get around. With a Dotnet only UI starting with a Form it is possible to control just about every aspect of how it will look and work.
In the image below a Dotnet Form has been used as a transparent overlay to the Max viewport. In the form I'm drawing graphics on the fly for heads up data about the object. This wouldn't be possible with a Max rollout and createDialog as you can't make it transparent and you can't draw graphics into it. Also the small box in the lower right corner of the viewport is a Dotnet Form that expands to show all the options for the graphics drawing. Forms can look like just about anything that you need or they can look just like a createDialog in Max script.
Once the form has been created you need to show it. Just like when creating a rollout you need to use createDialog or newRolloutFloater we need to use a method that is available in a form called "show". We can also close the form using the "close" method.
--Creates a dotNet form. form=dotNetObject "system.windows.forms.form" --You can also use the sort cut for this. form=dotNetObject "form" --Show the form form.show() --Close the form --form.close()The problem with using a "form" is it doesn't respect the rest of the settings that are being used in Max like the background colors. For this a custom dotNet maxForm was created that is inherited from "form". To create this we need to use the "maxCustomControls" path to access the new "maxForm".
form=dotNetObject "maxCustomControls.maxForm"Usage of the form when created in any one of these three ways is exactly the same.
The function below takes one argument which is the dotNetObject or dotNetClass that has been created or returned.
fn formatProps dn= ( if classOf dn==dotNetObject or classOf dn==dotNetClass then ( clearListener() format "Properties:\n" showProperties dn format "\nMethods:\n" showMethods dn format "\nEvents:\n" showEvents dn )else ( format "% is not a dotNetObject or dotNetClass\n" dn ) ) --Format all the properties to the listener. formatProps form
In the example below we are creating a "label", the "label" just creates a box that we can do add a title to, draw graphics into or add other controls to.
The label is also having the back ground color of it changed using the property "backColor" and a dotNet color class and then creating a red object from it. This is the way to create color to be added to any control or other object like text to set the color of it.
The size and location of it, with respect to the form that it is being added to, is then set using a dotNet rectangle. The rectangle is also part of the "system.drawing" name space. In the example below it has a position relative to the form of 10 units in X and Y and then a width and length of 100 units. We set the "bounds" property to the rectangle so that when it is added to the form it is set with those dimensions.
The label is then added to the form using the "form.controls.add" method. To find the "add" method we would have had to use "formatProps form" to find the controls property and then running "formatProps form.controls" to find out what properties, methods and events for it. This is where dotNet is very different from Max script, being object oriented you will find that it is far deeper and how it is structured.
--Add controls to the form. In this case a red label. --Create the label dnLabel=dotNetObject "label" --"system.windows.forms.label" --Set the backColor property to red dnLabel.backColor=(dotNetClass "system.drawing.color").red --Set the location and size of the label using a rectangle. dnLabel.bounds=dotNetObject "system.drawing.rectangle" 10 10 100 100 --Add the label to the form. form.controls.add dnLabelResult:
This form was created using "maxCustomControls.maxForm" and for that reason the backColor property of the form is ready set to the same that is used by the rest of Max. This tutorials was created using Max 2010 and the default colors.
Certain value types for dotNet are being converted for us by Max script, you need to be careful which version of Max you are using as older versions like in Max 9 some of the automatic conversions were not set up and dotNet value types needed to be passed. We are looking at what the normal processes are now in Max 2010 and above.
The "text" property shows that it needs a "System.String" object passed to it. This is one of the value types that Max will automatically convert from a Max script to a "system.string" object for use in dotNet.
The "opacity" property requires "System.Double" object, most of the number type values are converted for you automatically as well so we only need to pass it a value from 0 to 1 to set the amount of opacity.
--Set the title text. form.text="My First Form" --Set an over all opacity --Value range is 0-1 form.Opacity=.7
For other properties we will need to create dotNet objects as Max either doesn't have an equivalent data type or it will not automatically convert it for us. With the "formBorderStyle" property it requires a "System.Windows.Forms.FormBorderStyle" object to be passed to it. There are two ways that we can do this.
The first way fits closest to the way that we have been setting other properties like the backColor of the label. The formBorderStyle object needs to be created and one of the properties set for it. If we create a "dotNetClass "system.windows.forms.formBorderStyle"" and pass that to the "formatProps function we will see several options that can be set. In the example below we use the "sizableToolWindow" which creates a typical tool window dialog that has a small title bar and close button.
Properties: .Fixed3D :
, read-only, static .FixedDialog : , read-only, static .FixedSingle : , read-only, static .FixedToolWindow : , read-only, static .None : , read-only, static .Sizable : , read-only, static .SizableToolWindow : , read-only, static
borderStyle=(dotNetClass "System.Windows.Forms.FormBorderStyle").SizableToolWindow form.formBorderStyle=borderStyle --or form.FormBorderStyle=form.FormBorderStyle.SizableToolWindow
The next property we have already used with the label and that is a rectangle. The "system.drawing" name space is used for creating objects that will control graphics and drawing styles. In this case the "bounds" property we need to create a new rectangle object with the location and size passed to it. We could also do the same using two other properties, the "location" and the "size" property. In this case a "system.drawing.point" and a "system.drawing.size" object with the needed values passed to it.
.Bounds : <System.Drawing.Rectangle> .Location : <System.Drawing.Point> .Size : <System.Drawing.Size>
--Set the size and location of the form in screen space form.bounds=dotNetObject "system.drawing.rectangle" 5 20 300 200 --Or form.location=dotNetObject "system.drawing.point" 5 20 form.size=dotNetObject "System.Drawing.Size" 300 200Result:
Notice that the whole form is 30% transparent and you can see through it to the listener and editor in my viewport. It also has the small title bar that was set with the formBorderStyle.
Two other interesting properties are "backColor" and "transparencyKey", when used together it is possible to create dialogs that are completely transparent. This is how the heads up display tool was created in the first image on this page. If you set the "transparencyKey" to be the same color as the backColor property the form becomes 100% transparent. This is different then setting the opacity to 0 since it would also make all the controls that have been added to it become transparent as well.
--Set the background color form.backColor=(dotNetClass "system.drawing.color").black --Set the transparent color form.TransparencyKey=(dotNetClass "system.drawing.color").blackResult:
If you also set the "formBorderStyle" to "none" the whole form will disappear and you will only see the controls and graphics that are drawing to it.
In the example below we set three event handlers for the form, mouseDown, mouseUp and mouseMove. Checking what event handlers you can add using the formatProp functions you will see that there are far more available then when using a createDialog in Max script.
Lets inspect the first event handler. The first function is added called formMouseDown which has two arguments. These two arguments are passed to it automatically so you don't need to send them. The event handler for mouse down is then added, "dotNet.addEventHandlers" is used to do this. Three parameters are passed to it, the control that the event handler is being added to, the name of the event handler and then the function that it will call.
The functions two arguments contain information that you can use in the function. You can call the two arguments anything that you want but sender and arg are used often so it will keep it clear with other information on the internet. Sender is the actually control it self and running "formatProps sender" on it would yield the same results as "formatProps form" in this case. The arg argument is different for every event handler and you will have to check what it will return for you. In the cases below all return the same properties. For mouse down there are several properties return and are sown below.
In the example when the user clicks on the form and drags the mouse the label will stick to the mouse and follow it around. For this two of the properties of arg are being used. "button" is the first and isn't necessarily needed in this case but shown as it is very useful. "button" returns a "System.Windows.Forms.MouseButtons" class and you can check its properties to see that there are six that will allow you to track all the buttons on a five button mouse, one of the properties is "none"
In the mouse move event the mouse is being tracked over the form, the "arg.location" property returns a "system.drawing.point" object that can be used directly with the labels "location" property to set where it will be located in the form. I'm using a variable called mouseIsDown and setting it to true if the left mouse button has been clicked and then in the mouseUp handler I'm setting it back to false.
The last line sets the life time control. There is a problem created when Max script does a GC() operation, it will clear all the event handlers from memory as Max doesn't think that they are necessary. Setting the life time control to "#dotNet" stops Max script from clearing them and allows dotNet to manage if they are needed or not.
Properties: .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 Methods: .<System.Boolean>Equals <System.Object>obj .[static]<System.Boolean>Equals <System.Object>objA <System.Object>objB .<System.Int32>GetHashCode() .<System.Type>GetType() .[static]<System.Boolean>ReferenceEquals <System.Object>objA <System.Object>objB .<System.String>ToString()
--Variable used in mouseMove handler. mouseIsDown=false --Add event handlers for the form. fn formMouseDown sender arg= ( --Check to see which mouse button is being pressed. if arg.button==arg.button.left do formatProps arg;mouseIsDown=true if arg.button==arg.button.right do print #right ) fn formMouseUp sender arg= ( mouseIsDown=false ) fn formMouseMove sender arg= ( if mouseIsDown do ( format "Mouse Position:[%,%]\n" arg.x arg.y dnLabel.location=arg.location ) ) --Add the event handlers. dotNet.addEventHandler form "mouseDown" formMouseDown dotNet.addEventHandler form "mouseUp" formMouseUp dotNet.addEventHandler form "mouseMove" formMouseMove --Set the life time of the control to #dotNet so that it is not garbage collected with the rest of Max script. --For Max 2009 and previous you will need the avg extension to set this. dotNet.setLifeTimeControl form #dotNet
The "show" method will also take an argument, that argument needs to be a pointer to the HWND window handle. If you don't know what that is here is a very brief explanation. In windows every window is assigned a ID, that ID can then be used to access that window. A window is just about any object, so, a rollout, a button in a rollout, a dotNet form and any other things To set the form to be parented to Max and not windows we need to do a four step process.
Step one is to get the pointer value that represents the main Max window, this is done with a Max script command called the "windows" structure. This will return a value that looks something like this "2426114P"
Next we need to turn the pointer into a dotNet value type, this is one of those cases where Max script doesn't convert the value type automatically. A "system.IntPtr" object is used and set to the value that we received in step one.
Step three we create a wrapper for the system pointer, I'm not really quite sure at this point why this is needed but it is. We use the "maxCustomControls" structure to access the "win32HandlerWrapper" method and pass it the system pointer.
Once we have the Max handler wrapper we can pass that to the "show" method and now when the dialog opens it will be parented to Max and will always stay on top of it but not other dialogs.
--Set the parent of the form to be Max. --Get the max handle pointer. maxHandlePointer=(Windows.GetMAXHWND()) --Convert the HWND handle of Max to a dotNet system pointer sysPointer = DotNetObject "System.IntPtr" maxHandlePointer --Create a dotNet wrapper containing the maxHWND maxHwnd = DotNetObject "MaxCustomControls.Win32HandleWrapper" sysPointer --Show the Max form using the wrapper. form.Show (maxHwnd)