3
votes

I have a WinForms control in a WPF application. Luckily in WPF, mouse wheel events are normally handled by the control under the mouse cursor... but if it's a WinForms control, I have to click the control first to give it the keyboard focus. How do I bypass this behavior and send mouse wheel events to the WinForms control when it does not have the focus?

(Note: the MouseWheel and PreviewMouseWheel events do not seem to work for WindowsFormsHost. If you add a handler for PreviewMouseWheel in a parent of the WindowsFormsHost, it is not called when the mouse is on top of a non-WPF control. Obviously, the WinForms control does not get a MouseWheel event either; the event seems to vanish entirely.)

1
Did you try to set the focus on the wfh in its mouseover event ?Larry
No, but that would be incorrect behavior: the keyboard focus should not change just because the mouse was moved. My app has textboxes so the user might notice the focus changed unexpectedly that way. WindowsFormsHost does not receive MouseEnter or MouseMove events either, though the control that it contains does.Qwertie

1 Answers

5
votes

One possible solution is to handle all WM_MOUSEWHEEL messages and raise MouseWheel event for unfocused controls manually. My attached behavior implementing this idea:

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

It can be used both in XAML:

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

and code behind:

CaptureMouseWheelWhenUnfocusedBehavior.SetIsEnabled(host, true);

Update: added code to raise event for WinForms control also