Code Monkey home page Code Monkey logo

Comments (9)

elachlan avatar elachlan commented on July 4, 2024 1

@Olina-Zhang can your team please test this?

from winforms.

merriemcgaw avatar merriemcgaw commented on July 4, 2024 1

This didn't actually work in .NET Framework and we've got the work around. @LeafShi1 can your team to do a brief investigation to see if we can identify the cause. Otherwise, since it didn't work in Framework we may not be able to address it in Core.

from winforms.

snechaev avatar snechaev commented on July 4, 2024 1

The documentation of the MessageBox states then

The form that owns the message box (or the active form) also receives the HelpRequested event.

So, looks like the control flow is following

  • the Control.WmHelp called for the CustomForm to handle WM_HELP
  • if the active MessageBox context found, then help link opened immediately
    // If there's currently a message box open - grab the help info from it.
    HelpInfo? hpi = MessageBox.HelpInfo;
    if (hpi is not null)
    {
    switch (hpi.Option)
    {
    case HelpInfo.HelpFileOption:
    Help.ShowHelp(this, hpi.HelpFilePath);
  • even the help link was already opened, the Control.OnHelpRequested() is called (as stated by docs, so it is expected)
    OnHelpRequested(hevent);
    if (!hevent.Handled)
    {
    DefWndProc(ref m);
  • if the HelpRequested event was not handled by the current control (Form in our example), then the WM_HELP message is passed for further processing (so, bubbles to the parent).
  • the Control.WmHelp() is called for the parent form, and because the MessageBox is still active, the associated HelpInfo is grabbed and the help link opens again.
  • the interesting moment is that just a presence of the event handler is enough to decide that no bubbling is needed.
    HelpEventHandler? handler = (HelpEventHandler?)Events[s_helpRequestedEvent];
    if (handler is not null)
    {
    handler(this, hevent);
    // Mark the event as handled so that the event isn't raised for the
    // control's parent.
    if (hevent is not null)
    {
    hevent.Handled = true;

Conclusion

I can't say for sure if the current behaviour is correct or not. The WM_HELP bubbling makes sense in non-messagebox cases.

It looks like we can force the MW_HELP bubbling to stop for the MessageBox case. This will still be fully compliant with the documentation - the owner form of the MessageBox will get HelpRequested (as the documentation says), but other/parent forms will not. But this will at least make the HelpRequested behavior inconsistent in different scenarios.

The other solution might be to introduce an additional state to store information about "this help link has already been processed". But there is the problem that the user can really click the same help button in the same MessageBox multiple times, so it will be absolutely fine to process the same HelpInfo again.

Workaround

The possible workaround - add the empty HelpRequested handler for the MessageBox owner for (the CustomForm in the test project.

    public CustomForm()
    {
        HelpRequested += (sender, hlpevent) => { };
    }

from winforms.

Olina-Zhang avatar Olina-Zhang commented on July 4, 2024

Tested that project in .NET 9.0 and .NET framework 4.8, they have same result: this website "https://github.com/dotnet/winforms/" is opened twice. But if I put the same MessageBox in a button control on form, it is just opened one time.

from winforms.

Epica3055 avatar Epica3055 commented on July 4, 2024

this is the callstack of MessageBox.Show

image

image

I think we are not able to debug Help button click in Winform repo.

from winforms.

snechaev avatar snechaev commented on July 4, 2024

@Epica3055, the native MessageBox() is used here. As per docs,

When the user clicks the Help button or presses F1, the system sends a WM_HELP message to the owner.

So, you need to track down, where the WM_HELP is processed and why it is not marked as handled after the first help link opened.

And from the brief code analysis, it is most likely that such a help requests will end somewhere in one of the Help.ShowHelp() overloads: .

from winforms.

snechaev avatar snechaev commented on July 4, 2024

I did some other experiments and found out, that the problem is not related to the UITypeEditor at all. The problem can be reproduced by simply opening the multiple forms with Form.ShowDialog(). So, here is a simplified test code.

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

    protected override void OnActivated(EventArgs e)
    {
        base.OnActivated(e);
        using var t = new CustomForm();
        t.ShowDialog(this);
    }
}

public class CustomForm : Form
{
    protected override void OnActivated(EventArgs e)
    {
        base.OnActivated(e);
        MessageBox.Show(
                this,
                "Click Help. You will get 2 browsers opened (instead 1 as expected).",
                "MessageBox",
                MessageBoxButtons.OK,
                MessageBoxIcon.Information,
                MessageBoxDefaultButton.Button1,
                0,
                "https://github.com/dotnet/winforms/");
    }
}

I can also confirm, that we have two calls to the Help.ShowHelp() - the first one from the modal CustomForm (which is the owner of the MessageBox) and the second one from the Form1 (which is the owner/parent of the CustomForm via this Form.ShowDialog()).

from winforms.

Epica3055 avatar Epica3055 commented on July 4, 2024

@snechaev , thanks for you information. 👍 I will look into it.

from winforms.

Epica3055 avatar Epica3055 commented on July 4, 2024

@snechaev thanks you for the analysis ,I add some details here.

Root cause about this issue

When you try to open a MessageBox with a helpFilePath parameter, and the Window that contains the MessageBox has a parent Window, and click Help button like this
image

you will open two web page in browser.

Actually, when there are n ancestor windows, there will be n additional web page in browser.

This happens because:

  1. when you click that Help button, WM_HELP will be emitted.

    protected virtual void WndProc(ref Message m)
    {
    // Inlined code from GetStyle(...) to ensure no perf hit for a method call.
    if ((_controlStyle & ControlStyles.EnableNotifyMessage) == ControlStyles.EnableNotifyMessage)
    {
    // Pass message *by value* to avoid the possibility of the OnNotifyMessage modifying the message.
    OnNotifyMessage(m);
    }
    // If you add any new messages below (or change the message handling code for any messages)
    // please make sure that you also modify AxHost.WndProc to do the right thing and intercept
    // messages which the Ocx would own before passing them onto Control.WndProc.
    switch (m.MsgInternal)
    {
    case PInvoke.WM_CAPTURECHANGED:
    WmCaptureChanged(ref m);
    break;
    case PInvoke.WM_GETOBJECT:
    WmGetObject(ref m);
    break;
    case PInvoke.WM_COMMAND:
    WmCommand(ref m);
    break;
    case PInvoke.WM_CLOSE:
    WmClose(ref m);
    break;
    case PInvoke.WM_CONTEXTMENU:
    WmContextMenu(ref m);
    break;
    case PInvoke.WM_DISPLAYCHANGE:
    WmDisplayChange(ref m);
    break;
    case PInvoke.WM_DRAWITEM:
    if (m.WParamInternal != 0u)
    {
    WmOwnerDraw(ref m);
    }
    break;
    case PInvoke.WM_ERASEBKGND:
    WmEraseBkgnd(ref m);
    break;
    case PInvoke.WM_HELP:
    WmHelp(ref m);

  2. After the event is handled, WM_HELP event will also be sent to ancestor window, see the the document here windows/win32/shell/wm-help
    and code

    private unsafe void WmHelp(ref Message m)
    {
    // If there's currently a message box open - grab the help info from it.
    HelpInfo? hpi = MessageBox.HelpInfo;
    if (hpi is not null)
    {
    switch (hpi.Option)
    {
    case HelpInfo.HelpFileOption:
    Help.ShowHelp(this, hpi.HelpFilePath);
    break;
    case HelpInfo.HelpKeywordOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Keyword);
    break;
    case HelpInfo.HelpNavigatorOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator);
    break;
    case HelpInfo.HelpObjectOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator, hpi.Param);
    break;
    }
    }
    // Note: info.hItemHandle is the handle of the window that sent the help message.
    HELPINFO* info = (HELPINFO*)(nint)m.LParamInternal;
    HelpEventArgs hevent = new(info->MousePos);
    OnHelpRequested(hevent);
    if (!hevent.Handled)
    {
    DefWndProc(ref m);
    }
    }

    So the event will be handled multiple times accordingly.

If you want it to be opened one time when you click it once

  1. see the code here, line 120307 - 12038.We can see that when hevent.Handled is true the event will not be sent to ancestor window.

    private unsafe void WmHelp(ref Message m)
    {
    // If there's currently a message box open - grab the help info from it.
    HelpInfo? hpi = MessageBox.HelpInfo;
    if (hpi is not null)
    {
    switch (hpi.Option)
    {
    case HelpInfo.HelpFileOption:
    Help.ShowHelp(this, hpi.HelpFilePath);
    break;
    case HelpInfo.HelpKeywordOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Keyword);
    break;
    case HelpInfo.HelpNavigatorOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator);
    break;
    case HelpInfo.HelpObjectOption:
    Help.ShowHelp(this, hpi.HelpFilePath, hpi.Navigator, hpi.Param);
    break;
    }
    }
    // Note: info.hItemHandle is the handle of the window that sent the help message.
    HELPINFO* info = (HELPINFO*)(nint)m.LParamInternal;
    HelpEventArgs hevent = new(info->MousePos);
    OnHelpRequested(hevent);
    if (!hevent.Handled)
    {
    DefWndProc(ref m);
    }
    }

  2. See OnHelpRequested method line 8097-8106, We can see that when there is a handler, the Handled will be updated to true so that the event won't be sent to ancestor Window.

    protected virtual void OnHelpRequested(HelpEventArgs hevent)
    {
    HelpEventHandler? handler = (HelpEventHandler?)Events[s_helpRequestedEvent];
    if (handler is not null)
    {
    handler(this, hevent);
    // Mark the event as handled so that the event isn't raised for the
    // control's parent.
    if (hevent is not null)
    {
    hevent.Handled = true;
    }
    }
    if (hevent is not null && !hevent.Handled)
    {
    ParentInternal?.OnHelpRequested(hevent);
    }
    }

workaround

Add a HelpEventHandler to the window that contains the MessageBox with a helpFilePath parameter, even if it is empty.

    public class CustomForm : Form
    {
    ...
          public CustomForm()
          {
              HelpRequested += (sender, hlpevent) => { };
          }
    ...
    }

from winforms.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.