dotNet + MXS


#441

Some max rollout controls don’t have width and height properties. It’s possible to set width and height of those controls before creation but there is no easy way to change their size after.

but any control is a window and if you know control’s handle you technically can change window’s size or position.

this snippet shows how to make little sliders:


 fn getWinClass = 
 (
 	source = ""
 	source += "using System;
"
 	source += "using System.Runtime.InteropServices;
"
 	source += "public class Window
"
 	source += "{
"
 	source += "	[DllImport(\"user32.dll\")]
"
 	source += "	public static extern bool SetWindowPos(int hWnd, int hWndArg, int Left, int Top, int Width, int Height, int hWndFlags);
"
 	source += "}
"
 
 	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
 	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
 	compilerParams.GenerateInMemory = on
 	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
 	compilerResults.CompiledAssembly.CreateInstance "Window"
 )
 if wndclass == undefined do global wndclass = getWinClass()
 	
 try(destroydialog sliderRol) catch()
 rollout sliderRol "Little Sliders by denisT"
 (
 	slider s1 width:0 height:0 pos:[0,-100] ticks:0
 	slider s2 width:0 height:0 pos:[0,-100] ticks:0
 	slider s3 width:0 height:0 pos:[0,-100] ticks:0
 	slider s4 width:0 height:0 pos:[0,-100] ticks:0
 	
 	local pos = [10,4]
 	local size = [180,20]
 	
 	on sliderRol open do
 	(
 		hwnd = windows.getChildHWND 0 sliderRol.title
 		sliders = for c in (windows.getChildrenHWND hwnd[1]) where c[4] == "msctls_trackbar32" collect c[1]
 		for k=0 to sliders.count-1 do
 		(
 			::wndclass.SetWindowPos sliders[k+1] 0 pos.x (pos.y + k*size.y) size.x size.y 0
 		)
 	)
 )	
 createdialog sliderRol width:200 height:90
 

#442

Hm, I seem to be getting an error on that one, Denis. :shrug:

– Error occurred in sliderRol.open()
– Frame:
– sliders: undefined
– hWnd: undefined
>> MAXScript Rollout Handler Exception: – Unknown property: “getChildHWND” in #Struct:windows(
sendMessage:<fn>,
addChild:<fn>) <<

I’m running Max 9, if that’s relevant.


#443

it doesn’t work for max 9. Windows structure in max 9 doesn’t have methods that I use. There is some workaround but really I don’t care anymore about versions lower then max 2009.


#444

Ah okay… well thanks anyway :hmm:


#445

These windows methods were available in Avguard extensions before their integration in Max 2008. Here is the link to the dlx for Max 9:

http://www.scriptspot.com/extensions/web_upload/Larry%20Minton/avg_dlx_900.zip


#446

There is another common problem. How to get a control’s position and size after creation?

