Bring Max Process to Front


#1

Trying to figure out a way to make 3dsmax jump to front of windows focus. I’ve tried using this user32 method but it doesn’t seem to work. Any ideas? run this script, jump to another windows application, wait 15 seconds… max should come back to focus?

(
fn CreateUser32Assembly =
(
	src  = "using System;"
	src += "using System.Runtime.InteropServices;"
	src += "class User32"
	src += "{"
	src += "[DllImport(\"User32.dll\")]"
	src += "public static extern bool SetForegroundWindow(IntPtr hWnd);"
	src += "[DllImport(\"User32.dll\", SetLastError=true)]"
	src += "public static extern IntPtr SetActiveWindow(IntPtr hWnd);"
	src += "[DllImport(\"User32.dll\")]"
	src += "public static extern IntPtr GetActiveWindow();"
        src += "}"

    provider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	params = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

	params.GenerateInMemory = true
	result = provider.CompileAssemblyFromSource params #(src)
	result.CompiledAssembly.CreateInstance "User32"
)
 
user32assembly


fn SetActiveWindowHWND hWnd=
(
	if user32assembly == undefined do user32assembly = CreateUser32Assembly()
	return (user32assembly.SetActiveWindow hWnd)
)

try (destroydialog ::RO_TEST) catch()

rollout RO_TEST "" width:176 height:72
(
	
	local maxHWND = windows.getMAXHWND()
	
	on RO_TEST open do
	(
		sleep 15
		SetActiveWindowHWND maxHWND
	)
)

createdialog RO_TEST
)

#2

sorry my mistake


#3

Tried that too, but it just makes the taskbar item flash… which is 50% better :slight_smile:


#4

What if you use SetForegroundWindow() instead?

(
	fn CreateUser32Assembly =
	(
		src  = "using System;"
		src += "using System.Runtime.InteropServices;"
		src += "class User32"
		src += "{"
		src += "[DllImport(\"User32.dll\")]"
		src += "public static extern bool SetForegroundWindow(IntPtr hWnd);"
		src += "}"
		
		provider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		params = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		
		params.GenerateInMemory = true
		result = provider.CompileAssemblyFromSource params #(src)
		result.CompiledAssembly.CreateInstance "User32"
	)
	
	user32assembly = CreateUser32Assembly()
	
	try (destroydialog ::RO_TEST) catch()

	rollout RO_TEST "" width:176 height:72
	(
		on RO_TEST open do
		(
			sleep 8
			maxHWND = dotnetobject "system.IntPtr" (windows.getMAXHWND())
			user32assembly.SetForegroundWindow maxHWND
		)
	)

	createdialog RO_TEST
)

#5

