2

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)
Community
  • 1
  • 1
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • Have you tried setting WindowState to Normal? – Nemo Nov 01 '15 at 03:19
  • @Nemo Thanks for comment. Yes I tried every value of the enum but it does not restore the hidden window. I compared the resulting handle of `FindWindowEx` after the **While** loop ends with the handle that gives me the CMDOW commandline applicaton, and it is different (well, CMDOW gives me the main HWND), but the same code worked time ago, I don't understand why now happens that. – ElektroStudios Nov 01 '15 at 03:25
  • @ElektroStudios Did you try to make use of Process.GetProcessesByName("notepad") and Process.MainWindowHandle? – Tony Wu Nov 06 '15 at 15:03
  • @ElektroStudios I just found an article with same question in C#. You may make it into VB by yourself. http://stackoverflow.com/a/10400217/2884831 – Tony Wu Nov 06 '15 at 15:07
  • @Tony Wu Thanks for comment. **Process.MainWindowHandle** became "0" when the window is hidden. I cannot use the win32 **FindWindow** function because it will result in unexpected and inefficient result, such as when more than one instance of the same process are running, then, I need to use some robust approach like an unique identifier (handle/hwnd/pid) as I'm trying with **FindWindowEx** function. – ElektroStudios Nov 06 '15 at 21:16
  • For any reason **FindWindowEx** returns me the wrong window handle, but as I said, the code on the solution that I linked in my question worked for me time ago. I chedked the signatures of my platform invoke, all seems okay to me. – ElektroStudios Nov 06 '15 at 21:21

2 Answers2

2

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)
  • Thanks for answer. I'll share points between the two answers that I received, because @Hans Passant answer is very illustrative, and yours is a very good resolutive resume. Hard to decide, both are welcome as a solution. – ElektroStudios Nov 07 '15 at 09:37
2

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I know It is not well seen to comment juts to say thanks, but an answer like this deserves a big thanks for the time you spent writting this and linking references. – ElektroStudios Nov 07 '15 at 09:36