Link to home
Start Free TrialLog in
Avatar of BlearyEye
BlearyEyeFlag for United States of America

asked on

Making detached MessageBox visible with a parent form

This relates to https://www.experts-exchange.com/questions/26503615/Display-parent-screen-when-child-has-messagebox.html.

The attached code exercises parent form visibility in the presence of message boxes. The code as shown works fine with a message box that's associated with a form. But if the message box is displayed by a separate thread, it doesn't work.

In the code, I've launched the MessageBox in Parent.Parent_Load via the thread named detachedMessageBoxThread. I'd like for it to appear and disappear along with the parent form.

Test-Parent-Form.zip
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Really don't know what the "big picture" is here...

Why not just use a CUSTOM FORM instead of a MessageBox?

Then you can use a Timer and simply toggle the visibility of the "parent" form...
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ShowDialog(this); // pass in this form as the "Owner"
        }
    }

    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        Timer tmr = new Timer();

        private void Form2_Load(object sender, EventArgs e)
        {
            tmr.Interval = 2000;
            tmr.Tick +=new EventHandler(tmr_Tick);
            tmr.Start();
        }

        private void tmr_Tick(object sender, EventArgs e)
        {
            this.Owner.Visible = !this.Owner.Visible;
        }

    }

Open in new window

Avatar of BlearyEye

ASKER

The big picture is that I'm trying to adapt an existing application so that multiple instances can run, each in its own process. I need to be able to control which instance is visible at a given time.

I've tried a variety of approaches; at the moment, am using Application.OpenForms() to set visibility of forms but it doesn't handle MessageBox. Using a parent form seems more promising (i can make it visible or not, and child forms follow suit) except for this issue of detached message boxes.

I thought about using my own custom form, but MessageBox.Show() has a bunch of overloads and I'd have to handle each use in the application. In any event, I'd rather not have to go thru and fix every use of MessageBox.
"The big picture is that I'm trying to adapt an existing application so that multiple instances can run, each in its own process. I need to be able to control which instance is visible at a given time."

Can you expound on this aspect more?  What is currently preventing it from running "properly" with multiple instances?

You can use the SetforegroundWindows() API to make a particular instance the forefront window when desired.

The Process.GetProcessesByName() method can be used to get handles to all the instances...
To expound: At the moment, a dashboard launches the instances and sends a message to each (via WCF) directing it to either show or hide itself. The instance uses Application.OpenForms() to get a list of forms which are then brought to front or sent to back as appropriate. This has two problems: it distorts the z-order but more importantly, it misses message box forms and they wind up hidden behind the application forms.

My thought was to switch to a form that is parent to all the others in the application. The code I posted demonstrates that I can change the viewability of the parent and all child forms become viewable or not at the same time, maintaining z-order, and handling message boxes that were launched by a form. However, message boxes that launched by a separate thread are not handled. If you run the sample code I posted, you'll see.

Since the dashboard is launching the threads, I should have the thread handles available at that time and can save them. I'll look into SetForegroundWindow to see if it handles my situation here ...
Ok...so it sounds like you already have a handle on closing the MAIN windows/forms based on the instances via your WCF messaging scheme.

I think the question you will really want answered is:

    "How can I find all MessageBoxes (dialogs) associated with a particular application and hide them?"

Here is an example that will hide Notepad along with its MessageBox:
Imports System.Text
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
Public Class Form1

    Private Const SW_HIDE As Integer = 0
    Private Const SW_NORMAL As Integer = 1

    Private Declare Function GetParent Lib "user32" (ByVal hwnd As IntPtr) As IntPtr
    Private Declare Function ShowWindow Lib "user32" Alias "ShowWindow" (ByVal hwnd As IntPtr, ByVal nCmdShow As Integer) As Integer

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim Instances() As Process = Process.GetProcessesByName("Notepad") ' <-- NO path and NO extension!

        ' find all dialog boxes
        Dim we As New WindowsEnumerator
        For Each aw As ApiWindow In we.GetTopLevelWindows("#32770")
            Dim ParentHandle As IntPtr = GetParent(aw.Handle)
            For Each instance As Process In Instances
                If instance.MainWindowHandle.Equals(ParentHandle) Then
                    Debug.Print("Notepad: " & instance.MainWindowHandle.ToString("X"))
                    Debug.Print("Notepad Dialog: " & aw.Handle.ToString("X"))
                    Debug.Print("-----------------------------------------------------")

                    ShowWindow(instance.MainWindowHandle, SW_HIDE)
                    ShowWindow(aw.Handle, SW_HIDE)

                    System.Threading.Thread.Sleep(3000)

                    ShowWindow(instance.MainWindowHandle, SW_NORMAL)
                    ShowWindow(aw.Handle, SW_NORMAL)
                    Exit For
                End If
            Next
        Next
    End Sub

