Set the window state of a hidden window

2.7k views Asked by At

Time ago I asked this question, it was solved here:

But now, and for unknown reason, the C# or Vb.Net code provided there is not working, and I don't understand why not.

I did some modifications to the original code provided there, but I tested the originals and didn't worked.

What happens is that I can't undide a hidden process, I'm not sure where I'm failing. At first view I think that the handle I get with FindWindowEx does not really corresponds to the handle I want.

These are my P/Invoking function signatures and the showwindow enumeration:

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto, 
           BestFitMapping:=False, ThrowOnUnmappablechar:=True)>
Friend Shared Function FindWindow(
                 ByVal lpClassName As String,
                 ByVal lpWindowName As String
) As IntPtr
End Function

<DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, 
           BestFitMapping:=False, ThrowOnUnmappablechar:=True)>
Friend Shared Function FindWindowEx(
                 ByVal hwndParent As IntPtr,
                 ByVal hwndChildAfter As IntPtr,
                 ByVal strClassName As String,
                 ByVal strWindowName As String
) As IntPtr
End Function

<DllImport("user32.dll")>
Friend Shared Function GetWindowThreadProcessId(
                 ByVal hWnd As IntPtr,
                 ByRef processId As Integer
) As Integer
End Function

<DllImport("User32", SetLastError:=False)>
Friend Shared Function ShowWindow(
                 ByVal hwnd As IntPtr,
                 ByVal nCmdShow As ProcessUtil.WindowState
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

Public Enum WindowState As Integer
    Hide = 0
    Normal = 1
    ShowMinimized = 2
    Maximize = 3
    ShowMaximized = Maximize
    ShowNoActivate = 4
    Show = 5
    Minimize = 6
    ShowMinNoActive = 7
    ShowNA = 8
    Restore = 9
    ShowDefault = 10
    ForceMinimize = 11
End Enum

The function:

Public Function SetWindowState(ByVal p As Process,
                               ByVal windowState As ProcessUtil.WindowState) As Boolean

    Dim pHandle As IntPtr = IntPtr.Zero
    Dim pid As Integer

    ' If window is visible then...
    If (p.MainWindowHandle <> IntPtr.Zero) Then
        Return ProcessUtil.NativeMethods.ShowWindow(p.MainWindowHandle, windowState)

    Else ' window is hidden.

        ' Check all open windows (not only the process we are looking), 
        ' begining from the child of the desktop.
        While (pid <> p.Id)

            ' Get child handle of window who's handle is "pHandle".
            pHandle = NativeMethods.FindWindowEx(IntPtr.Zero, pHandle, Nothing, Nothing)

            ' Get PID from "pHandle".
            NativeMethods.GetWindowThreadProcessId(pHandle, pid)

        End While

        Return NativeMethods.ShowWindow(pHandle, windowState)

    End If

End Function

And the way that I'm trying to test the function, where first I hide the window of notepad process, then I try to unhide it.

Dim p As Process = Process.GetProcessesByName("notepad").First
ProcessUtil.SetWindowState(p, ProcessUtil.WindowState.Hide)

' I find again the process to renew the "p.MainWindowHandle" as IntPtr.Zero.
p = Process.GetProcessesByName("notepad").First
ProcessUtil.SetWindowState(p, ProcessUtil.WindowState.Restore)
2

There are 2 answers

1
γηράσκω δ' αεί πολλά διδασκόμε On BEST ANSWER

The problem with notepad is that it has 3 windows (spy++, class names):

1. "Notepad"
2. "MSCTFIME UI"
3. "IME"

you are getting the handle of the second one (I got anyway), MSCTFIME UI, thats why you can't show it. You need to specify the class name Notepad to get the correct handle:

pHandle = FindWindowEx(IntPtr.Zero, pHandle, "Notepad", Nothing)
1
Hans Passant On

It is high time that you start using the .NET Framework source code so you can discover why code like this doesn't work by yourself. Visit the Reference Source web site to get started.

In the search box type "Process.MainWindowHandle", you'll arrive at this page. Easy to see that it is ProcessManager.GetMainWindowHandle that gets the job done. Click on "GetMainWindowHandle". It is very small, click on "FindMainWindow". Note how it enumerates windows, scroll a bit to the right to see that it "EnumWindowsCallback" that gets the job done. Click it, easy to see that it "IsMainWindow" that decides whether a window is the main one. Click it:

    bool IsMainWindow(IntPtr handle) {

        if (NativeMethods.GetWindow(new HandleRef(this, handle), NativeMethods.GW_OWNER) != (IntPtr)0
            || !NativeMethods.IsWindowVisible(new HandleRef(this, handle)))
            return false;

        return true;
    }

Putting it into words, it is not a main window handle if it is an owned window. Or if it is not visible.

The latter clause is of course your nemesis. You hid the window, now the framework code no longer thinks it can be the main window. So Process.MainWindowHandle returns IntPtr.Zero and your code can no longer work.

One obvious workaround is to simply rewrite the .NET Framework code and skip the IsWindowVisible() test. It is however an important test, it avoids finding "message windows", the kind that almost any process creates. They are used for internal plumbing. The other answer mentions them. You can see them with Spy++, Notepad has two of them. Not otherwise created in an order that makes you find them, you'll find the main window first. That's not guaranteed to happen in any process.

The correct workaround is that you must not forget that you did something very unfriendly to that window. Notepad itself never hides its main window. You basically turned it into a zombie, the user cannot do anything to restore the window anymore. The only option left is to use Task Manager to kill the process. It is therefore entirely your job to make it visible again. A job you cannot ignore, you must restore them before your program exits for example.

Or just don't do this, messing with the user's windows like this is just plain hostile.