Search code examples
c#user32interopservices

How do I get child windows from another process and not child controls?


I am tasked with finding a way to make another application appear on top of other windows (Always On Top). I am able to get processes that have a Window Title using the RetrieveProcesses() function. Once the user selects which process they want to modify, my application will call either MakeProcessOnTop or MakeProcessNormal. Both functions modify the main application's window. Before I added modifying its children, this worked correctly.

I then discovered this wouldn't work on child windows (like an email in outlook) so I set off to find a way to handle child windows. The way the following code is written, it will end up messing up child windows. How do I get the handle pointer of child windows but not child controls?

public static class ProcessManagement
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);

    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SetFocus(IntPtr hWnd);

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

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);

    static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
    static readonly IntPtr HWND_TOP = new IntPtr(0);
    static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

    public static IEnumerable<Process> RetrieveProcesses()
    {
        List<Process> returnList = new List<Process>();

        Process[] processArray = Process.GetProcesses();

        foreach (Process p in processArray)
        {
            if (!String.IsNullOrEmpty(p.MainWindowTitle))
            {
                returnList.Add(p);
            }
        }

        return returnList;
    }

    public static IntPtr GetProcessWindowHandle(int processId)
    {
        Process p = Process.GetProcessById(processId: processId);
        return p.MainWindowHandle;
    }

    public static List<IntPtr> GetProcessChildWindowHandles(IntPtr parent)
    {
        List<IntPtr> result = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(result);
        try
        {
            EnumWindowsProc childProc = new EnumWindowsProc(EnumWindow);
            EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated)
                listHandle.Free();
        }
        return result;
    }

    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;
    }

    public static bool MakeProcessOnTop(IntPtr targetWindowHandle, bool targetChildren = true)
    {
        bool bReturn = true;

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Minimize))
        {
            bReturn = false;
        }

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Restore))
        {
            bReturn = false;
        }

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.ShowNoActivate))
        {
            bReturn = false;
        }

        if (!SetWindowPos(targetWindowHandle, HWND_TOPMOST, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE))
        {
            bReturn = false;
        }

        if (targetChildren)
        {
            List<IntPtr> childProcesses = GetProcessChildWindowHandles(targetWindowHandle);

            foreach(IntPtr iPtr in childProcesses)
            {
                MakeProcessOnTop(iPtr, false);
            }
        }

        return bReturn;
    }
    public static bool MakeProcessNormal(IntPtr targetWindowHandle, bool targetChildren = true)
    {
        bool bReturn = true;

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Minimize))
        {
            bReturn = false;
        }

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Restore))
        {
            bReturn = false;
        }

        if (!ShowWindow(targetWindowHandle, ShowWindowCommands.ShowNoActivate))
        {
            bReturn = false;
        }

        if (!SetWindowPos(targetWindowHandle, HWND_NOTOPMOST, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE))
        {
            bReturn = false;
        }

        if (targetChildren)
        {
            List<IntPtr> childProcesses = GetProcessChildWindowHandles(targetWindowHandle);

            foreach (IntPtr iPtr in childProcesses)
            {
                MakeProcessNormal(iPtr, false);
            }
        }

        return bReturn;
    }
}

Solution

  • Always On Top only makes sense for top level windows or possibly MDI children.

    You could raise a child window by manipulating the Z order but it's not well defined how to put it back.