5

I have a Panel where AutoScroll is true. This panel contains many smaller panels that fill all available space like tiles. When there are too many sub-panels to display I get the vertical scroll bar as expected.
Each of these "tiles" have some event handlers tied to them to handle MouseDown / MouseUp / MouseMove as they can be dragged around.

The problem I'm having is that mouse wheel scroll doesn't work on the parent panel as it won't have focus. I can't give it focus because in all probability I'll be scrolling while moving a sub-panel which will have the focus instead, and even then that would require workarounds since panels don't like focus.

I've been trying (and failing) to find a way to propagate only mousewheel events from the children to the parent.
I've read that in Winforms if a control cannot handle a mouse event it will bubble it up to the parent of that control, and then to the parent of that control and so on until it finds a suitable handler.
With this in mind I figured the best solution would be to use WndProc to override all scroll related events on the sub-panel and pass them to the parent while leaving all other events intact but admittedly this isn't my strong suit and I'm lost.

I've tried a few other solutions such as making the sub-panels invisible to all mouse events but as you might have guessed this was bad. I've read about implementing a message filter but didn't understand it.

Here's the code that will give you a very basic example of the panel and its children:

private void Form1_Load(object sender, EventArgs e)
{
    Height = 600;
    Width = 300;

    Color[] colors = new Color[]{ Color.PowderBlue, Color.PeachPuff };

    Panel panel = new Panel()
    {
        Height = this.ClientSize.Height - 20,
        Width = 200,
        Top = 10,
        Left = 10,
        BackColor = Color.White,
        BorderStyle = BorderStyle.FixedSingle,
        AutoScroll = true
    };

    for (int i = 0; i < 10; i++)
    {
        Panel subPanel = new Panel()
        {
            Name = @"SubPanel " + i.ToString(),
            Height = 100,
            Width = panel.Width - System.Windows.Forms.SystemInformation.VerticalScrollBarWidth - 2,
            BackColor = colors[i % 2],
            Top = i * 100
        };
        subPanel.MouseClick += subPanel_MouseClick;
        panel.Controls.Add(subPanel);
    }

    Controls.Add(panel);
}

void subPanel_MouseClick(object sender, MouseEventArgs e)
{
    Panel panel = sender as Panel;
    Text = panel.Name;
}

Here's my attempt at overriding WndProc in a custom panel:

class NoScrollPanel : Panel
{
    private const int WM_HSCROLL = 0x114;
    private const int WM_VSCROLL = 0x115;
    private const int MOUSEWHEEL = 0x020A;
    private const int KEYDOWN = 0x0100;

    protected override void WndProc(ref Message m)
    {
        if ((m.HWnd == Handle) && (m.Msg == MOUSEWHEEL || m.Msg == WM_VSCROLL || (m.Msg == KEYDOWN && (m.WParam == (IntPtr)40 || m.WParam == (IntPtr)35))))
        {
            PostMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
        }
        else
        {
            base.WndProc(ref m);
        }
    }

    [DllImport("User32.dll")]
    private static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}

Any help or alternative approaches are most welcome. Thanks!

Equalsk
  • 7,954
  • 2
  • 41
  • 67
  • 1
    Windows sends the message to the window with the focus. That is not going to be the panel and very unlikely to be the form, certainly not that NoScrollPanel. Probably some kind of button or toolstrip you have on the form. You really *do* need [a panel that can take the focus](http://stackoverflow.com/a/3562449/17034). – Hans Passant Oct 14 '15 at 16:27
  • Thanks Hans. Now I feel a little stupid as I saw that thread and ignorantly read right past your answer. Oops! I'll try it tomorrow when I'm able as I'm sure it'll work perfectly. Much appreciated. – Equalsk Oct 14 '15 at 16:32

4 Answers4

6

At my side @a-clymer's solution does not work, perhaps different environment. Currently there is no direct, clear answer for my problem so I try to combine several thoughts of other professionals and succeed.

In my current project, I have a few input controls contained in a Panel. I managed to make the mouse wheel scroll the panel instead of the ComboBox items by creating a child class of ComboBox and override its WndProc:

public class ComboBoxWithParentMouseWheel : ComboBox
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

    const int WM_MOUSEWHEEL = 0x020A;

    //thanks to a-clymer's solution
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MOUSEWHEEL)
        {
            //directly send the message to parent without processing it
            //according to https://stackoverflow.com/a/19618100
            SendMessage(this.Parent.Handle, m.Msg, m.WParam, m.LParam);
            m.Result = IntPtr.Zero;
        }else base.WndProc(ref m);
    }
}
AdrianHou
  • 201
  • 2
  • 9
2

All credit goes to Hans Passant (again), taken from the thread he suggested: https://stackoverflow.com/a/3562449/17034

Allowing the the containing panel to take focus worked fine. For the demo code above the class needs no changes, just use it for only the containing panel. I had to make some tweaks to my project to call focus when necessary, but it was far from rocket science.

Thanks again.

Community
  • 1
  • 1
Equalsk
  • 7,954
  • 2
  • 41
  • 67
1

I could not get any of these solutions to work. I am overriding the WndProc fxn just like you. Finally found the solution! I noticed that if the container holding my TextBox was in focus, then the scroll worked, just not when the TextBox was in focus.

I didn't need to change the transparency of the event, I needed to send the event to a different Control Handle! (It seems so simple now, but I've trying to figure this out for days!)

internal class myTextBox : TextBox
{
    const int WM_MOUSEWHEEL = 0x020A;

    protected override void WndProc(ref Message m)
    {

        if (m.Msg == WM_MOUSEWHEEL)
            m.HWnd = this.Parent.Handle; // Change the Handle of the message

        base.WndProc(ref m);
    }
}

I haven't seen any mention of this method in all my Google searches, if there is some reason NOT to do this I hope someone will reply.

A.Clymer
  • 472
  • 3
  • 9
1

An improved version of @a-clymer version that works better.

const int WM_MOUSEWHEEL = 0x020A;

if (m.Msg == WM_MOUSEWHEEL)
{
    // find the first scrollable parent control
    Control p = this;
    do
    {
        p = p.Parent;
    } while (p != null && !(p is ScrollableControl));

    // rewrite the destination handle of the message
    if (p != null)
        m.HWnd = p.Handle;
}
DipSwitch
  • 5,470
  • 2
  • 20
  • 24