this snippet shows how to make resizable pickButton using its text extent:


   fn getWinClass = 
   (
   	source = ""
  	source += "using System;
"
  	source += "using System.Runtime.InteropServices;
"
  	source += "public class Window
"
  	source += "{
"
  	source += "	[DllImport(\"user32.dll\")]
"
  	source += "	public static extern bool SetWindowPos(int hWnd, int hWndArg, int Left, int Top, int Width, int Height, int hWndFlags);
"
  	source += "	[DllImport(\"user32.dll\")]
"
  	source += "	static extern bool GetWindowRect(int hWnd, out POS rect);
"
  	source += "	public struct POS
"
  	source += "	{
"
  	source += "		public int Left;
"
  	source += "		public int Top;
"
  	source += "		public int Right;
"
  	source += "		public int Bottom;
"
  	source += "	}
"
  	source += "	public int[] GetWindowPosAndSize(int hWnd)
"
  	source += "	{
"
  	source += "		POS rect;
"
  	source += "		if ( GetWindowRect(hWnd, out rect) )
"
  	source += "		{
"
  	source += "			return new int[] { rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top };
"
  	source += "		}
"
  	source += "		return null;
"
  	source += "	}
"
  	source += "}
"
  
  	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
   	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
   	compilerParams.GenerateInMemory = on
   	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
   	compilerResults.CompiledAssembly.CreateInstance "Window"
   )
   if wndclass == undefined do global wndclass = getWinClass()
   
   try(delete objects) catch()
   
   long_named = box name:"The Long Named Box Object" wirecolor:yellow pos:[0,0,0]
   short_named = box name:"Just a Box" wirecolor:orange pos:[0,0,long_named.height]
   
   try(destroydialog pickRol) catch()
   rollout pickRol "Resizable Pick"
   (
   	pickbutton pb "Pick a Node" width:180 pos:[0,-1000] autoDisplay:on
   	
   	local windHWND
   	local pickHWND
   	local pos = [10,4]
   	local size = [120,22]
   	local minmax = [120,300]
   	
   	on pb picked obj do if obj != undefined do
   	(
   		w = (gettextextent obj.name).x + 16
   		w = amin minmax[2] (amax minmax[1] w)
   		if pickHWND != undefined do
   		(
   			ps = wndclass.GetWindowPosAndSize windHWND[1]
   			ss = wndclass.GetWindowPosAndSize pickHWND[1]
   			d = (w + 20 - ps[3])/2
   			wndclass.SetWindowPos windHWND[1] 0 (ps[1]-d) ps[2] (w+26) ps[4] 0
   			wndclass.SetWindowPos pickHWND[1] 0 0 0 w ss[4] 2 -- nomove
   		)
   	)
   	
   	on pickRol open do
   	(
   		windHWND = windows.getChildHWND 0 pickRol.title
   		wndclass.SetWindowPos windHWND[1] 0 0 0 (size.x+26) (size.y+40) 2 -- nomove
   		
   		if (pickHWND = windows.getChildHWND windHWND[1] "Pick a Node") != undefined do
   		(
   			wndclass.SetWindowPos pickHWND[1] 0 pos.x pos.y size.x size.y 0
   		)
   	)
   )	
   createdialog pickRol width:0 height:0
   

ps. some bug fixed…


#447

that is some seriously good stuff you are working on currently, Denis.

