views:

197

answers:

5

I have a form that is showing a MessageBox using MessageBox.Show, and trying to receive events from the Help button on the MessageBox so I can execute my own code. The Microsoft documentation shows how to do this; however, using what is suggested does not work. Here's a shortened version of my code:

    Private Function MethodName() As Boolean

      AddHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
      Select Case MessageBox.Show("Text", "Title", MessageButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, 0, True)
        Case MsgBoxResult.Yes
          ' Do stuff
        Case MsgBoxResult.No
          ' Do stuff
        Case MsgBoxResult.Cancel
          RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
          Return False
      End Select
      RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested

    End Function

Private Sub MsgBoxHelpRequested(ByVal sender As Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs)
  ' Breakpoint that never gets hit
  ' More code
End Sub

I've been searching for a solution to this problem, but the best I've found is this question: http://stackoverflow.com/questions/2407880/how-to-detect-help-button-press-in-windows-forms-messagebox that refers me back to the same Microsoft code that doesn't seem to be working.

Can anybody help me with this?

Thank you.

+1  A: 

Pass Me as the first parameter to MessageBox.Show.

Add the handler to Form.ActiveForm instead of Me.

SLaks
Not sure that's needed. My sample works without explicity passing `this`.
MusiGenesis
@MusiGenesis: Only if `this` is the active form.
SLaks
Actually, since you are explicity adding the handler, you do not need to pass the Owner parameter.
AMissico
@AMissico: I'm talking about the `MessageBox.Show` call. That has nothing to do with the handler.
SLaks
@SLaks. Oops. You are right, now that I look at the code again. It works with and without, so I jumped to the conclusion it was not required because the handler was explicitly added.
AMissico
@SLaks: if something other than the active form is showing message boxes, this app might have other problems.
MusiGenesis
A: 

This is C#, and I'll auto-translate it to VB in a second.

Put this code in your form's Load event:

this.HelpRequested += new HelpEventHandler(Form1_HelpRequested);

Then add this code to your form:

void Form1_HelpRequested(object sender, HelpEventArgs hlpevent)
{
    hlpevent.Handled = true; // this will prevent windows from also opening
        // any associated help file
    // do whatever you're gonna do here
}

Then call MessageBox like this:

MessageBox.Show("message", "caption", MessageBoxButtons.OK, 
    MessageBoxIcon.Asterisk,
    MessageBoxDefaultButton.Button1, 0, true);

This will show a message box with OK and HELP buttons. When HELP is clicked, Form1_HelpRequested will be called.

VB.Net version:

Put this code in your form's Load event:

AddHandler Me.HelpRequested, AddressOf Form1_HelpRequested 

Then add this code to your form:

Private Sub Form1_HelpRequested(ByVal sender As Object, ByVal hlpevent As 
    HelpEventArgs) 
    ' this will prevent windows from also opening 
    ' any associated help file:
    hlpevent.Handled = True 
    ' do whatever you're gonna do here 
End Sub

Then call MessageBox like this:

MessageBox.Show("message", "caption", MessageBoxButtons.OK, 
    MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, 0, _ 
    True)

This will show a message box with OK and HELP buttons. When HELP is clicked, Form1_HelpRequested will be called.

MusiGenesis
A: 

Okay, I'm not sure how to respond to these Answers individually, so I'll respond to them here.

@SLaks: Already thought of that, but there is no Show method that takes a window as a parameter that allows you to handle the Help event yourself. All the options that take a window as a parameter either require you to specify a Help file and other stuff referencing that Help file, or don't allow you to do anything with Help at all.

@MusiGenesis: The only difference between your code and my code that I can see is that you put AddHandler in Load instead of right before making this call. I have already tried specifying the handler function as a handler in the function declaration, but that didn't work; I tried moving the AddHandler to the Load function, and that didn't work either.

Incidentally, is there any way to verify that my handler has been added properly?

