Restore a minimized window of another application
Asked Answered
G

6

26

I'm adding some code to an app that will launch another app if it isn't already running, or if it is, bring it to the front. This requires a small amount of interop/WinAPI code, which I've gotten examples for from other sites but can't seem to get to work in Win7.

If the window is in some visible state, then the API's SetForegroundWindow method works like a treat (and this would be the main case, as per company policy if the external app is running it should not be minimized). However, if it is minimized (exceptional but important as my app will appear to do nothing in this case), neither this method nor ShowWindow/ShowWindowAsync will actually bring the window back up from the taskbar; all of the methods simply highlight the taskbar button.

Here's the code; most of it works just fine, but the call to ShowWindow() (I've also tried ShowWindowAsync) just never does what I want it to no matter what the command I send is:

[DllImport("user32.dll")]
    private static extern int SetForegroundWindow(IntPtr hWnd);

    private const int SW_SHOWNORMAL = 1;
    private const int SW_SHOWMAXIMIZED = 3;
    private const int SW_RESTORE = 9;

    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

...

//The app is named uniquely enough that it can't be anything else,
//and is not normally launched except by this one.
//so this should normally return zero or one instance
var processes = Process.GetProcessesByName("ExternalApp.exe");

        if (processes.Any()) //a copy is already running
        {
            //I can't currently tell the window's state,
            //so I both restore and activate it
            var handle = processes.First().MainWindowHandle;
            ShowWindow(handle, SW_RESTORE); //GRR!!!
            SetForegroundWindow(handle);
            return true;
        }

        try
        {
            //If a copy is not running, start one.
            Process.Start(@"C:\Program Files (x86)\ExternalApp\ExternalApp.exe");
            return true;
        }
        catch (Exception)
        {
            //fallback for 32-bit OSes
            Process.Start(@"C:\Program Files\ExternalApp\ExternalApp.exe");
            return true;
        }

I've tried SHOWNORMAL (1), SHOWMAXIMIZED (3), RESTORE (9), and a couple other sizing commands, but nothing seems to do the trick. Thoughts?

EDIT: I found an issue with some of the other code I had thought was working. The call to GetProcessesByName() was not finding the process because I was looking for the executable name, which was not the process name. That caused the code I thought was running and failing to actually not execute at all. I thought it was working because the external app will apparently also detect that a copy is already running and try to activate that current instance. I dropped the ".exe" from the process name I search for and now the code executes; however that seems to be a step backwards, as now the taskbar button isn't even highlighted when I call ShowWindow[Async]. So, I now know that neither my app, nor the external app I'm invoking, can change the window state of a different instance programmatically in Win7. What's goin' on here?

Goat answered 1/2, 2012 at 16:31 Comment(2)
Have you tried making the window visible before trying to restore it, with a line like this: ShowWindow(handle, SW_SHOW);?Fromm
I had tried a lot of permutations including calling ShowWindow first. The problem was that the thread provided by Process.MainWindowHandle was not the thread of the "main window".Goat
G
15

... Apparently you cannot trust the information a Process gives you.

Process.MainWindowHandle returns the window handle of the first window created by the application, which is USUALLY that app's main top-level window. However, in my case, a call to FindWindow() shows that the handle of the actual window I want to restore is not what MainWindowHandle is pointing to. It appears that the window handle from the Process, in this case, is that of the splash screen shown as the program loads the main form.

If I call ShowWindow on the handle that FindWindow returned, it works perfectly.

What's even more unusual is that when the window's open, the call to SetForegroundWindow(), when given the process's MainWindowHandle (which should be invalid as that window has closed), works fine. So obviously that handle has SOME validity, just not when the window's minimized.

In summary, if you find yourself in my predicament, call FindWindow, passing it the known name of your external app's main window, to get the handle you need.

