【问题标题】:How to transfer mouse wheel events to WindowsFormsHost?如何将鼠标滚轮事件传输到 WindowsFormsHost?
【发布时间】:2012-12-24 03:57:51
【问题描述】:

我在 WPF 应用程序中有一个 WinForms 控件。幸运的是,在 WPF 中,鼠标滚轮事件通常由鼠标光标下的控件处理......但如果它是 WinForms 控件,我必须先单击该控件以使其获得键盘焦点。当 WinForms 控件没有焦点时,如何绕过此行为并将鼠标滚轮事件发送到它?

(注意:MouseWheel 和 PreviewMouseWheel 事件似乎不适用于 WindowsFormsHost。如果您在 WindowsFormsHost 的父级中添加 PreviewMouseWheel 的处理程序,则当鼠标位于顶部时不会调用它非 WPF 控件。显然,WinForms 控件也没有收到MouseWheel 事件;该事件似乎完全消失了。)

【问题讨论】:

  • 您是否尝试在鼠标悬停事件中将焦点设置在 wfh 上?
  • 不,但那将是不正确的行为:键盘焦点不应仅仅因为鼠标移动而改变。我的应用程序有文本框,因此用户可能会注意到焦点以这种方式意外更改。 WindowsFormsHost 也不接收 MouseEnterMouseMove 事件,尽管它包含的控件接收。

标签: .net wpf mousewheel windowsformshost


【解决方案1】:

一种可能的解决方案是处理所有WM_MOUSEWHEEL 消息并手动为未聚焦的控件引发MouseWheel 事件。我的附加行为实现了这个想法:

public static class CaptureMouseWheelWhenUnfocusedBehavior
{
    private static readonly HashSet<WindowsFormsHost> TrackedHosts =
        new HashSet<WindowsFormsHost>();

    private static readonly System.Windows.Forms.IMessageFilter MessageFilter =
        new MouseWheelMessageFilter();

    private sealed class MouseWheelMessageFilter : System.Windows.Forms.IMessageFilter
    {
        [DllImport("User32.dll"), SuppressUnmanagedCodeSecurity]
        private static extern IntPtr WindowFromPoint(System.Drawing.Point point);

        private static System.Drawing.Point LocationFromLParam(IntPtr lParam)
        {
            int x = (int)((((long)lParam) >> 0) & 0xffff);
            int y = (int)((((long)lParam) >> 16) & 0xffff);
            return new System.Drawing.Point(x, y);
        }

        private static bool ConsiderRedirect(WindowsFormsHost host)
        {
            var control = host.Child;
            return control != null &&
                  !control.IsDisposed &&
                   control.IsHandleCreated &&
                   control.Visible &&
                  !control.Focused;
        }

        private static int DeltaFromWParam(IntPtr wParam)
        {
            return (short)((((long)wParam) >> 16) & 0xffff);
        }

        private static System.Windows.Forms.MouseButtons MouseButtonsFromWParam(IntPtr wParam)
        {
            const int MK_LBUTTON = 0x0001;
            const int MK_MBUTTON = 0x0010;
            const int MK_RBUTTON = 0x0002;
            const int MK_XBUTTON1 = 0x0020;
            const int MK_XBUTTON2 = 0x0040;
            int buttonFlags = (int)((((long)wParam) >> 0) & 0xffff);
            var buttons = System.Windows.Forms.MouseButtons.None;
            if(buttonFlags != 0)
            {
                if((buttonFlags & MK_LBUTTON) == MK_LBUTTON)
                {
                    buttons |= System.Windows.Forms.MouseButtons.Left;
                }
                if((buttonFlags & MK_MBUTTON) == MK_MBUTTON)
                {
                    buttons |= System.Windows.Forms.MouseButtons.Middle;
                }
                if((buttonFlags & MK_RBUTTON) == MK_RBUTTON)
                {
                    buttons |= System.Windows.Forms.MouseButtons.Right;
                }
                if((buttonFlags & MK_XBUTTON1) == MK_XBUTTON1)
                {
                    buttons |= System.Windows.Forms.MouseButtons.XButton1;
                }
                if((buttonFlags & MK_XBUTTON2) == MK_XBUTTON2)
                {
                    buttons |= System.Windows.Forms.MouseButtons.XButton2;
                }
            }
            return buttons;
        }

