使MouseWheel事件从子事件传递到父事件

本文关键字:事件 MouseWheel | 更新日期: 2023-09-27 18:17:37

我有一个Panel,其中AutoScroll为真。这个面板包含许多较小的面板,像瓷砖一样填充所有可用的空间。当有太多子面板要显示时,我得到了垂直滚动条。
每个"tile"都有一些事件处理程序绑定到它们,以处理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);
}

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

使MouseWheel事件从子事件传递到父事件

在我身边@a-clymer的解决方案不起作用,可能是不同的环境。目前对于我的问题没有直接明确的答案,所以我尝试结合其他专业人士的一些想法并取得成功。

在我当前的项目中,我有几个输入控件包含在一个面板中。我设法使鼠标滚轮滚动面板,而不是ComboBox项目通过创建一个子类的ComboBox和覆盖它的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);
    }
}

所有功劳归于Hans Passant(再次),取自他建议的帖子:https://stackoverflow.com/a/3562449/17034

允许包含面板的焦点工作良好。对于上面的演示代码,该类不需要更改,只需将其仅用于包含面板。我不得不对我的项目做一些调整,以便在必要时集中注意力,但这远非火箭科学。

再次感谢。

我无法让这些解决方案工作。我像你一样重写了windproc 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);
    }
}

我还没有看到任何提及这个方法在我所有的谷歌搜索,如果有一些原因不这样做,我希望有人会回复。

@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;
}