I saw after I translated my code to VB that it was virtually the same as your code. All I know is that my C# version works fine (I've run and tested it). Try creating a brand-new application with only the code in the VB version (use Form1_HelpRequested to set a label's Text property or something) and make sure that works as expected. Then you can start digging into your code to find out what's different about it.
MusiGenesis
Add the handler to `Form.ActiveForm` instead of `Me`.
SLaks
Okay, so I took the code from my app, copied and pasted it into a new project, stripped out extra variables, and ran it. It worked perfectly. So there somehow has to be something outside this code that's causing this problem. I thought maybe the event is already handled - but Find In Files turns up no other references to HelpRequested. What else in the world could possibly be interfering with this event being raised or calling my handle?Also, I can't see any way to ask AMissico to clarify what she said about calling MethodName from constructors, so I'll add it here. Very confusing site.
@SLaks: Form.ActiveForm is Nothing and crashes when the accompanying other code is called during start-up.
And also when called during normal execution.
@AMissico: Firstly, what do you mean, add the handler through the designer? I don't see any way to do that. If you mean to explicitly define the sub as a handler in the definition, I've tried that and it doesn't work. (Oh, and the reason the MSDN example does it this way is so that they can remove the handler in case another MessageBox needs to use it - sloppy of MS, if you ask me).
@AMissico: I did try putting code in the test project in New() and I saw the same symptoms; except when I had it called from both the constructor and a Button click, it worked fine on the Button click - in the real project, it doesn't even work after the form is already showing. And I did double-check that Me is valid before I add the handler: it looks good.
@trevortni: Select the form in the designer. Open / Goto the Property Grid. Change the grid to Events. Look for HelpRequested and double-click in combo-box immediately to the right. This will generate the `Private Sub Form1_HelpRequested(ByVal sender As System.Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs) Handles MyBase.HelpRequested End Sub`. Notice the `Handles MyBase.HelpRequested`.
AMissico
@AMissico: Thanks, I never knew you could do that. It still doesn't work, though. :(
@trevortni: "slopp of MS" Well, they are just trying to maximize the value of the topic which is for all languages, not just VB. Better examples are in the Visual Basic Concept and other Visual Basic documentation.
AMissico
@trevortni: Try a different handler. Make sure the handler has the signature `Sub MethodName(ByVal sender As System.Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs)`.
AMissico
@trevortni: Did you use the method that accepts the owner paraemter as SLaks suggested? Did it work or not?
AMissico
@AMissico: As I mentioned earlier, there is no method accepting the owner parameter that allows you to handle the event yourself. I tried that long before posting here.
@trevortni: Owner assigns the message box's parent handle property. It is not related to handling the event. SLaks probably suggested it to make sure the message box raises the event for the property form (owner).
AMissico
@AMissico: And the sloppiness I was referring to is the fact that you have to use a global event for the form, rather than allowing you to specify a handler or something else that allows any degree of flexibility. But then I remember how much flexibility is mysteriously missing in the weirdest places in so much MS stuff, and it all begins to make sense.
@AMissico: There is no compatible Show() method for what I'm trying to do that takes the Owner parameter. If you've seen one, let me know and I'll gladly use that, but they all seem to either not show a help button, or the MessageBox takes care of the help itself and doesn't raise the event. I understand what he was saying, but MS DOES NOT LET ME DO IT THAT WAY.
@trevortni: "Incidentally, is there any way to verify that my handler has been added properly?" Yes. Break after adding the handler then inspect the form's Event property. You will have to dig for it under handler.Method for the head and each next node.
AMissico
@trevortni: I understand what you are saying about Show(), since you are specifying "show-help-button" there is no Show method that provides for both owner and show-help-button. Okay, so try using VB's MsgBox. `MsgBox(MsgBoxStyle.YesNoCancel Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.Question Or MsgBoxStyle.MsgBoxHelp)` and see if you have same problem.
AMissico
@AMissico: I see an Events property, but no Event property, and no way to iterate through the List. And no evidence of any handler.Method. I tried using the name of the event for the Object it asks for, but it just says that the event is not a member of the form.
@trevortni: "you have to use a global event for the form, rather than allowing you to specify a handler or something else that allows any degree of flexibility." You don't have to use a global event. Any method call on any object works. You just have to add the handler like you are doing. Your problem is you have something going on with the form you are using.
AMissico
@trevortni: Ah, idea from last comment. Create a module. Move your handler to this module. Should work.
AMissico
Oops, should be `MsgBox("Text", MsgBoxStyle.YesNoCancel Or MsgBoxStyle.DefaultButton2 Or MsgBoxStyle.Question Or MsgBoxStyle.MsgBoxHelp, "Caption")`.
AMissico
@trevortni: Yes, it is Events property. Typo on my part. You need to turn on "Enable property evaluation and other implicit function calls" under Tools/Options/Debugging/General. Also, check "Call string conversion...". This will allow you to see all kinds of good stuff about objects. (It is off by default because it can be overwhelming.)
AMissico
@AMissico: Yeah, I got the right syntax for that - from the MsgBox I had previously been using that I had commented out because I didn't realize that MsgBox could do the same thing. :-) Sigh, it's been a long, long day. Anyway, is there any specific Module I should try out? I'm getting to where I barely trust my own sanity anymore.
@AMissico: "Enable property evaluation and other implicit function calls" - OK< that's an interesting sounding option. That wouldn't possibly let me use loops and With's in the Immediate window by any chance, would it? Or am I just getting my hopes up?
@trevortni: No specific module. What I do is add a quick module to the bottom of the form's source code. All you are doing adding Module xxx : <code for your handler> : End Module. (Where : are new lines.
AMissico
@AMissico - Eh, guess not. It was already turned on....
@tervortni: "That wouldn't possibly let me use loops and With's in the Immediate window by any chance, would it?" No. Do what I do. Create a simple module, like the one I mentioned about and write methods that you can call from the Immediate window. Works great. Also, you can use the object test bench and even declare variable in Immediate window, but you cannot use the Dim or As... keywords, so sName = "trevortni" works.
AMissico
@AMissico: If I understand this correctly, I'm doing this so that the event and handler will all exist inside this Module, to separate it from everything else? So I'll be putting this function in here?
@trevortni: "It was already turned on." Hm, you should have seen the form's Events property and its member variables. Make sure "Show all members for non-user objects in variables windows (Visual Basic only)" is checked. Under Tools/Options/Debuggion/General -> "Enable Just My Code (Managed only)"
AMissico
@AMissico: Oh dear, I've got to go home in fifteen minutes. I'll have to bookmark this page and check back tomorrow, probably.
@trevortni: You are just putting the handler(function/method) in the module. This ensures the pointer to the exists when the HelpRequested event fires. (I suspect the form is being destroyed for some reason. Moving the handler will verify this.)
AMissico
@trevortni: Later, and don't stress over this. It is not worth it. You will get it easily tomorrow once your brain has a chance to absorb today and learn from it.
AMissico
@AMissico: Checking those checkboxes didn't change anything. I still just see 9 methods and the Items property under Events.
@AMissico: I guess I must not be clear on what a Module is, then. I was thinking it sounded like another context within which the handler would exist, from which I would be calling the MsgBox or Show function. Is the HelpRequested event still within the form itself?
@trevortni: When you expand Me.Events, do you see three members: head, Item, and parent?
AMissico
@trevortni: I will update my answer so you can see the code.
AMissico
@AMissico: Unless you're directing me to look at something other than the IntelliSense popup, no head, Item, or parent members.
@AMissico: "I will update my answer so you can see the code." - Thank you.
@trevortni: Updated code. You need to look in the "Auto" or "Locals" debug windows.
AMissico
@trevortni: I have to go. Later.
AMissico
@AMissico: OK, so I'm finally able to see the Event list, and was able to use Me.eventhelprequested as an index to the correct event (I couldn't see any way to identify which event you were in when manually iterating through the list); however, when I go through them, all the legible information I can see that claims to identify the method talks about the Class/Module that the method is in, and nothing says the name of the method. Is this normal, with the actual method only referenced through methodPtr's and the like, or is there something else going on? If so, how do I identify methods?
@AMissico: Also, putting it in a Module didn't help. Is there any possible chance that the event could just be not being called or something? And if so, is there any way I can breakpoint an event itself somehow so I can tell?
@trevortni: "Module didn't help:" I was sure it would work around the problem.
AMissico
@trevortni: "Event could just be not being called:" Yes. It doesn't get called if the handler is no longer associated with the Event. This can happen if the handler method belongs to an object that is null or disposed. That is why I ask you to move the handler to a module. The module code is static and always available.
AMissico
@trevortni: "any way I can breakpoint an event itself:" No. Download ftp://missico.net/EventSpy.zip. This is a class you can add to the project to help "spy" on control events. I will **not** help you will this. I only provide it, because it is no longer available for download at original location.
AMissico
@trevortni: The only other issue I know of relates to threading. **Is this message box being called on a background thread or other thread?** If so, it will not work reliably or work at all.
AMissico
@trevortni: Break after adding the handler then inspect the form's Event property. You will have to dig for it under `handler.Method` for the `head` and each `next` node. I believe `MethodName` should be your handler.
AMissico
@AMissico: There doesn't appear to be any mention of threads in the project besides comments, sleeps, and Designer-added code.
@trevortni: Put the following code immediately before displaying the message-box: `If Me.InvokeRequired Then : Stop : End If` and run the code. If the debugger stops then you are trying to run the message-box from a thread other than the main thread. (You can replace the : with newlines if you want.)
AMissico
@AMissico: When you say the handler.Method, are you referring to the property on each node? I have been able to find the name of the Module in there, but no mention of the name of the actual method.
@AMissico: Me.InvokeRequired remains False.
@trevortni: "Me.InvokeRequired remains False." At this point I am out of ideas. We tried all the typical "gotchas". Following are my suggestions.
AMissico
1) Copy your existing application and strip it down (if possible) until you get it working.
AMissico
@trevortni: "but no mention of the name of the actual method," it is there. As I mentioned, you will have to dig around. Create a test project that works and inspect the Events property so know what to look for. Then check your application. If it is not there, then the handler is not being added and you have to figure out why.
AMissico
@AMissico: Okay, I added a MessageBox at another random place on the Form, and every single handler I've ever added for this event got called. I'm going to start looking through the Call Stack for this spot to see if there's anything I might have missed.
2) I always say, "if you are right you are right and something else is the problem." Meaning do not focus on what you know is right and try to force the program to do what you want. You control the code, so make your application break or do other things in order to troubleshoot. For instance, add two handlers. Does one work and the other fail, and so on. What happens if you display a form instead of a message-box and raise an event from that message-box-like-form. Can you catch that event? If not, then the problem is not the message-box. You get the idea.
AMissico
@AMissico: The Stack Trace does show going through a .dll that raises an event that brings it back to the Main Form that originally called the .dll.
@trevortni: Excellent. You applied #2 and took control of your code. At this point, I think you got a handle on it. I am done for the day. Good Luck. Let us know what the problem was.
AMissico
If I move this code back to just before where the code heads out to the .dll, it works. Could something be going on behind the scenes due to the .dll call that could be causing this?
OH. MY. GOODNESS. So I started checking the source code for the MessageBox.Show in Reflector. As it turns out, if you don't specify the Owner, as you aren't allowed to do when you want to handle HelpRequested yourself, it uses UnsafeNativeMethods.GetActiveWindow() to determine who to send the HelpRequested to. The moment I saw this, I knew what was going on. The application I'm working in has a splash screen that shows the status to the user that is shown in the .dll. THAT is the ActiveWindow. When I forced it down and called Activate on my Form just before this code, it worked.
I think I'll go ahead and add an answer that just says that, for anyone else that stumbles across this. I don't think I'll be able to Mark it as the Answer, though, since I didn't get an account here until after I had already posted the original Question.
A: 