End Class

Public Class ApiWindow
    Public Text As String = ""
    Public ClassName As String = ""
    Public Handle As IntPtr
End Class

Public Class WindowsEnumerator

    Private Delegate Function EnumCallBackDelegate _
        (ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
    Private Declare Function EnumWindows Lib "user32" _
        (ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer
    Private Declare Function EnumChildWindows Lib "user32" _
        (ByVal ParentHandle As IntPtr, ByVal lpEnumFunc As EnumCallBackDelegate, _
         ByVal lParam As Integer) As Integer
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
        (ByVal handle As IntPtr, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
    Private Declare Function IsWindowVisible Lib "user32" (ByVal handle As IntPtr) As Integer
    Private Declare Function GetParent Lib "user32" (ByVal handle As IntPtr) As IntPtr
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal handle As IntPtr, ByVal wMsg As Integer, _
         ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
        (ByVal handle As IntPtr, ByVal wMsg As Integer, _
         ByVal wParam As Integer, ByVal lParam As StringBuilder) As Integer

    Private _listChildren As New List(Of ApiWindow)
    Private _listTopLevel As New List(Of ApiWindow)

    Private _topLevelClass As String = ""
    Private _childClass As String = ""

    Public Overloads Function GetTopLevelWindows() As List(Of ApiWindow)
        EnumWindows(AddressOf EnumWindowProc, &H0)
        Return _listTopLevel
    End Function

    Public Overloads Function GetTopLevelWindows(ByVal className As String) As List(Of ApiWindow)
        _topLevelClass = className
        Return Me.GetTopLevelWindows()
    End Function

    Public Overloads Function GetChildWindows(ByVal handle As IntPtr) As List(Of ApiWindow)
        ' Clear the window list.
        _listChildren = New List(Of ApiWindow)
        ' Start the enumeration process.
        EnumChildWindows(handle, AddressOf EnumChildWindowProc, &H0)
        ' Return the children list when the process is completed.
        Return _listChildren
    End Function

    Public Overloads Function GetChildWindows(ByVal handle As IntPtr, ByVal ChildClass As String) As List(Of ApiWindow)
        ' Set the search
        _childClass = childClass
        Return Me.GetChildWindows(handle)
    End Function

    Private Function EnumWindowProc(ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
        'If GetParent(handle) = 0 AndAlso CBool(IsWindowVisible(handle)) Then
        If CBool(IsWindowVisible(handle)) Then
            ' Get the window title / class name.
            Dim window As ApiWindow = GetWindowIdentification(handle)

            ' Match the class name if searching for a specific window class.
            If _topLevelClass.Length = 0 OrElse window.ClassName.ToLower() = _topLevelClass.ToLower() Then
                _listTopLevel.Add(window)
            End If
        End If

        Return 1
    End Function

    Private Function EnumChildWindowProc(ByVal handle As IntPtr, ByVal lParam As Integer) As Integer
        Dim window As ApiWindow = GetWindowIdentification(handle)

        ' Attempt to match the child class, if one was specified, otherwise
        ' enumerate all the child windows.
        If _childClass.Length = 0 OrElse window.ClassName.ToLower() = _childClass.ToLower() Then
            _listChildren.Add(window)
        End If

        Return 1
    End Function

    Private Function GetWindowIdentification(ByVal handle As IntPtr) As ApiWindow
        Const WM_GETTEXT As Integer = &HD
        Const WM_GETTEXTLENGTH As Integer = &HE

        Dim window As New ApiWindow()
        Dim title As New StringBuilder()

        ' Get the size of the string required to hold the window title.
        Dim size As Integer = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0)

        ' If the return is 0, there is no title.
        If size > 0 Then
            title = New StringBuilder(size + 1)
            SendMessage(handle, WM_GETTEXT, title.Capacity, title)
        End If

        ' Get the class name for the window.
        Dim classBuilder As New StringBuilder(64)
        GetClassName(handle, classBuilder, classBuilder.Capacity)

        ' Set the properties for the ApiWindow object.
        window.ClassName = classBuilder.ToString()
        window.Text = title.ToString()
        window.Handle = handle

        Return window
    End Function

End Class

Open in new window

MessageBoxHiJinx.jpg
VB in a C# forum? For shame ! (sorry, couldn't resist)

I'll try this to see if it works for my scenario ...
Ha!...sorry, I switch between the two languages so much I forgot where I was!

If you need a conversion let me know...  =D
A C# version would help, thanks. I can read VB to some extent, but it would be a substantial effort for me to produce the same thing in C#.

This looks like a lot of machinery but I assume it's all necessary. I'll be testing all my scenarios; the main one is whether a MessageBox generated by a thread not connected to a form will display and hide correctly.

btw, your VB code gives an error: "Error    1    Handles clause requires a WithEvents variable defined in the containing type or one of its base types.    C:\Users\leal\Documents\Visual Studio 2010\Projects\WindowsApplication1\WindowsApplication1\Form1.vb    12    99    WindowsApplication1"
SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Might not be a good idea but I didn't write the app ... Anyway, thanks for the c# version and I'll give it a run in the morning ...
Good luck!
What's with the argument in
     GetTopLevelWindows("#32770")
? Is #32770 a special kind of literal?
Ask Microsoft..."dialogs" were given that classname waaaay back when for some reason.  It isn't really documented but if you Google it you'll find billions of hits where people use it in all kinds of languages and for all kinds of apps.
I ran the program you sent. It hides the form, pauses, flashes it momentarily, then hides it again. Why would it show up briefly like that?
Not sure...so you are targeting your actual intended app...or Notepad?
Notepad, following your instructions and without any change to your program other than adding the necessary button to the form.
Hmmm...I'm running Win 7 Pro 64-bit with VS2010.

I'll post a video of how it looks on my system.
I'm Vista 32 bit home premium with VS 2010 / C# 4.0.

I increased the sleep to 10 and 20 seconds. The flash seems to happen midway. Rather odd ...

I've attached a screencast,
BlearyEye-353986.flv
Well, this is very strange ... I took your solution, added a project called MyVersion, and cloned your project. When I run MyVersion, there's no intermediate blink. Notepad just disappears and then reappears as it should.

All I did in MyVersion was to change the namespace and the name of the form window.
... clicked Submit too soon

When I run your version, I get the intermediate blink. When I run my version, I don't. This can be repeated consistently.
Bizarre...not sure what's going on there!

Here is a screencast of it running on my system with a ten second delay.  I have two Notepad instances running and if you watch the TaskBar the hidden instance doesn't reappear during the ten second interval:
Idle-Mind-354039.flv
This indeed is peculiar. I guess it'll have to register as an anomaly for the time being.

As to the code itself, I've reworked it with prototype app. I launch an instance process that immediately starts a new thread that executes a MessageBox.Show() so there's a detached modal form.

There is also a button where I can create execute a MessageBox.Show() so there's an attached modal form.

The main form and the attached modal appear and disappear as desired. But the detached modal is ignored, and that's the one I'm particularly trying to handle. The problem seems to be in the test
     theInstance.MainWindowHandle.Equals(ParentHandle)
since the detached modal does not have a parent.

Instead of checking for the parent, is there a way to check for modal forms according to their thread? I want to handle all modals associated with a thread, whether attached or not.

On a separate note, see the code snippet below. If I use
     Process[] Instances = { newInstance };
then the value stored is different than the value stored when using
     Process[] Instances = Process.GetProcessesByName("MyVersionInstance");
I need to use the first version since, when multiple instances are launched, they all have the same process name, and I need to be able to distinguish.
Process newInstance;
        private void frmMyVersion_Load(object sender, EventArgs e) {
            newInstance = new Process();
            newInstance.StartInfo.FileName = "MyVersionInstance.exe";
            newInstance.Start();
        }

        private void button1_Click(System.Object sender, System.EventArgs e) {
            //Process[] Instances = { newInstance };

            Process[] Instances = Process.GetProcessesByName("MyVersionInstance");

Open in new window

If you already have a Process instance (from explicitly launching it from within your app?) then sure, just use that to check against.

For the "detached" modals...what is being reported as the "parent" from this line?

    IntPtr ParentHandle = GetParent(aw.Handle);
    Debug.Print("Modal Found");
    Debug.Print("Modal Parent Handle: " & ParentHandle.ToString("X"))    

If you have more than one Process displaying "detached" modals at the same time...do they all report the same "parent"?

I'm not sure who "owns" the detached modals, or if there is a way to trace it back to the launching process.

The parent handle is 0. Not too surprising since it's not associated with a form. My problem, of course, is that there could be several modals with parent 0, each associated with a separate instance. It would nice if I could associate a parent, but I don't see a way to do so.

On the other point, I apparently made an error; I can use Process[] Instances = { newInstance }; instead, so that's ok.
I'm not much of a LOW LEVEL guy...really don't know if there is a way to associate those puppies with a distinct process.  =\

There's no way to change the design of this thing huh?  There is a reason that you're not supposed to do that!  ;)
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
So the "detached" MessageBox is correctly found and hidden if you specify the owner via the first parameter?
Yep. It was just a matter of understanding the interface.