使鼠标滚轮事件从子元素传递到父元素

5
我有一个`Panel`,其中`AutoScroll`为true。该面板包含许多填充所有可用空间的较小面板,如平铺。当子面板过多无法显示时,我会得到垂直滚动条,这是预期的。
每个“瓷砖”都有一些事件处理程序与它们绑定,以处理MouseDown / MouseUp / MouseMove,因为它们可以拖动。
我遇到的问题是,鼠标滚轮在父面板上不起作用,因为它没有焦点。我无法给它焦点,因为很可能我正在移动子面板时进行滚动,子面板将代替它具有焦点,即使这样也需要解决问题,因为面板不喜欢焦点。
我一直在尝试(并失败)找到一种方法,仅从子级传播鼠标滚轮事件到父级。
我读到,在Winforms中,如果控件无法处理鼠标事件,它将将其冒泡到该控件的父控件,然后到该控件的父控件,依此类推,直到找到合适的处理程序。
考虑到这一点,我想最好的解决方案是使用`WndProc`覆盖子面板上的所有滚动相关事件,并将它们传递到父级,同时保留所有其他事件,但是我承认这不是我的强项,我感到很迷茫。
我尝试过其他一些解决方案,例如使子面板对所有鼠标事件都不可见,但如你所料,这很糟糕。我读到了实现消息过滤器的方法,但是没有理解它。
这是代码,可以为您提供面板及其子元素的基本示例:
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;
}

这里是我尝试在自定义面板中重写WndProc的结果:
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);
}

任何帮助或替代方法都非常欢迎。谢谢!

1
Windows会将消息发送到具有焦点的窗口。 这不可能是面板,也不太可能是表格,当然也不是NoScrollPanel。 可能是您在表单上放置的某种按钮或工具栏。 您确实需要一个可以接受焦点的面板。 - Hans Passant
谢谢Hans。现在我感觉有点傻,因为我看到了那个帖子,却无知地忽略了你的答案。糟糕!我会在明天尝试它,因为我相信它会完美地工作。非常感谢。 - Equalsk
4个回答

6

在我的环境下,@a-clymer的解决方案无法生效。目前没有直接、清晰的答案来解决我的问题,所以我试图结合其他专业人士的想法并成功地解决了它。

在我的当前项目中,我有一些包含在面板中的输入控件。通过创建一个继承自ComboBox的子类,并覆盖它的WndProc方法,我成功地使得鼠标滚轮将面板滚动而不是ComboBox中的项目。

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://dev59.com/f2Ik5IYBdhLWcg3wKLSd#19618100
            SendMessage(this.Parent.Handle, m.Msg, m.WParam, m.LParam);
            m.Result = IntPtr.Zero;
        }else base.WndProc(ref m);
    }
}

2
所有的功劳再次归功于Hans Passant,以下内容取自他建议的帖子:https://dev59.com/xnA65IYBdhLWcg3w7DP8#3562449 允许包含面板获得焦点是可以的。对于上面的示例代码,该类不需要进行任何更改,只需将其用于包含面板即可。我必须对我的项目进行一些调整以在必要时调用焦点,但它远非火箭科学。
再次感谢。

1
我无法让这些解决方案起作用。就像您一样,我正在覆盖WndProc fxn。最终找到了解决方案!我注意到,如果包含文本框的容器处于焦点状态,则滚动工作正常,但是当文本框处于焦点状态时则不行。
我不需要更改事件的透明度,而是需要将事件发送到另一个控件句柄!(现在看来似乎很简单,但我已经试图解决这个问题几天了!)
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);
    }
}

我在所有的谷歌搜索中都没有看到关于这种方法的提及,如果有任何不应该使用这种方法的原因,希望有人能回复。


1
一个改进版的@a-clymer版本,运行更加顺畅。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接