Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure a spawned process's windows are in front of the main application?

Tags:

c#

.net

windows

In my GUI application, I'm using the C# Process class to spawn external processes which may launch windows. The subprocess windows may be displayed via third-party API calls, so it's not always possible to get the window handle. Is there any way to ensure that the subprocess's windows are displayed in front of the main application window?

like image 346
japreiss Avatar asked Dec 14 '12 15:12

japreiss


2 Answers

The usual method is:

1 . Get Process class instance returned by Process.Start()
2 . Query Process.MainWindowHandle
3 . Call unmanaged Win32 API function "ShowWindow" or "SwitchToThisWindow"

The trick to your question is that "sub-process windows may be displayed via third-party API calls". In that case you will need Get the Window Handle of the spawned exe and Enum Child Windows. Once you have the Handles for the forms that are shown after API calls you can use the BringWindowToTop API.

I put together a small test using How To Enumerate Windows Using the WIN32 API as inspiration. Create a Windows application with 1 form and 2 buttons:

public partial class Form1 : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(IntPtr hWnd);

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);

public Form1()
{
    InitializeComponent();
}

private System.IntPtr hWnd;
private void button1_Click(object sender, EventArgs e)
{
    Process p = Process.Start(@"C:\TFS\Sandbox\3rdPartyAppExample.exe");         
    try
    {
        do
        {
            p.Refresh();
        }
        while (p.MainWindowHandle.ToInt32() == 0);

        hWnd = new IntPtr(p.MainWindowHandle.ToInt32());
    }
    catch (Exception ex)
    {
        //Do some stuff...
        throw;
    }
}

private void button2_Click(object sender, EventArgs e)
{
    3rdPartyAppExample.Form1 f = new 3rdPartyAppExample.Form1();
    f.ShowForm2();
    //Bring main external exe window to front
    BringWindowToTop(hWnd);
    //Bring child external exe windows to front
    BringExternalExeChildWindowsToFront(hWnd);
}

private void BringExternalExeChildWindowsToFront(IntPtr parent)
{
    List<IntPtr> childWindows = GetChildWindows(hWnd);
    foreach (IntPtr childWindow in childWindows)
    {
        BringWindowToTop(childWindow);
    }
}

// <summary>
/// Returns a list of child windows
/// </summary>
/// <param name="parent">Parent of the windows to return</param>
/// <returns>List of child windows</returns>
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
    List<IntPtr> result = new List<IntPtr>();
    GCHandle listHandle = GCHandle.Alloc(result);
    try
    {
        EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
        EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
    }
    finally
    {
        if (listHandle.IsAllocated)
            listHandle.Free();
    }
    return result;
}

/// <summary>
/// Callback method to be used when enumerating windows.
/// </summary>
/// <param name="handle">Handle of the next window</param>
/// <param name="pointer">Pointer to a GCHandle that holds a reference to the list to fill</param>
/// <returns>True to continue the enumeration, false to bail</returns>
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
    GCHandle gch = GCHandle.FromIntPtr(pointer);
    List<IntPtr> list = gch.Target as List<IntPtr>;
    if (list == null)
    {
        throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
    }
    list.Add(handle);
    //  You can modify this to check to see if you want to cancel the operation, then return a null here
    return true;
}

/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
/// <returns>True to continue enumerating, false to bail.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

}

The 3rdPartyAppExample is a Winform App with 2 forms. I reference this application and call Form1's Public method to show Form2:

public partial class Form1 : Form
{        
    public Form1()
    {
        InitializeComponent();
    }
    public void ShowForm2()
    {
        var f = new Form2();
        f.Show();
    }

Optionally you may wish to check the Windows Caption:

  hInst = ProcessStart("calc.exe")

  // Begin search for handle
  hWndApp = GetWinHandle(hInst)

  If hWndApp <> 0 Then
   // Init buffer
   buffer = Space$(128)

   // Get caption of window
   numChars = GetWindowText(hWndApp, buffer, Len(buffer))

The other solution (that isn't very stable) is discussed here: http://www.shloemi.com/2012/09/solved-setforegroundwindow-win32-api-not-always-works/

The trick is to make windows ‘think’ that our process and the target window (hwnd) are related by attaching the threads (using AttachThreadInput API).

Regarding the myTopForm.TopMost = true answer, that will not work for external applications.

like image 193
Jeremy Thompson Avatar answered Sep 21 '22 11:09

Jeremy Thompson


It has nothing to do with the "process" and everything to do with Windows focus.

You might be able to do something like this:

http://msdn.microsoft.com/en-us/library/3saxwsad.aspx

public void MakeOnTop()
{
  myTopForm.TopMost = true;
}

But in general, you'd need the Window handle (the spawned process can figure out its own handle easily enough) and you'd need something like this:

http://support.microsoft.com/kb/186431

like image 40
paulsm4 Avatar answered Sep 19 '22 11:09

paulsm4