Floating Dialog Amnesty

Mar 16, 2010 by     9 Comments    Posted under: 3dsMax, DotNet, Maxscript, Tips and Tricks

WindowBoxUI.png

Ack, not another one of those posts where the only picture is a p*ss poor dialog with some stolen twinkly twink icons. My old art teacher would be despairing right now, and perhaps comforting himself by grinding a yellow ochre paste. Oh, how he loved that yellow ochre.

A situation existed recently where between working on the train and at work, I’d lost some dialogs on the screen space outside the resolution of my laptop monitor. A few of 3ds max’s window locations are stored in the ini file.

inipath = getMAXIniFile()
setINISetting inipath “MtlEditorPosition”  “MainWindow” “0 0 375 734”
setINISetting inipath “RenderVFBPosition”  “Position” “0 0”
setINISetting inipath “MaterialBrowserDialogPosition” “Dimension” “0 0 340 600”
setINISetting inipath “EnvironmentDialogPosition” “Dimension” “0 0 350 580”
setINISetting inipath “RenderDialogPosition” “Dimension” “0 0 360 750”
setINISetting inipath “mentalrayMessagesPosition” “Dimension” “0 0 600 400”
setINISetting inipath “RenderPresetsCategoryDialogPosition” “RenderPresetsCategoryDialogDimension” “0 0 210 280”

But most of the useful ones are not – like the layer manager and curve editor.

Unfortunately, you cant set their position like you can a rollout floater – it meant it was time for the programmatic equivalent of James Herriot putting on a large rubber glove.

Windows has a wealth of useful functions in the API that aren’t yet exposed to the dotnet framework – well not out the box, there is a managed API but I haven’t looked into this yet . However this doesn’t mean you cant use native API calls in a managed code environment. You can declare an API call in a dotnet assembly, and with a little coercion, get it working as you want.

pinvokelogoThe place to look for Win32 and API related stuff is pinvoke.net. This has loads of info about how to call these windows functions via the managed dotnet framework

From the site itself-

“The term PInvoke is derived from the phrase “Platform Invoke”. PInvoke signatures are native method signatures, also known as Declare statements in VB”

After looking on this site, I found the methods i was looking for.

  • GetWindowRect – Gets the size of the window from the window handle
  • GetWindowPlacement – Gets the current window location from the window handle
  • MoveWindow – Moves the window to a screen location

All that was need now was to convert these signatures into a format string ready to compile directly from 3dsmax, so that I could call these win32 functions from a dotnetobject within 3dsmax, and hopefully retrieve my missing windows.

When you compile a dll directly in 3dsmax, you are taking the code that you would write within the Visual Studio window and sending it to the compiler manually. So the flavour of managed language you are using dictates the compiler type.

The key to this class is to set up some custom properties so that the data coming out when used in 3dsmax is not too weird –  it is logical to return a drawing.point for a location, and a drawing.size for a window size for instance.

Getwindowrect uses a RECT structure (a structure is similar to a class) but creating one in max can be tricky. Mike Biddlecombe on CG Talk had posted a method to instantiate a structure in 3dsmax by adding it into an array and retrieving the first member. That was brilliant stuff and typical of the sort of stuff that Mike is figuring out the whole time. I decided in this case, that since I was compiling an assembly anyway, i’d just handle all of the structure business there and avoid any potential banana skins. So I added a width and height property to the RECT structure definition, meaning after it was instantiated, you can call the method from PInvoke and return a structure that has standard properties. Planning return values in your classes is something that helps in the long run – It makes deployment much easier in Max. I have found that since the VS interface is easier to debug, it’s a good place to do as much as you can there.

fn DialogWindowOpsClass =
(
source = “”
source += “Imports System.Runtime.InteropServicesn”
source += “Imports System.Drawingn”
source += “Public Class DialogWindowOpsn”
source += “Public Structure RECTn”
source += “Public left As Integern”
source += “Public top As Integern”
source += “Public right As Integern”
source += “Public bottom As Integern”
source += “Public ReadOnly Property Width() As Integern”
source += “Getn”
source += “Return right – leftn”
source += “End Getn”
source += “End Propertyn”
source += “Public ReadOnly Property Height() As Integern”
source += “Getn”
source += “Return bottom – topn”
source += “End Getn”
source += “End Propertyn”
source += “End Structuren”
source += “Public Structure POINTAPIn”
source += “Public x As Integern”
source += “Public y As Integern”
source += “End Structuren”
source += “Public Structure WINDOWPLACEMENTn”
source += “Public Length As Integern”
source += “Public flags As Integern”
source += “Public showCmd As Integern”
source += “Public ptMinPosition As POINTAPIn”
source += “Public ptMaxPosition As POINTAPIn”
source += “Public rcNormalPosition As RECTn”
source += “End Structuren”
source += “<DllImport(“user32.dll”)> _n”
source += “Public Shared Function MoveWindow(ByVal hWnd As System.IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal bRepaint As Boolean) As Booleann”
source += “End Functionn”
source += “<DllImport(“user32.dll”)> _n”
source += “Public Shared Function GetWindowRect(ByVal hWnd As System.IntPtr, ByRef lpRect As RECT) As Booleann”
source += “End Functionn”
source += “<DllImport(“user32.dll”)> _n”
source += “Public Shared Function GetWindowPlacement(ByVal hWnd As System.IntPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Booleann”
source += “End Functionn”
source += “Public Function WindowSize(ByVal Hwnd As System.IntPtr) As System.Drawing.Sizen”
source += “Dim LPRECT As RECTn”
source += “GetWindowRect(Hwnd, LPRECT)n”
source += “Dim WinSize As System.drawing.size = New System.drawing.size(LPRECT.Width, LPRECT.Height)n”
source += “Return WinSizen”
source += “End Functionn”
source += “Public Function WindowPosition(ByVal Hwnd As System.IntPtr) As System.Drawing.Pointn”
source += “Dim intRet As Integern”
source += “Dim wpTemp As WINDOWPLACEMENT = New WINDOWPLACEMENT()n”
source += “wpTemp.Length = System.Runtime.InteropServices.Marshal.SizeOf(wpTemp)n”
source += “intRet = GetWindowPlacement(Hwnd, wpTemp)n”
source += “Dim WinPoint As System.drawing.point = New System.drawing.point(wpTemp.rcNormalPosition.left, wpTemp.rcNormalPosition.top)n”
source += “Return WinPointn”
source += “End Functionn”
source += “End Class”

VBProvider = dotnetobject “Microsoft.VisualBasic.VBCodeProvider”
compilerParams = dotnetobject “System.CodeDom.Compiler.CompilerParameters”
compilerParams.ReferencedAssemblies.add “C:WindowsMicrosoft.NETFrameworkv2.0.50727System.drawing.dll”
compilerParams.GenerateInMemory = on
compilerResults = VBProvider.CompileAssemblyFromSource compilerParams #(source)

— this is very useful to debug your source code and check for referencing errors
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:% %n” err.ErrorNumber err.Line
err.Column err.ErrorText to:errs
)
MessageBox (errs as string) title: “Errors encountered while compiling VB code”
return undefined
)
return compilerResults.CompiledAssembly.CreateInstance “DialogWindowOps”
)

