0

I have a WinForms app written for the classical .NET Framework (namely v4.7.2). I need to process the mouse back and forward buttons in one of the forms. The problem is that the form is fully covered by other controls like SplitContainer and Panels, and I could not find a way to intercept the required buttons using standard native .NET techniques.

For example, a solution with overriding WndProc

private const int WM_XBUTTONDOWN = 0x020B;
private const int MK_XBUTTON1 = 0x0020;
private const int MK_XBUTTON2 = 0x0040;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_XBUTTONDOWN)
    {
        int lowWord = (m.WParam.ToInt32() << 16) >> 16;
        switch (lowWord)
        {
            case MK_XBUTTON1:
                // navigate backward
                break;
            case MK_XBUTTON2:
                // navigate forward
                break;
        }
    }
    base.WndProc(ref m);
}

works only if the mouse pointer is over the part of the client area not covered by any control.

Is there a way to make all form controls 'transparent' for clicks of the mouse back/forward buttons using a .NET native technique and process those x-buttons in WndProc or a similar form method? If not, should I use a low-level mouse hook based on WinAPI to implement what I need? All solutions in C# or VB.NET are welcome.

TecMan
  • 2,743
  • 2
  • 30
  • 64
  • 1
    This is usually done implementing [IMessageFilter](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.imessagefilter). The `PreFilterMessage()` method is called whenever a message is generated in the UI, before it's dispatched to the destination (any Control). You can implement the Interface directly on the Form Container. -- Then it depends on how the Mouse Buttons are mapped, so check other message besides `WM_XBUTTONDOWN` & Co. – Jimi May 25 '21 at 12:48
  • @Jimi, I am currently working on this :). Found a sample on SO [here](https://stackoverflow.com/questions/986529/how-to-detect-if-the-mouse-is-inside-the-whole-form-and-child-controls). If it works, I'll publish my solution. – TecMan May 25 '21 at 12:51
  • Sure, that will work. As mentioned, you can implement the Interface on the Form directly, there's no need for a dedicated class (unless you want to build a general-purpose filter). Otherwise, just `public partial class SomeForm : Form, IMessageFilter { }`. If you implement the filter using a dedicate class, then make the class raise a public event that any other class can subscribe to. You can use custom EventArgs, as shown here: [How can I make a Control lose focus clicking anywhere outside of it?](https://stackoverflow.com/a/65712931/7444103) – Jimi May 25 '21 at 12:54
  • @Jimi, any good sample of this? I.e. implementing IMessageFilter directly in the same form? What are benefits compared to a separate class implementing the same interface? – TecMan May 25 '21 at 12:56
  • Besides the one you linked, see the one I posted (to implement IMessageFilter directly on a Form). You can find **many** working examples here. -- I wouldn't talk about real *benefits*: if you need this just for a single Form (still a class as any other), then you restrict its use to this class alone. Otherwise, use a general-purpose handler. If you implement it locally, it's clearly meant to be used locally. You have direct access to any other class / object in the Form itself (if required / needed / preferable in that context). – Jimi May 25 '21 at 12:59

1 Answers1

0

A solution based on IMessageFilter does the work:

public partial class Form1 : Form, IMessageFilter
{
    public Form1()
    {
        Application.AddMessageFilter(this);
        this.FormClosed += (s, e) => Application.RemoveMessageFilter(this);

        InitializeComponent();
    }

    private const int WM_XBUTTONDOWN = 0x020B;
    private const int MK_XBUTTON1 = 0x0020;
    private const int MK_XBUTTON2 = 0x0040;

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == WM_XBUTTONDOWN)
        {
            int lowWord = (m.WParam.ToInt32() << 16) >> 16;
            switch (lowWord)
            {
                case MK_XBUTTON1:
                    // navigate backward
                    BackButton_Click(null, null);
                    break;
                case MK_XBUTTON2:
                    // navigate forward
                    ForwardButton_Click(null, null);
                    break;
            }
        }
        return false; // dispatch further
    }
}
TecMan
  • 2,743
  • 2
  • 30
  • 64