It got me thinking about past research, so I tried a dynamic compile of a simple inherited control. It’s called MaxformPlus as it inherits the maxcustomcontrols.maxform class. This has all the usual maxform properties and methods but allows you to move the window around by clicking the client area and moving the mouse. I thought it would be useful for anyone wanting to make a minimal looking form without a top menubar and still be able to re-position it - perhaps useful for popup character controls.

 fn MaxFormPlusClass = 
   (
  	-- credit for this method goes to Mike Biddlecombe, I simply made it more paletable for my VB taste	 
  	-- Thanks also to Denis T for his overall dotNet wisdom	
  	 
  	 source = ""
  	source += "Imports System.Windows.Forms
"
  	 source += "Public Class MaxFormPlus
"	 
  	 source += "Inherits MaxCustomControls.MaxForm
"
  	 source += "Const WM_NCHITTEST As Integer = &H84
"
  	source += " Const HTCLIENT As Integer = &H1
"
  	source += " Const HTCAPTION As Integer = &H2
"
  	 source += "Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
"
  	 source += "Select Case m.Msg
"
  	source += "Case WM_NCHITTEST
"
  	 source += "MyBase.WndProc(m)
"
  	 source += "If m.Result = HTCLIENT Then m.Result = HTCAPTION
"	 
  	source += "Case Else
"
  	source += "MyBase.WndProc(m)
"
  	source += "End Select
"
  	source += "End Sub
"
  	source += "End Class"
  
  	 VBProvider = dotnetobject "Microsoft.VisualBasic.VBCodeProvider"
  	 compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
  	compilerParams.ReferencedAssemblies.add (getdir #maxroot +"MaxCustomControls.dll")
  	compilerParams.ReferencedAssemblies.add "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll"	
  	 compilerParams.GenerateInMemory = on
  	 compilerResults = VBProvider.CompileAssemblyFromSource compilerParams #(source)
  	
  	-- this is very useful to debug your source code and check for referencing errors - --thanks mike
  	if (compilerResults.Errors.Count > 0 ) then
  	 (
  		 errs = stringstream ""
  		 for i = 0 to (compilerResults.Errors.Count-1) do
  		 (
  			 err = compilerResults.Errors.Item[i]
  			 format "Error:% Line:% Column:% %
" err.ErrorNumber err.Line \											  
  												  err.Column err.ErrorText to:errs 
  		 )
  		 MessageBox (errs as string) title: "Errors encountered while compiling VB code"
  		 return undefined
  	 ) 	
  	 
  	-- create a dotnetobject from the class 
  	return compilerResults.CompiledAssembly.CreateInstance "MaxFormPlus"
   )
  
  -- return the inherited maxform class
  moveablemaxform = MaxFormPlusClass()
  moveablemaxform.text = "Click the client area and move!"
  moveablemaxform.showdialog()

#448

really nice! i’m using this kind of window behavior very often. It seams like the first sample of using on fly VB. :wink:


#449

I found that this helps determining the difference between a 32 and 64 bit:

if (dotNetClass "System.IntPtr").Size == 4 then print "32 Bit" else print "64 Bit"

http://msdn.microsoft.com/en-us/library/system.intptr.aspx
Maybe it’s of some use to someone, I found there’s some other classes that give more info on the OS, but for simple 64bit check this works seem to work ok.

Cheers,
-Johan


#450

you can just use the built in maxscript function is64bitApplication()


#451

Hi Joel, it’s just a dotnet approach to determine the OS variant, which I haven’t found a function for.

-Johan


#452

Is there any chance we could get a sub-forum of maxScript for .net?
It is becoming increasingly difficult to navigate through this sticky for information as well as it seems like it’d get in the way of straight maxscripting in the regular forum, or is that where we should post?


#453

Hey guys,

I don’t know if this has already been discussed before in this thread, but I thought it might be good to look at the performance of .NET in maxscript. Now that I’m working on my outliner tool again, which heavily uses .NET, I’ve been investigating the performance impact for various uses of .NET objects.
It turns out that in general, creating .NET objects and sending objects to and from .NET is slow and should be minimized. An example, creating a .NET Color object, something that is quite common:

-- Create a .NET color object
local dotnetColor = (dotNetClass "System.Drawing.Color").FromArgb 255 255 255
-- Send it to another .NET object (lets say a treenode)
myTreeNode.ForeColor = dotnetColor;

It looks straightforward enough, and for a single, or a couple of consecutive operations it should be fine. However, if you need performance, it would definitely be a better idea to handle the creation of the color object on the .NET side. By either making a class that wraps your object, or extends it, and adds specific methods:

//.NET code, class wrapping TreeNode
private TreeNode tN;
public void SetForeColor(Int32 r, Int32 g, Int32 b)
{
  tN.ForeColor = Color.FromArgb(r, g, b);
}

-- In maxscript:
myWrappedTreeNode.SetForeColor 255 255 255

This way you’re only calling a single .net function, and you aren’t sending any complex objects “over the fence” that need time-consuming boxing.

Another example I just tried, which surprised me a bit.
Assume you have a .NET class in which you store a boolean, let’s say it’s a flag indicating whether a node it belongs to is a group member or not. Retrieving this flag from the .NET object turns out to be slower that actually using the maxscript function IsGroupMember each time you need the flag.

For my own scripts, I’ve now made it a policy to avoid as much “interaction” with .NET as possible when using it in loops. By this I mean that I’ll try to send as little, and as simple data as possible to and from .NET.
I’d be interested to hear your thoughts on this.


#454

As far as I know there are 2 methods to add nodes to a treeview Nodes.Add and Nodes.AddRange. Now I haven’t done any speedtest, but by assumption I guess addrange is faster, have you tried that? What I like about creating the nodes in maxscript is that you’re very flexible in adding max objects to a tag, which, I think, cannot be done straight from a extended treeview dll.

Maybe it’s a good idea, to have a test treeview to time some different implementations of adding nodes.

-Johan


#455

Using AddRange(TreeNode[]) would be faster than Add(TreeNode) for each node separately.
However, you’re still having to make those TreeNode objects in maxscript. So what I do, is just pass all the data that I want to use in .NET to a function on the .NET side. This function then handles everything that has to be done with it (i.e. add a TreeNode to the tree).

You can still use the dotNetMXSValue using this method. Just have the function accept a parameter with type Object. Of course this means that there isn’t any type checking, but that’s a very small price to pay :slight_smile:


#456

in the code above there two things that slow everything down. First - you create max value using method dotnetclass . Second - you create new local variable. Both of them need time and memory.
How to do it faster with minimum memory leaking?

  1. Short:

 for node in list do node.BackColor = node.BackColor.FromARGB 255 255 255
 
  1. Smart :slight_smile:

 local dotnetColor  = dotNetClass "System.Drawing.Color"
 for node in list do node.BackColor = dotnetColor.FromARGB 255 255 255
 

#457

addrange is faster then add and doesn’t cause memory leaking. If you need do add many nodes to tree view the fastest way to do it is:


(
	treeview = dotnetobject "TreeView"
	treenode = dotnetobject "TreeNode"
	-- < set all common properties ... > -- e.g.  treenode.backcolor = treenode.backcolor.Red; treenode.ImageIndex = 0
	nodes = for k=1 to 1000 collect
	(
		n = treenode.clone()
		-- < set individual properties >
		n
	)
	treeview.BeginUpdate()
	treeview.nodes.addrange nodes
	treeview.EndUpdate()
)


#458

What would be causing a memory leak then? :rolleyes:
Creating some locals will of course (temporarily) result in more memory usage, but shouldn’t cause leakage, right?

Cloning one node to get a new one quickly is an interesting idea. However, I still think you’d be faster off by handling complex object creation on the .NET side. It’ll save all the boxing/unboxing. But I’d have to do some more tests to see if I’m really correct in this assumption. The ones I did up to now were quite focussed on my particular situation with the outliner.


#459

hi,

im having an issue with dot net events. I setup events c# and then handle them in my maxscript. These work fine if I use the maxscript in its own rollout but I am trying to get this to work in the material editor as an extention of the directx shader interface. The events dont seem to fire in maxscript and nothing occurs, I assume the material editor is blocking them somehow?

Any ideas please?

Thanks


#460

If you are running Max2010, creating a MXS array then using “dotnet.ValueToDotNetObject” to convert that array to a .NET array you can pass to AddRange would appear to be a win.

(
sz = 10000
tnc = dotNetClass "System.Windows.Forms.TreeNode"
tna = dotNetClass "System.Windows.Forms.TreeNode[]"

treeView1 = dotnetobject "System.Windows.Forms.TreeView"
treeView2 = dotnetobject "System.Windows.Forms.TreeView"
treeView3 = dotnetobject "System.Windows.Forms.TreeView"
	
stime = timestamp()
nodes = #()
nodes[sz] = undefined
for i = 1 to nodes.count do 
	nodes[i] = (dotnetobject tnc)

treeView1.nodes.addRange (dotnet.ValueToDotNetObject nodes tna)
etime = (timestamp()) - stime
	
format "Create first  view (generate list of % nodes then AddRange) %
" treeView1.nodes.count etime

stime = timestamp()
treeView2.nodes.addRange (dotnet.ValueToDotNetObject nodes tna)
etime = (timestamp()) - stime
format "Create second view (reuse list of % nodes with AddRange) %
" treeView2.nodes.count etime
	
stime = timestamp()
for i = 1 to sz do 
	treeView3.nodes.add (dotnetobject tnc )
etime = (timestamp()) - stime
format "Create third  view (% nodes, adding one node at a time) %
" treeView3.nodes.count etime
)
	

Here are some test runs under Max 2010 64bit:

Create first  view (generate list of 10000 nodes then AddRange) 239
Create second view (reuse list of 10000 nodes with AddRange) 11
Create third  view (10000 nodes, adding one node at a time) 762
OK
Create first  view (generate list of 10000 nodes then AddRange) 208
Create second view (reuse list of 10000 nodes with AddRange) 11
Create third  view (10000 nodes, adding one node at a time) 763
OK
Create first  view (generate list of 10000 nodes then AddRange) 280
Create second view (reuse list of 10000 nodes with AddRange) 11
Create third  view (10000 nodes, adding one node at a time) 667
OK

It seems that first generating a list of dotnetobjects and then calling AddRange is 2-3 times faster. YMMV.

.biddle