January 24th, 2022

The MainWindowHandle property is just a guess based on heuristics

A customer had a program written in Windows Forms that wanted the following behavior:

  • When the user minimizes the app, it hides the window.
  • When the user relaunches the app, the second instance finds the existing (hidden) window and makes it visible again.

They got the “Hide when minimized” part working, but were not having success with the “Find the existing window and make it visible again.” Here’s their code:

Private Sub RestoreHiddenInstance
    For Each process As Process In Process.GetProcesses()
        If process.ProcessName.StartsWith("Contoso") Then
            If proceess.StartTime <> Process.GetCurrentProcess.StartTime Then
                ShowWindow(process.MainWindowHandle, SW_RESTORE)
                ShowWindow(process.MainWindowHandle, SW_SHOW)
                ShowWindow(process.MainWindowHandle, SW_SHOWDEFAULT)
                SetForegroundWindow(process.MainWindowHandle)
            End If
        End If
    Next
End Sub

They didn’t provide any debugging details about what “didn’t work.” All they said was that it “didn’t work.”

We noted some time ago that the Main­Window­Handle property is just a guess based on heuristics. There is no formal definition of a “main window” for a process. It’s a synthetic property driven by enumerating all the top-level of the windows that belong to the process and trying to guess which one is the main one.

And if you go to the reference source, you’ll see how the BCL decides whether a window is the main window:

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

According to the BCL heuristics, any unowned visible window is a candidate for being the “main” window.

Since the Contoso program hid all of its windows, there are no “main” windows as far as the Main­Window­Handle property is concerned. The process.Main­Window­Handle property is null, and naturally that means that the code doesn’t actually do anything with the main window of the previous instance.

You need to move away from the heuristic-based window-detection and design something more deterministic. Here are some ideas.

  • Give the main window a unique class name like Contoso_Main­Window. Enumerate the top-level windows owned by the previous instance and look for one that has the correct class name. (This solution won’t work for this particular customer because the class name for Windows Forms windows cannot be customized.)
  • Register a custom message like Is­Contoso­Main­Window. Have your main window respond TRUE to that message, and have the second instance send this message to each candidate, and see which one returns TRUE.
  • Create a named shared memory block and put the window handle in it.
  • Find some other shared data storage that you can use to hold the window handle.

This list is far from exhaustive, but gives you an idea of the sort of thinking you need to engage in.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

7 comments

Discussion is closed. Login to edit/delete existing comments.

  • Kasper Brandt

    > (This solution won’t work for this particular customer because the class name for Windows Forms windows cannot be customized.)

    You could override CreateHandle and reimplement most of the logic while registering your custom class. It seems that the existing logic only supports using system-defined classes[0].

    [0] https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/NativeWindow.cs,1883e2cdb3ad338c

  • Alexis Ryan · Edited

    A custom window message to tell the existing window to unhide itself seems like it would work. instead of querying the window to see if its the one and then unhide it

    • Frédéric B.

      You may need more than one message though. You could need one custom message to localize the window so you can send, say, a file path through WM_COPYDATA.

  • Neil Rashbrook

    This was so much easier back in 16-bit Windows when you could simply use hPrevInst to access the main window variable in your original instance’s data segment.

  • Harry Johnston

    Typo: proceess.StartTime

    • Me Gusta

      Focus is a tricky thing. You see, that statement of "window opened by the process that currently has the focus" is technically bad.
      If, as an example, you have a small dialog window with a text box and two buttons, if you are entering text into the text box then it is the text box that has focus, not the dialog window.
      What's more, logically, a window doesn't stop being the main window if the...

      Read more