【问题标题】:Make MouseWheel event pass from child to parent使 MouseWheel 事件从子传递到父
【发布时间】:2016-01-12 19:24:23
【问题描述】:

我有一个Panel,其中AutoScroll 是真的。此面板包含许多较小的面板,它们填充所有可用空间,如瓷砖。当要显示的子面板过多时,我会按预期获得垂直滚动条。
这些“图块”中的每一个都有一些与它们相关联的事件处理程序来处理 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);
}

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

【问题讨论】:

  • Windows 将消息发送到具有焦点的窗口。那不会是面板,也不太可能是表单,当然不是那个 NoScrollPanel。可能是表单上的某种按钮或工具条。你真的确实需要a panel that can take the focus
  • 谢谢汉斯。现在,当我看到该线程并且无知地阅读您的答案时,我感到有些愚蠢。哎呀!我会在明天有能力的时候尝试一下,因为我相信它会完美地工作。非常感谢。

标签: c# winforms


【解决方案1】:

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

允许包含面板获得焦点效果很好。对于类上面的演示代码不需要更改,只需将它用于包含面板。我不得不对我的项目进行一些调整,以便在必要时调用焦点,但这远非火箭科学。

再次感谢。

【讨论】:

    【解决方案2】:

    我无法让这些解决方案中的任何一个起作用。我像你一样覆盖 WndProc fxn。终于找到解决办法了!我注意到,如果容纳我的 TextBox 的容器处于焦点位置,那么滚动就会起作用,只是在 TextBox 处于焦点位置时就不行了。

    我不需要更改事件的透明度,我需要将事件发送到不同的控制句柄! (现在看起来很简单,但我已经尝试了好几天了!)

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

    我在所有的 Google 搜索中都没有看到任何提及此方法的内容,如果有什么理由不这样做,我希望有人回复。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        在我这边@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://stackoverflow.com/a/19618100
                    SendMessage(this.Parent.Handle, m.Msg, m.WParam, m.LParam);
                    m.Result = IntPtr.Zero;
                }else base.WndProc(ref m);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-04-16
          • 1970-01-01
          • 1970-01-01
          • 2019-05-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多