Goat answered 1/2, 2012 at 17:54 Comment(5)
There is no "Main window" concept, the author of that object just assumed that the first would be it. Further more, MSDN says "Brings the thread that created the specified window into the foreground"Kathlyn
Good info. Apparently I fell into the trap. Anyway, FindWindow, given that you know the title of the window you want, gives the right answer.Goat
+1 @Goat what find window method are you using here. I take it that you are talking about an Interop method?Enwreathe
Yes, a P/Invoke call to FindWindow in the same WinAPI dll as ShowWindow.Goat
+1 - FindWindow was the answer in my case too. MainWindowHandle worked fine, until I would hide the window on minimize & made a TrayIcon visible instead. In this case, MainWindowHandle returned the handle of the TrayIcon... Gotta love that beautiful win32 API, huh? :DSemanteme
C
25

Working code using FindWindow method:

[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string className, string windowTitle);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, ShowWindowEnum flags);

[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowPlacement(IntPtr hWnd, ref Windowplacement lpwndpl);

private enum ShowWindowEnum
{
    Hide = 0,
    ShowNormal = 1, ShowMinimized = 2, ShowMaximized = 3,
    Maximize = 3, ShowNormalNoActivate = 4, Show = 5,
    Minimize = 6, ShowMinNoActivate = 7, ShowNoActivate = 8,
    Restore = 9, ShowDefault = 10, ForceMinimized = 11
};

private struct Windowplacement
{
    public int length;
    public int flags;
    public int showCmd;
    public System.Drawing.Point ptMinPosition;
    public System.Drawing.Point ptMaxPosition;
    public System.Drawing.Rectangle rcNormalPosition;
}

private void BringWindowToFront()
{
    IntPtr wdwIntPtr = FindWindow(null, "Put_your_window_title_here");

    //get the hWnd of the process
    Windowplacement placement = new Windowplacement();
    GetWindowPlacement(wdwIntPtr, ref placement);

    // Check if window is minimized
    if (placement.showCmd == 2)
    {
        //the window is hidden so we restore it
        ShowWindow(wdwIntPtr, ShowWindowEnum.Restore);
    }

    //set user's focus to the window
    SetForegroundWindow(wdwIntPtr);
}

You can use it by calling BringWindowToFront().

I always have one instance of the application running so if you can have several open instances simultaneously you might want to slightly change the logic.

Chary answered 26/1, 2016 at 15:51 Comment(2)
Just a note... IsIconic() works just as well as GetWindowPlacement() for checking if a window is minimized... and is a little simpler to deal with.Napoli
It's worth nothing that ShowWindow() doesn't work (fails silently) if the application window you are trying to restore is running under elevated permissions (i.e. as an administrator) and the current process is not. To work around this, ShowWindow() must also be called from a process running with the same elevated permissions.Hales
G
15

... Apparently you cannot trust the information a Process gives you.

Process.MainWindowHandle returns the window handle of the first window created by the application, which is USUALLY that app's main top-level window. However, in my case, a call to FindWindow() shows that the handle of the actual window I want to restore is not what MainWindowHandle is pointing to. It appears that the window handle from the Process, in this case, is that of the splash screen shown as the program loads the main form.

If I call ShowWindow on the handle that FindWindow returned, it works perfectly.

What's even more unusual is that when the window's open, the call to SetForegroundWindow(), when given the process's MainWindowHandle (which should be invalid as that window has closed), works fine. So obviously that handle has SOME validity, just not when the window's minimized.

In summary, if you find yourself in my predicament, call FindWindow, passing it the known name of your external app's main window, to get the handle you need.

Goat answered 1/2, 2012 at 17:54 Comment(5)
There is no "Main window" concept, the author of that object just assumed that the first would be it. Further more, MSDN says "Brings the thread that created the specified window into the foreground"Kathlyn
Good info. Apparently I fell into the trap. Anyway, FindWindow, given that you know the title of the window you want, gives the right answer.Goat
+1 @Goat what find window method are you using here. I take it that you are talking about an Interop method?Enwreathe
Yes, a P/Invoke call to FindWindow in the same WinAPI dll as ShowWindow.Goat
+1 - FindWindow was the answer in my case too. MainWindowHandle worked fine, until I would hide the window on minimize & made a TrayIcon visible instead. In this case, MainWindowHandle returned the handle of the TrayIcon... Gotta love that beautiful win32 API, huh? :DSemanteme
I
12

I had the same problem. The best solution I have found is to call ShowWindow with the flag SW_MINIMIZE, and then with SW_RESTORE. :D

Another possible solution:

// Code to display a window regardless of its current state
ShowWindow(hWnd, SW_SHOW);  // Make the window visible if it was hidden
ShowWindow(hWnd, SW_RESTORE);  // Next, restore it if it was minimized
SetForegroundWindow(hWnd);  // Finally, activate the window 

from comments at: http://msdn.microsoft.com/en-us/library/ms633548%28VS.85%29.aspx

Imbrue answered 21/3, 2012 at 15:34 Comment(1)
ShowWindow(hWnd, SW_SHOW) and `SetForeGroundWindow(hWnd) were key for me. Using just "restore" did not work. Awesome!Dozy
G
4

Tray calling ShowWindow(handle, SW_RESTORE); after SetForegroundWindow(handle);

This might solve your problem.

Grilled answered 9/2, 2012 at 4:50 Comment(0)
A
2

It sounds like you're trying to perform an action that has the same result as alt-tabbing, which brings the window back if it was minimized while "remembering" if it was maximized.

NativeMethods.cs:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

// Specify your namespace here
namespace <your.namespace>
{
    static class NativeMethods
    {
        // This is the Interop/WinAPI that will be used
        [DllImport("user32.dll")]
        static extern void SwitchToThisWindow(IntPtr hWnd, bool fUnknown);
    }
}

Main code:

// Under normal circumstances, only one process with one window exists
Process[] processes = Process.GetProcessesByName("ExternalApp.exe");
if (processes.Length > 0 && processes[0].MainWindowHandle != IntPtr.Zero)
{
    // Since this simulates alt-tab, it restores minimized windows to their previous state
    SwitchToThisWindow(process.MainWindowHandle, true);
    return true;
}
// Multiple things are happening here
// First, the ProgramFilesX86 variable automatically accounts for 32-bit or 64-bit systems and returns the correct folder
// Secondly, $-strings are the C# shortcut for string.format() (It automatically calls .ToString() on each variable contained in { })
// Thirdly, if the process was able to start, the return value is not null
try { if (Process.Start($"{System.Environment.SpecialFolder.ProgramFilesX86}\\ExternalApp\\ExternalApp.exe") != null) return true; }
catch
{
    // Code for handling an exception (probably FileNotFoundException)
    // ...
    return false;
}
// Code for when the external app was unable to start without producing an exception
// ...
return false;

I hope this provides a much simpler solution.

(General Rule: If a string value is ordinal, i.e. it belongs to something and isn't just a value, then it is better to get it programmatically. You'll save yourself a lot of trouble when changing things. In this case, I'm assuming that the install location can be converted to a global constant, and the .exe name can be found programmatically.)

Ajmer answered 20/4, 2019 at 0:30 Comment(0)
D
2

I know its too late, still my working code is as follows so that someone later can get quick help :)

using System.Runtime.InteropServices;
using System.Diagnostics;

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

[DllImport("user32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);

private static void ActivateApp(string processName)
{
    Process[] p = Process.GetProcessesByName(processName);

    if (p.Length > 0)
    {
        IntPtr handle = FindWindowByCaption(IntPtr.Zero, p[0].ProcessName);
        ShowWindow(handle, 9); // SW_RESTORE = 9,
        SetForegroundWindow(handle);
    }
} 

ActivateApp(YOUR_APP_NAME);

Actually, FindWindowByCaption is the key here, this method collects the window handle correctly when app is running silently in the system tray and also when app is minimized.

Diaconicum answered 12/11, 2019 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.