If you call MethodName for the form's constructor (new) or in the form's Load event, you sample code will not work. That may be why it is not working for you.

The constructor is Sub New. You have to be careful of some initializations in the constructor or the form's Load event. The reason being is the handle to the control, which includes the form, is not created yet. If you test project works, then compare the test project to what you have. Consider how you are calling the methods and where. The most likely reason your application is not working, is due to the handler is not added because form is not created. (It is created when the form becomes visible. You can try adding a form.CreateControl just before adding the handler.)

Moreover, try adding the handler to the form through the designer. This will guarantee handler is assigned correctly. (The MSDN example does everything manually, and is not a good example. The VB example should show you how to do this the easy VB way, instead of a more advanced manual way.)

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Designer-Generated "

    'Form overrides dispose to clean up the component list. 
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub
    Friend WithEvents Button2 As System.Windows.Forms.Button

    'Required by the Windows Form Designer 
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer 
    'It can be modified using the Windows Form Designer.   
    'Do not modify it using the code editor. 
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
        Me.Button1 = New System.Windows.Forms.Button
        Me.Button2 = New System.Windows.Forms.Button
        Me.SuspendLayout()
        '
        'Button1
        '
        Me.Button1.Location = New System.Drawing.Point(0, 0)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(75, 23)
        Me.Button1.TabIndex = 0
        Me.Button1.Text = "Button1"
        Me.Button1.UseVisualStyleBackColor = True
        '
        'Button2
        '
        Me.Button2.Location = New System.Drawing.Point(0, 29)
        Me.Button2.Name = "Button2"
        Me.Button2.Size = New System.Drawing.Size(75, 23)
        Me.Button2.TabIndex = 1
        Me.Button2.Text = "Button2"
        Me.Button2.UseVisualStyleBackColor = True
        '
        'Form1
        '
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.ClientSize = New System.Drawing.Size(292, 266)
        Me.Controls.Add(Me.Button2)
        Me.Controls.Add(Me.Button1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub
    Friend WithEvents Button1 As System.Windows.Forms.Button

#End Region

    Public Sub New()
        ' This call is required by the Windows Form Designer. 
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call. 
        MethodName() 'will not work here 
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        MethodName() 'will not work here 
        'Me.CreateControl()
        MethodName2() 'still will not work 
    End Sub

    Private Function MethodName() As Boolean
        AddHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
        Select Case MessageBox.Show("Text", "Title", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, 0, True)
            Case Windows.Forms.DialogResult.Yes
                ' Do stuff  
            Case Windows.Forms.DialogResult.No
                ' Do stuff  
            Case Windows.Forms.DialogResult.Cancel
                RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
                Return False
        End Select
        RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
    End Function

    Private Function MethodName2() As Boolean
        AddHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested2
        Select Case MessageBox.Show("Text", "Title", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, 0, True)
            Case Windows.Forms.DialogResult.Yes
                ' Do stuff  
            Case Windows.Forms.DialogResult.No
                ' Do stuff  
            Case Windows.Forms.DialogResult.Cancel
                RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested2
                Return False
        End Select
        RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested2
    End Function


    ''' <summary>
    '''  AddHandler Me.HelpRequested, AddressOf Module1.MsgBoxHelpRequested3
    ''' </summary>
    Private Function MethodName3() As Boolean
        AddHandler Me.HelpRequested, AddressOf Module1.MsgBoxHelpRequested3
        Select Case MessageBox.Show("Text", "Title", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3, 0, True)
            Case Windows.Forms.DialogResult.Yes
                ' Do stuff  
            Case Windows.Forms.DialogResult.No
                ' Do stuff  
            Case Windows.Forms.DialogResult.Cancel
                RemoveHandler Me.HelpRequested, AddressOf Module1.MsgBoxHelpRequested3
                Return False
        End Select
        RemoveHandler Me.HelpRequested, AddressOf Module1.MsgBoxHelpRequested3
    End Function

    Private Sub MsgBoxHelpRequested(ByVal sender As Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs)
        ' Breakpoint that never gets hit  
        MsgBox("Here I am to save the day!")
    End Sub

    Private Sub MsgBoxHelpRequested2(ByVal sender As Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs)
        ' Breakpoint that never gets hit  
        MsgBox("Shoot, still now working.")
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        MethodName() 'always works because all handles are created 
    End Sub

    Private Sub Form1_HelpRequested(ByVal sender As System.Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs) Handles MyBase.HelpRequested
        MsgBox("Always works! No need to add a handler because of Handles MyBase.HelpRequested.")
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        MethodName3()
    End Sub

End Class

Module Module1

    Public Sub MsgBoxHelpRequested3(ByVal sender As Object, ByVal hlpevent As System.Windows.Forms.HelpEventArgs)
        MsgBox("Being handled in a module.")
    End Sub

End Module
AMissico
A: 

As it turns out, there was another window Active than the Form calling the MessageBox. Since no version of MessageBox.Show allows you to both handle the HelpRequested event AND specify the Owner, MessageBox was looking to the ActiveForm for the recipient of the Event, rather than sending it to my Form. Making the following change finally got it working:

Private Function MethodName() As Boolean

  Me.Activate() ' <-------------------!!!!!!!!!

  AddHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
  Select Case MessageBox.Show("Text", "Title", MessageButtons.YesNoCancel, _
            MessageBoxIcon.Question, MessageBoxDefaultButton.Button2, 0, True)
    Case MsgBoxResult.Yes
      ' Do stuff
    Case MsgBoxResult.No
      ' Do stuff
    Case MsgBoxResult.Cancel
      RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
      Return False
  End Select
  RemoveHandler Me.HelpRequested, AddressOf Me.MsgBoxHelpRequested
End Function


Private Sub MsgBoxHelpRequested(ByVal sender As Object, _
            ByVal hlpevent As System.Windows.Forms.HelpEventArgs)
  ' Breakpoint that **finally** gets hit
  ' More code
End Sub

There are still a number of things that I will be fixing with this code relating to other things, but it sure is nice to finally have this figured out.

Thank you to everybody that helped.

Sorry to rain on your parade, but Me.Activate() has *no* guarantee to actually activate your form. Review the docs for SetForegroundWindow to see the rules.
Hans Passant