Let’s face it, Dotnetting in Maxscript is one of the less inviting things to get into. It seems half your code is just namespace strings, you’re never sure if you should be using a DotNetClass, DotNetObject or DotNetControl, and the garbage collector is suddenly your worst enemy.
Below are a few small tips that should make your initial venture into Dotnet a bit less taxing on your keyboard. Some might seem obvious and trivial, but I think they are worth mentioning.
Omit the “System.Windows.Forms.” Prefix
If you’re starting out with Dotnet interfaces in Maxscript, you’ve probably typed these three words out more times than you’d care to acknowledge.
Well, good news then. You do not need to type them at all. The gracious developers at Autodesk have hardcoded this namespace (and sadly, only this namespace) to be presearched for a match when trying to resolve the type string.
What this means is that you can just omit the prefix for all types in the System.Windows.Forms namespace. Thats right:
is exactly the same as
Cache types inside loops
Even better than omitting part of the type string, is omitting all of it.
Whenever you are instantiating many
DotNetObjects it is prudent to cache the type altogether.
Let’s look at a contrived example:
for i = 1 to 10000 collect dotNetObject "System.Drawing.Point" i i --Execution time: 1015 ms --Memory usage: 1280 KB
Now, how could we optimize this?
Well, there’s certainly no reason to allocate 10000 type strings. Let’s put the string into a variable outside of the loop:
ptStr = "System.Drawing.Point" for i = 1 to 10000 collect dotNetObject ptStr i i --Execution time: 990 ms --Memory usage: 720 KB
This helped our memory usage a bit, but execution time is left almost unphased. This is because most of the time is spent looking up the correct object type to instantiate based on the type string.
The solution to this is to look up the type only once. This is done by creating a
DotNetClass variable which hold the type information and inputting this variable in place of a type string into the
Now let’s see what happens when we precache the type:
ptCls = dotNetClass "System.Drawing.Point" for i = 1 to 10000 collect dotNetObject ptCls i i --Execution time: 93 ms --Memory usage: 720 KB
A tenfold improvement! Not only are we saving MAXScript from allocating 10000 strings, we’re also saving it 10000 type checks.
Object, Class or Control?
This is an often confusing topic when starting out with Dotnet in MAXScript, though it’s really more simple than it sounds. I will not get into the differences between classes and objects here, you should really read the articles mentioned at the top of this post if you are not sure what the difference is. In a sentence, a
DotNetClassrepresents a class, or a type, while a
DotNetObjectrepresents an instance of that type.
DotNetClass whenever you want to access a static method or property of an object. e.g:
(dotNetClass "System.Environment").newLine --Access a static property (dotNetClass "System.GC").Collect() --Execute a static method
DotNetObject when you need to instantiate (construct) an object. If you need to add a control to a Dotnet form, you must create it using a
myButton = dotNetObject "Button" myForm.Controls.Add myButton
DotNetControl is a MAXScript wrapper around a Dotnet object which inherits from type
System.Windows.Forms.Control. Use a
DotNetControl only when you want to embed a Dotnet control inside a MAXScript rollout.
This wrapper enables you to use MAXScript style event handler syntax on the control. e.g.:
rollout myRollout "My Rollout" ( dotNetControl myBtn "Button" on myBtn click sender args do print sender )
Enumeration values, for all syntax purposes, can be treated as static fields of a class. They can be accessed in one of two ways. Either using a type string:
myGfxObject.SmoothingMode = (dotNetClass "System.Drawing.Drawing2D.SmoothingMode").AntiAlias
Or simply using the property which we want to assign:
myGfxObject.SmoothingMode = myGfxObject.SmoothingMode.AntiAlias
This works because the property
SmoothingMode is already of type
System.Drawing.Drawing2D.SmoothingMode, and thus has a member
Note that the fact that both the property we are assigning and the enumeration type have the same name is a coincidence and is not always the case.
Using the former syntax might be more readable in cases where the type of the enumeration is not immediately evident from the name of the property. Using the latter syntax is faster, more performant (only within the context of a large loop, it would never be noticable as a single call), and usually involves less typing.
Another way is to precache the enumeration type in your script:
smoothingMode = dotNetClass "System.Drawing.Drawing2D.SmoothingMode" myGfxObject.SmoothingMode = smoothingMode.AntiAlias
This had the benefits of both methods.
Enumeration values can be combined using the
myButton.Anchor = dotNet.CombineEnums myButton.Anchor.Left myButton.Anchor.Right
This is equivalent to the c#
bitwise or operator (|), and essentially means to use both values – in this case, anchor the control to both left side and right side of the parent control.
Note that not all enumerations can be combined in this way.
Use Factory Functions for Repetitive Controls
If you are initializing many controls of the same type, your code can get unnecessarily long. Consider the following:
btn1 = dotNetObject "Button" btn1.Anchor = dotNet.combineEnums btn1.Anchor.Bottom btn1.Anchor.Right btn1.Width = 50 btn1.Height = 25 btn1.Text = "Button 1" btn2 = dotNetObject "Button" btn2.Anchor = dotNet.combineEnums btn2.Anchor.Bottom btn2.Anchor.Right btn2.Width = 80 btn2.Height = 25 btn2.Text = "Button 2" ... btn10 = ...
This results in a massive ugly code block that is uncomfortable to revise. In place of this code we could create a function which returns a button, as such:
fn buttonFactory text width height = ( local btn = dotNetObject "Button" btn.Anchor = dotNet.combineEnums btn.Anchor.Left btn.Anchor.Right btn.Text = text btn.Width = width btn.Height = height btn ) btn1 = buttonFactory "Button 1" 75 25 btn2 = buttonFactory "Button 2" 50 25
Of course, this function could be made as specific or as general as needed; Only add the properties which are not common to all controls as parameters. Similarly, it could even be turned into a general purpose control factory, accepting a type string, and as many parameters as needed.
fn controlFactory typeString params = ( --construct the control according to type string: local ctrl = dotNetObject typeString --initialize any common properties here: ctrl.Anchor = dotNet.combineEnums ctrl.Anchor.Bottom ctrl.Anchor.Right --initialize specified parameters: for p in params do setProperty ctrl p p --return the new control: ctrl ) btn1 = controlFactory "Button" #(#(#width, 50), #(#height, 25), #(#text, "Button1")) chk1 = controlFactory "CheckBox" #(#(#width, 80), #(#height, 20), #(#text, "CheckBox1"), #(#checked, true))