What this code does is make a class that can be instantiated in max and used to control the positions of the dialogs in the 3dsmax user interface, whether we can see them or not.

In order to call them though, we need an array of handles for the active max dialogs. Fortunately, Maxscript gives us a way of doing this –

UIAccessor.GetPopupDialogs()

The rest, was building an interface. I went for the datagridview – Its a complex user control and I am probably only scratching the surface of the sort of things you can do. Its designed for handling masses of complex data from data sources like sql databases but you can just use it like a listview. Its just got a few more options.

Maxscript also has methods for interacting with win32 functions, so it’s entirely possible that this could be achieved using that, but then that’d just be cheating, wouldn’t it?

download

9 Comments + Add Comment

  • avatar

    Hi I am keen to look at your script
    When I run it it cant find the icons
    AssetDir = ((getdir #scripts) + “\\LoneRobot\\Assets\\Icons\\”)
    I downloaded only the MCR is there a mzp or file with Icons I am missing?

  • avatar

    THX man Appreciate it 7 months lost till I found your script thx a lot man can t express how frustrating it was

  • avatar

    Hello,
    I’m trying to place a dialog with some offset from the material editor position, so I’m trying to figure how to obtain only the position of material dialog, but can’t catch it in your code. I can locate the handler from “windows.getChildrenHWND” in the same way you are doing for storing in “DialogList” and “StrWindowList”, but after that, how can I get the position of the dialog? Sorry but my dotNET capabilities are below zero.

    Thanks in advance
    Alejandro

  • avatar

    Hi James,

    I was aware of this behaviour, getpoupdialogs() does not seem to return all of the floating dialogs present in a session of max.

    You can get a better result by changing the line –

    DialogList = for i in (UIAccessor.GetPopupDialogs()) where not i == 0 collect i

    to –

    DialogList = for i in (windows.getChildrenHWND 0 parent:#max) where
    UIAccessor.IsWindow i[1] and (not i[5] == “AdApplicationButton”) and (not i[5] == “”) collect i[1]

    This seems to give a better result on my Win7 x64 build. I didn’t get this issue on vista 32bit so could be something to do with that.

  • avatar

    Have you ever run into any issues where UIAccessor.getPopupDialogs() doesn’t return all of the dialogs you would expect? I can see in your screenshot your script works with LayerMangager, SME, and others. I’ve tried running it on my system (Win7 64bit MaxDesign2011) for the first time and it’s not listing everything. The only item’s I’ve got it to show so far is itself, maxscript listener, and track view curve editor. I also have Layer Manage, SME, Render Settings dialog open, but the array returned by getPopupDialogs() doesn’t seem to include references to those (which in turn of course means they aren’t listed in your UI.) Any thoughts on some setting or something I missed in the directions for this?

  • avatar

    Thanks everyone for your comments, I’m glad you find the script useful. I wrote it because I had the same problem for years, I run a three 24″ monitor setup – two landscape and one portrait so it’s useful for me too.

  • avatar

    THIS IS SO HUGE !

    i really thank you for that, this is so useful for my 3dsmax.
    I usually use max in 3 differents ways,now in 4 ( 2nd screen in vertical position, not so bad ) and i used to loose so often my windows changing to X resolution to Z resolution , or 2nd screen in the left of the first one,then in the right.., then in one screen only.

    You did this perfectly, i put a comment even if i didn’t test this for more than 5 minutes but, it cames directly in my worklow in my main toolbar 🙂
    maxscript + windows position tool or floating dialog does not result anymore with the lost of one of them . thanks lonerobot ! thanks !

  • avatar

    Very cool 🙂 your scripts are nice!

  • avatar

    Hi,

    Thanks for this useful piece of script 🙂

    Cheers.

Got anything to say? Go ahead and leave a comment!

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>