Close event handler on native window


#1

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


#2

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


#3

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

is the two methods you provided are the only methods ?


#4

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 ?


#5

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


#6

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

on ischecked do MatEditor.isOpen()

#7

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?

SetWinEventHook
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);

    [DllImport(\"user32.dll\")]
    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
       hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
       uint idThread, uint dwFlags);

    [DllImport(\"user32.dll\")]
    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 )
    {	
		hWinHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_SYSTEM_DESTROY, IntPtr.Zero,
                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)
        {
            return;
        }
		
        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.ReferencedAssemblies.Add("System.dll");
	
	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
			undefined
		)
		else
		(
			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)
	)
)

WinHook.Stop()
dotNet.removeAllEventHandlers WinHook

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

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

#8

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


#9

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


#10

do not MatEditor.isOpen()


#11

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


#12

try :wink:

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


#14

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


#15

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


#16

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.


#17

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

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


#18

@denisT
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


#19

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


#20

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.