        public bool PreFilterMessage(ref System.Windows.Forms.Message m)
        {
            const int WM_MOUSEWHEEL = 0x020A;
            if(m.Msg == WM_MOUSEWHEEL)
            {
                var location = LocationFromLParam(m.LParam);
                var hwnd = WindowFromPoint(location);
                foreach(var host in TrackedHosts)
                {
                    if(!ConsiderRedirect(host)) continue;
                    if(hwnd == host.Child.Handle)
                    {
                        var delta = DeltaFromWParam(m.WParam);
                        {
                            // raise event for WPF control
                            var mouse = InputManager.Current.PrimaryMouseDevice;
                            var args = new MouseWheelEventArgs(mouse, Environment.TickCount, delta);
                            args.RoutedEvent = WindowsFormsHost.MouseWheelEvent;
                            host.RaiseEvent(args);
                        }
                        {
                            // raise event for winforms control
                            var buttons = MouseButtonsFromWParam(m.WParam);
                            var args = new System.Windows.Forms.MouseEventArgs(
                                buttons, 0, location.X, location.Y, delta);
                            var method = typeof(System.Windows.Forms.Control).GetMethod(
                                "OnMouseWheel",
                                System.Reflection.BindingFlags.Instance |
                                System.Reflection.BindingFlags.NonPublic);
                            method.Invoke(host.Child, new object[] { args });
                        }
                        return true;

                    }
                }
            }
            return false;
        }
    }

    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached(
            "IsEnabled",
            typeof(bool),
            typeof(CaptureMouseWheelWhenUnfocusedBehavior),
            new PropertyMetadata(false, OnIsEnabledChanged));

    private static void OnIsEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var wfh = o as WindowsFormsHost;
        if(wfh == null) return;

        if((bool)e.NewValue)
        {
            wfh.Loaded += OnHostLoaded;
            wfh.Unloaded += OnHostUnloaded;
            if(wfh.IsLoaded && TrackedHosts.Add(wfh))
            {
                if(TrackedHosts.Count == 1)
                {
                    System.Windows.Forms.Application.AddMessageFilter(MessageFilter);
                }
            }
        }
        else
        {
            wfh.Loaded -= OnHostLoaded;
            wfh.Unloaded -= OnHostUnloaded;
            if(TrackedHosts.Remove(wfh))
            {
                if(TrackedHosts.Count == 0)
                {
                    System.Windows.Forms.Application.RemoveMessageFilter(MessageFilter);
                }
            }
        }
    }

    private static void OnHostLoaded(object sender, EventArgs e)
    {
        var wfh = (WindowsFormsHost)sender;
        if(TrackedHosts.Add(wfh))
        {
            if(TrackedHosts.Count == 1)
            {
                System.Windows.Forms.Application.AddMessageFilter(MessageFilter);
            }
        }
    }

    private static void OnHostUnloaded(object sender, EventArgs e)
    {
        var wfh = (WindowsFormsHost)sender;
        if(TrackedHosts.Remove(wfh))
        {
            if(TrackedHosts.Count == 0)
            {
                System.Windows.Forms.Application.RemoveMessageFilter(MessageFilter);
            }
        }
    }
}

它可以在 XAML 中使用:

<WindowsFormsHost namespace:CaptureMouseWheelWhenUnfocusedBehavior.IsEnabled="True" />

和后面的代码:

CaptureMouseWheelWhenUnfocusedBehavior.SetIsEnabled(host, true);

更新:添加代码以引发 WinForms 控件的事件

【讨论】:

  • 这很好用,除了它只将滚轮事件传递给WindowsFormsHost,它不会在 WinForms 控件上引发事件。如果您愿意,我可以使用必要的基于反射的命令来编辑答案,以调用 Control.OnMouseWheel,它受到保护,因此不能直接调用。
  • 这里是 LocationFromLParam 方法的改进版本,适用于多个监视器系统:short x = (short) ((((long) lParam) &gt;&gt; 0) &amp; 0xffff); short y = (short) ((((long) lParam) &gt;&gt; 16) &amp; 0xffff); return new System.Drawing.Point(x, y);
  • 非常好!小修复: OnMouseWheel 事件参数应包括客户端坐标,而不是屏幕坐标,因此需要转换它们: var clientpoint = host.Child.PointToClient(location); var args = new System.Windows.Forms.MouseEventArgs(buttons, 0, clientpoint.X, clientpoint.Y, delta);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
  • 2014-12-30
  • 2011-07-21
  • 1970-01-01
相关资源
最近更新 更多