如何将消息(例如鼠标滚轮)转发到另一个控件,而不会窃取焦点和没有 P/Invoke

本文关键字:焦点 Invoke 控件 消息 鼠标 转发 另一个 | 更新日期: 2023-09-27 17:56:50

我想在用鼠标越过此控件时转发一条消息(例如WM_MOUSEWHEEL),而不会窃取焦点。这个问题可以很容易地解决,用IMessageFilter截获消息(将添加到应用程序消息泵)并使用P/Invoke(d)SendMessage()转发它。问题是:我可以在不使用 P/Invoke 的情况下做同样的事情吗(我在 StackOverflow 中找到的解决方案使用 P/Invoke)?如果没有,为什么?

下面的代码是我使用 P/Invoke 的解决方案。我只用new MessageForwarder(control, 0x20A)

.
/// <summary>
/// This class implements a filter for the Windows.Forms message pump allowing a
/// specific message to be forwarded to the Control specified in the constructor.
/// Adding and removing of the filter is done automatically.
/// </summary>
public class MessageForwarder : IMessageFilter
{
#region Fields
private Control _Control;
private Control _PreviousParent;
private HashSet<int> _Messages;
private bool _IsMouseOverControl;
#endregion // Fields
#region Constructors
public MessageForwarder(Control control, int message)
    : this(control, new int[] { message }) { }
public MessageForwarder(Control control, IEnumerable<int> messages)
{
    _Control = control;
    _Messages = new HashSet<int>(messages);
    _PreviousParent = control.Parent;
    _IsMouseOverControl = false;
    control.ParentChanged += new EventHandler(control_ParentChanged);
    control.MouseEnter += new EventHandler(control_MouseEnter);
    control.MouseLeave += new EventHandler(control_MouseLeave);
    control.Leave += new EventHandler(control_Leave);
    if (control.Parent != null)
        Application.AddMessageFilter(this);
}
#endregion // Constructors
#region IMessageFilter members
public bool PreFilterMessage(ref Message m)
{
    if (_Messages.Contains(m.Msg) && _Control.CanFocus && !_Control.Focused
        && _IsMouseOverControl)
    {
        SendMessage(_Control.Handle, m.Msg, m.WParam, m.LParam);
        return true;
    }
    return false;
}
#endregion // IMessageFilter
#region Event handlers
void control_ParentChanged(object sender, EventArgs e)
{
    if (_Control.Parent == null)
        Application.RemoveMessageFilter(this);
    else
    {
        if (_PreviousParent == null)
            Application.AddMessageFilter(this);
    }
    _PreviousParent = _Control.Parent;
}
void control_MouseEnter(object sender, EventArgs e)
{
    _IsMouseOverControl = true;
}
void control_MouseLeave(object sender, EventArgs e)
{
    _IsMouseOverControl = false;
}
void control_Leave(object sender, EventArgs e)
{
    _IsMouseOverControl = false;
}
#endregion // Event handlers
#region Support
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
#endregion // Support

}

编辑:我的答案中的完整解决方案

如何将消息(例如鼠标滚轮)转发到另一个控件,而不会窃取焦点和没有 P/Invoke

找到了一个方法:你必须继承NativeWindow,将所选控件的句柄分配给它,在你以任何你喜欢的方式截获消息后调用受保护的 WndProc(就我而言,继承的类甚至是一个 IMessageFilter,所以我可以轻松地将其插入应用程序)。我用它来new MessageForwarder(anycontrol, 0x20A)重定向鼠标滚轮。

因此,可以在没有 p/invoke 的情况下截获消息并将其转发到任何控件。不过隐藏得很好。

/// <summary>
/// This class implements a filter for the Windows.Forms message pump allowing a
/// specific message to be forwarded to the Control specified in the constructor.
/// Adding and removing of the filter is done automatically.
/// </summary>
public class MessageForwarder : NativeWindow, IMessageFilter
{
    #region Fields
    private Control _Control;
    private Control _PreviousParent;
    private HashSet<int> _Messages;
    private bool _IsMouseOverControl;
    #endregion // Fields
    #region Constructors
    public MessageForwarder(Control control, int message)
        : this(control, new int[] { message }) { }
    public MessageForwarder(Control control, IEnumerable<int> messages)
    {
        _Control = control;
        AssignHandle(control.Handle);
        _Messages = new HashSet<int>(messages);
        _PreviousParent = control.Parent;
        _IsMouseOverControl = false;
        control.ParentChanged += new EventHandler(control_ParentChanged);
        control.MouseEnter += new EventHandler(control_MouseEnter);
        control.MouseLeave += new EventHandler(control_MouseLeave);
        control.Leave += new EventHandler(control_Leave);
        if (control.Parent != null)
            Application.AddMessageFilter(this);
    }
    #endregion // Constructors
    #region IMessageFilter members
    public bool PreFilterMessage(ref Message m)
    {
        if (_Messages.Contains(m.Msg) && _Control.CanFocus && !_Control.Focused
            && _IsMouseOverControl)
        {
            m.HWnd = _Control.Handle;
            WndProc(ref m);
            return true;
        }
        return false;
    }
    #endregion // IMessageFilter
    #region Event handlers
    void control_ParentChanged(object sender, EventArgs e)
    {
        if (_Control.Parent == null)
            Application.RemoveMessageFilter(this);
        else
        {
            if (_PreviousParent == null)
                Application.AddMessageFilter(this);
        }
        _PreviousParent = _Control.Parent;
    }
    void control_MouseEnter(object sender, EventArgs e)
    {
        _IsMouseOverControl = true;
    }
    void control_MouseLeave(object sender, EventArgs e)
    {
        _IsMouseOverControl = false;
    }
    void control_Leave(object sender, EventArgs e)
    {
        _IsMouseOverControl = false;
    }
    #endregion // Event handlers
}

我找到了一个更简单的解决方案,仅当您尝试转发的消息具有相应的事件时,才能应用该解决方案。例如,对于鼠标滚轮事件:

// Redirect the mouse wheel event from panel1 to panel2.
// When the panel1 is focused and the mouse wheel is used the panel2 will scroll.
private void panel1_MouseWheel(object sender, MouseEventArgs e)
{
   // Get the MouseWheel event handler on panel2
   System.Reflection.MethodInfo onMouseWheel = 
       panel2.GetType().GetMethod("OnMouseWheel", 
                                   System.Reflection.BindingFlags.NonPublic | 
                                   System.Reflection.BindingFlags.Instance);
   // Call the panel2 mousehwweel event with the same parameters
   onMouseWheel.Invoke(panel2, new object[] { e });
}

这实际上取决于事件的类型和数量。如何将鼠标移动等事件传递给父控件(例如,使控件行为"透明")?

控件中的一个事件处理程序可能如下所示(未经测试的代码):

private void MyControl_MouseMove(object sender, MouseEventArgs e)
{
    if(Parent == null)
        return;
    // add this control's offsets first so the coordinates fit to the parent control
    e.X += this.Top;
    e.Y += this.Left;
    if(parent.MouseMove != null)
        parent.MouseMove(sender, e);
}