Close event handler on native window


it there a way to get close event handler when closing any of the native 3ds max dialogs like ( material editor, render setup , light lister , max listener, etc ) by pressing the the “X” button or alt+f4 ?

i want to make that action trigger something to be done


If you want to keep it simple just probe once a second if any of these windows are still open. Otherwise you’ll need to use window sub-classing or hooks like Set­Win­Event­Hook


i want a certain macro button to be unchecked when the dialog get closed

is the two methods you provided are the only methods ?


i read in the setwineventhook and from what i undertand that their will be a backgroud proccess always monitoring the window until its closed

does the subclassing works the same ?


Yes, but when subclassing a window you override WndProc of each window individually. Search for MessageSnooper and you’ll find a few mxs examples


are they your scripted macros?
if so, we can use the isChecked handler.
for example:

on ischecked do MatEditor.isOpen()


material editor, render setup , light lister

so it’s not possible for these

I’m curious what’s cheaper to use performance wise. SetWinEventHook or a timer that simply checks if a certain window is opened at the moment?

global WinHook =
	source = "using System;\n"
	source += "using System.Runtime.InteropServices;\n"
	source += "class WinHook\n"
	source += "{	
	public class MsgEventArgs : EventArgs
		public MsgEventArgs( IntPtr hwnd, uint eventType )
			HWND = hwnd;
			EventType = eventType;
		public readonly IntPtr HWND;		
		public readonly uint EventType;		
	public static event EventHandler MessageEvent;
	public static IntPtr hWinHook;
	delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
       hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
       uint idThread, uint dwFlags);

    static extern bool UnhookWinEvent(IntPtr hWinEventHook);

	const uint EVENT_OBJECT_CREATE = 0x8000;
	const uint EVENT_SYSTEM_DESTROY = 0x8001;	

    const uint WINEVENT_OUTOFCONTEXT = 0;

    static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);

    public static void Start( uint procID, uint threadID )
                procDelegate, procID, threadID, WINEVENT_OUTOFCONTEXT);
	public static void Stop()
		if ( hWinHook != null ) UnhookWinEvent( hWinHook );

    static void WinEventProc(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
		if(idObject != 0 || idChild != 0)
        if ( MessageEvent != null )
			MsgEventArgs msg = new MsgEventArgs( hwnd, eventType );
			MessageEvent( null, msg );			
	source += "}\n"

	csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
	compilerParams.GenerateInMemory = on
	compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
	if (compilerResults.Errors.Count > 0 ) then
			local errs = stringstream ""
			for i = 0 to (compilerResults.Errors.Count-1) do
				local err = compilerResults.Errors.Item[i]
				format "Error:% Line:% Column:% %\n" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
			format "%\n" errs
			compilerResults.CompiledAssembly.CreateInstance "WinHook"

fn OnMsg s ev =
	if ev.EventType == 0x8000 do
		format "[ create  ] %: %\n" ev.HWND (windows.getHWNDData ev.hwnd)
	if ev.EventType == 0x8001 do
		format "[ destroy ] %: %\n" ev.HWND (windows.getHWNDData ev.hwnd)

dotNet.removeAllEventHandlers WinHook

proc   = (dotNetClass "System.Diagnostics.Process").GetCurrentProcess()
thread = GetCurrentThreadId()

dotNet.addEventHandler WinHook "MessageEvent" OnMsg
WinHook.Start proc.ID thread


you only need to check WS_VISIBLE window flag… you can do GetWindowLong(hWnd, GWL_STYLE) and test for WS_VISIBLE.

or IsWindowVisible in win32


i found this old amazing post that does exactly what i want but with created dialogs rollouts not with native max dialogs like ( material editor , etc)

actually what am trying to do is the exact opposite to make the button unchecked when the window get closed


do not MatEditor.isOpen()


will that be triggered with me closing using the X button or i have to attach it to a timer ?


try :wink:

I try to avoid complicated solutions (like using a timer or hooks)


well, if you want to do it your own way, then do as you want


Ok, I see what you mean. Using do not MatEditor.isOpen() for isChecked handler sure will work for macrobuttons. Topic starter asked exactly that.


most max dialogs send an “update toolbar buttons” message on open/close. so every macro button catches it with its own handlers.
but there may be some dialogs which don’t send. these dialogs need special attention.


do you know what kind of notification code could it be?

I guess it is possible to catch it with dotNetObject "managedservices.maxnotificationlistener" <code>


man i think you’re a genius
on ischecked do MatEditor.isOpen() worked very well

now the button gets checked when the dialog is opened and gets unchecked instantly with some dialogs and others after a couple of clicks here and there

thanks alot @Serejah for pointing out multiple ways i was not aware of them before
thanks alot guys


this notification should be forced by third party tools, and implemented by the system for its own dialogs that have action buttons on the main or other built-in toolbars:
see SetMacroButtonStates


Haven’t seen this method before, thanks!
I checked the sources for these methods in core.dll and looks like there’s nothing to subscribe and listen to, so it is not a notification but a way for plugin to invalidate and redraw button rect.