try this one: ForceForegroundWindow

	src  = "using System;"
	src += "using System.Runtime.InteropServices;"
	src += "class User32"
	src += "{"
	src += "[DllImport(\"User32.dll\")]
        public static extern bool ShowWindow(IntPtr handle, int nCmdShow);
	[DllImport(\"User32.dll\")]
	static extern bool BringWindowToTop(IntPtr hWnd);
	[DllImport(\"User32.dll\")]
	public static extern IntPtr GetForegroundWindow();
	[DllImport(\"user32.dll\")]
	static extern bool AttachThreadInput(uint idAttach, uint idAttachTo,bool fAttach);
	[DllImport(\"user32.dll\")]
	static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
	[DllImport(\"kernel32.dll\")]
	static extern uint GetCurrentThreadId();
		
	public static void ForceForegroundWindow(IntPtr hWnd)
	{
		uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
		uint appThread = GetCurrentThreadId();    
                const int SW_SHOW = 5;
		if (foreThread != appThread)
		{			
			AttachThreadInput(foreThread, appThread, true);
			BringWindowToTop(hWnd);		
			ShowWindow(hWnd, SW_SHOW);
			AttachThreadInput(foreThread, appThread, false);

		}
		else
		{
			BringWindowToTop(hWnd);
			ShowWindow(hWnd, SW_SHOW);
		}
	}}"

based on this stackoverflow answer

if I switch to browser and wait until SetForegroundWindow will execute it just gets highlighted in the taskbar without actually switching back to max
tested on 2014 as usual


#6

Maybe it’s a Windows issue? I tested on Win7 Max 2014.
Here is another one:

(
	fn CreateUser32Assembly =
	(
		src  = "using System;"
		src += "using System.Runtime.InteropServices;"
		src += "class User32"
		src += "{"
		src += "[DllImport(\"User32.dll\")]"
		src += "public static extern bool SwitchToThisWindow(IntPtr hWnd, bool fUnknown);"
		src += "}"
		
		provider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		params = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		
		params.GenerateInMemory = true
		result = provider.CompileAssemblyFromSource params #(src)
		result.CompiledAssembly.CreateInstance "User32"
	)
	
	user32assembly = CreateUser32Assembly()
	
	try (destroydialog ::RO_TEST) catch()

	rollout RO_TEST "" width:176 height:72
	(
		timer clock "" interval:8000
		
		on clock tick do
		(
			maxHWND = dotnetobject "system.IntPtr" (windows.getMAXHWND())
			user32assembly.SwitchToThisWindow maxHWND true
			destroydialog ::RO_TEST
		)
	)

	createdialog RO_TEST
)

This one does bring Max to the front even if minimized.


#7

That brings it back only if it’s minimized, otherwise it flashes on taskbar…


#8

Running on Windows 10 here


#9

same story win7x64


#10

This one appears to work though!

What I’m doing is making it so you can use Windows Toast notification to notify a user after max has been doing something for a very long time, it’ll pop up a toast, and then when you click on the notification it’ll bring that session of max back into focus.


#11

Alas not when I’ve connected it to the Toast Notification…


#12

because you ask to bring MAX to top from MAX it should be enough only:

SetWindowPos(max_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetWindowPos(max_hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);

(it’s c++ Win32)

after that you should set focus to the control you want


#13

Is this focus fighting?

struct notify 
(
	fn CreateUser32Assembly =
(
src  = "using System;"
src += "using System.Runtime.InteropServices;"
src += "class User32"
src += "{"
src += "[DllImport(\"User32.dll\")]
    public static extern bool ShowWindow(IntPtr handle, int nCmdShow);
[DllImport(\"User32.dll\")]
static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport(\"User32.dll\")]
public static extern IntPtr GetForegroundWindow();
[DllImport(\"user32.dll\")]
static extern bool AttachThreadInput(uint idAttach, uint idAttachTo,bool fAttach);
[DllImport(\"user32.dll\")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport(\"kernel32.dll\")]
static extern uint GetCurrentThreadId();
	
public static void ForceForegroundWindow(IntPtr hWnd)
{
	uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
	uint appThread = GetCurrentThreadId();    
            const int SW_SHOW = 5;
	if (foreThread != appThread)
	{			
		AttachThreadInput(foreThread, appThread, true);
		BringWindowToTop(hWnd);		
		ShowWindow(hWnd, SW_SHOW);
		AttachThreadInput(foreThread, appThread, false);

	}
	else
	{
		BringWindowToTop(hWnd);
		ShowWindow(hWnd, SW_SHOW);
	}
}}"
	
	provider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
	params = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
	
	params.GenerateInMemory = true
	result = provider.CompileAssemblyFromSource params #(src)
	result.CompiledAssembly.CreateInstance "User32"
),

maxHWND,
	
fn BalloonTipClicked = 
(
	user32assembly = _notifyUser.CreateUser32Assembly()
	user32assembly.ForceForegroundWindow _notifyUser.maxHWND

),
fn BalloonClick             = NotifyIcon.Dispose(),
fn notifyUser title:"3ds Max Notification" message:"" =
(
	--Use this to notify using the windows 10 pop-up 'toast' notifications.
	--Useful for when someone leaves a max session to run a script for a while and to alert them that something is done.
	
	NotifyIcon = dotnetobject "System.Windows.Forms.NotifyIcon"
	
	NotifyIcon.BalloonTipTitle = title
	NotifyIcon.BalloonTipText  = message
	NotifyIcon.Visible         = true
	
	-- Application Asterisk Error Exclamation Hand Information Question Shield Warning WinLogo --
	NotifyIcon.Icon = (dotnetclass "System.Drawing.SystemIcons").Asterisk
	
	
	--fn BalloonTipClosed  = NotifyIcon.Dispose()
	maxHWND = (dotnetobject "system.IntPtr" (windows.getMAXHWND()))
		
	dotnet.addeventhandler NotifyIcon "BalloonTipClicked" BalloonTipClicked
	dotnet.addeventhandler NotifyIcon "Click" BalloonClick
	--dotnet.addeventhandler NotifyIcon "BalloonTipClosed" BalloonTipClosed
	
	NotifyIcon.ShowBalloonTip 0
	
)

)
_notifyUser = notify()

sleep 10
_notifyUser.notifyUser message:"test"

#14

or as Sereiah showed above:

BringWindowToTop(hwnd);
ShowWindow(hwnd, SW_SHOW);

all other doesn’t make sense because you call it via MXS (which means same as MAX process and foreground window)


#15

Any idea why it doesn’t work when using the toast notification as above?


#16

Even tough, this works consistently well on my end, across all Max versions from 2014 to 2021. Weird.

When I click the Ballontip it brings Max to the front, regardless if it is maximized or minimized.

(
	
	fn CreateUser32Assembly =
	(
		src  = "using System;"
		src += "using System.Runtime.InteropServices;"
		src += "class User32"
		src += "{"
		src += "[DllImport(\"User32.dll\")]"
		src += "public static extern bool SwitchToThisWindow(IntPtr hWnd, bool fUnknown);"
		src += "}"
		
		provider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		params   = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
		
		params.GenerateInMemory = true
		result = provider.CompileAssemblyFromSource params #(src)
		result.CompiledAssembly.CreateInstance "User32"
	)
	
	user32 = CreateUser32Assembly()

	NotifyIcon                 = dotnetobject "System.Windows.Forms.NotifyIcon"
	NotifyIcon.BalloonTipTitle = "3ds Max Notification"
	NotifyIcon.BalloonTipText  = "Task Completed!"
	NotifyIcon.Visible         = true
	
	/* Application Asterisk Error Exclamation Hand Information Question Shield Warning WinLogo */
	NotifyIcon.Icon = (dotnetclass "System.Drawing.SystemIcons").Asterisk
	
	fn SwitchToMax =
	(
		maxHWND = dotnetobject "system.IntPtr" (windows.getMAXHWND())
		user32.SwitchToThisWindow maxHWND true
		NotifyIcon.Dispose()
	)
	
	dotnet.addeventhandler NotifyIcon "BalloonTipClicked" SwitchToMax
	dotnet.addeventhandler NotifyIcon "Click" SwitchToMax
	dotnet.addeventhandler NotifyIcon "BalloonTipClosed" SwitchToMax
	
	try (destroydialog ::RO_TEST) catch()

	rollout RO_TEST "" width:242 height:72
	(
		label lb "A NOTIFICATION WILL BE SEND OUT IN: 10" pos:[12,24]
		timer clock "" interval:1000
		
		local counter = 10
		
		on clock tick do
		(
			counter -= 1
			if counter == 0 do
			(
				NotifyIcon.ShowBalloonTip 0
				destroydialog RO_TEST
			)
			lb.text = "A NOTIFICATION WILL BE SEND OUT IN: " + (counter as string)
		)
	)

	createdialog RO_TEST
	
)

#17

Must be a win7/10 thing… it only flashes on taskbar for me…


#18

here is my version (works for me):

global WinAssembly
fn CreateWinAssembly forceRecompile:off =
(
	if forceRecompile or not iskindof ::WinAssembly dotnetobject or (::WinAssembly.GetType()).name != "Assembly" do
	(
		source  = "using System;\n"
		source += "using System.Runtime.InteropServices;\n"
		source += "using System.Text;\n"
		source += "class _user32\n"
		source += "{\n"
		source += " [DllImport(\"user32.dll\")]\n"
		source += " public static extern bool SetWindowPos(Int32 hWnd, int hWndArg, int Left, int Top, int Width, int Height, int hWndFlags);\n"
		source += "	public void BringWindowToTop(Int32 hWnd)\n"
		source += "	{\n"
		source += "		SetWindowPos(hWnd, -1, 0, 0, 0, 0, 3);\n"
		source += "		SetWindowPos(hWnd, -2, 0, 0, 0, 0, 67);\n"
		source += "	}\n"
		source += "\t[DllImport(\"user32.dll\")]\n"
		source += "\tpublic static extern Int32 SetFocus(Int32 hWnd);\n"
		source += "}\n"

		csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
						
		compilerParams.GenerateInMemory = on
		compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
		
		WinAssembly = compilerResults.CompiledAssembly
		WinAssembly.CreateInstance "_user32"
	)
)


global _user32 = if _user32 == undefined then CreateWinAssembly() else _user32

try(destroydialog rol) catch()
rollout rol "Test" width:191
(
	local ni
	
	fn setupNotifyIcon = 
	(
		local NotifyIcon = dotnetobject "System.Windows.Forms.NotifyIcon"
		
		NotifyIcon.BalloonTipTitle = "3ds Max Notification"
		NotifyIcon.BalloonTipText  = "Call 3DS MAX !"
		NotifyIcon.Visible         = true
		
		NotifyIcon.Icon = (dotnetclass "System.Drawing.SystemIcons").Asterisk
		
		fn BalloonTipClicked sender args = 
		(
			hwnd = windows.getMAXHWND()
			_user32.BringWindowToTop hwnd

			sender.Dispose()
		)
		
		fn Click sender args = 
		(
			sender.Dispose()
		)
		
		dotnet.addeventhandler NotifyIcon "BalloonTipClicked" BalloonTipClicked
		dotnet.addeventhandler NotifyIcon "Click" Click
		
		NotifyIcon
	)

	button easy_topmost_tb "Easy Topmost" width:185
	
	on easy_topmost_tb pressed do
	(
		ni = setupNotifyIcon()
		
		sleep 10

		ni.ShowBalloonTip 0
	)

	on rol open do
	(
	)

)
createdialog rol

#19

Scratch that… scope variable issue


#20

Yep that works! DenisT magic for the win! Thanks a lot!