Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Releasing mouse capture and letting mouse click pass through

I have a control that is similar to a Popup or Menu. I want to display it and when the user clicks outside the bounds of the box, have it hide itself. I've used Mouse.Capture(this, CaptureMode.SubTree) as well as re-acquired the capture the same way Menu/Popup do in OnLostMouseCapture.

When the user clicks outside the bounds of the control, I release the mouse capture in OnPreviewMouseDown. I don't set e.Handled to true. The mouse click will make it to other controls on the main UI, but not to the close button (Red X) for the window. It requires 2 clicks to close the app.

Is there a way to tell WPF to restart the mouse click, or to send a repeated mouse click event?

Here's my code. Note I renamed it to MainMenuControl - I'm not building a Menu, so Menu/MenuItem and Popup aren't options.

public class MainMenuControl : Control
    {
        static MainMenuControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MainMenuControl), new FrameworkPropertyMetadata(typeof(MainMenuControl)));
        }

        public MainMenuControl()
        {
            this.Loaded += new RoutedEventHandler(MainMenuControl_Loaded);

            Mouse.AddPreviewMouseDownOutsideCapturedElementHandler(this, OnPreviewMouseDownOutsideCapturedElementHandler);
        }

        void MainMenuControl_Loaded(object sender, RoutedEventArgs e)
        {
            this.IsVisibleChanged += new DependencyPropertyChangedEventHandler(MainMenuControl_IsVisibleChanged);
        }

        void MainMenuControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (this.IsVisible)
            {
                Mouse.Capture(this, CaptureMode.SubTree);
                Debug.WriteLine("Mouse.Capture");
            }
        }

        // I was doing this in OnPreviewMouseDown, but changing to this didn't have any effect
        private void OnPreviewMouseDownOutsideCapturedElementHandler(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("OnPreviewMouseDownOutsideCapturedElementHandler");

            if (!this.IsMouseInBounds())
            {
                if (Mouse.Captured == this)
                {
                    Mouse.Capture(this, CaptureMode.None);
                    Debug.WriteLine("Mouse.Capture released");
                }
                Debug.WriteLine("Close Menu");
            }
        }

        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);

            Debug.WriteLine("OnLostMouseCapture");

            MainMenuControl reference = e.Source as MainMenuControl;
            if (Mouse.Captured != reference)
            {
                if (e.OriginalSource == reference)
                {
                    if ((Mouse.Captured == null) || (!reference.IsAncestorOf(Mouse.Captured as DependencyObject)))
                    {
                        //TODO: Close
                        Debug.WriteLine("Close Menu");
                    }
                }
                // if a child caused use to lose the capture, then recapture.
                else if (reference.IsAncestorOf(e.OriginalSource as DependencyObject))
                {
                    if (Mouse.Captured == null)
                    {
                        Mouse.Capture(reference, CaptureMode.SubTree);
                        Debug.WriteLine("Mouse.Capture");
                        e.Handled = true;
                    }
                }
                else
                {
                    //TODO: Close
                    Debug.WriteLine("Close Menu");
                }
            }

        }

        private bool IsMouseInBounds()
        {
            Point point = Mouse.GetPosition(this);
            Rect bounds = new Rect(0, 0, this.Width, this.Height);

            return bounds.Contains(point);
        }

    }
like image 677
Geoff Cox Avatar asked Apr 22 '11 03:04

Geoff Cox


1 Answers

The problem is that the mouse handling you are talking about is outside the WPF eventing system and part of the operating system so we're really talking about two fairly different mouse message queues that interact well enough most of the time but in these edge case we see that the interoperability is not perfect.

You could try to generate Win32 mouse messages or send your own window a close message but all those approaches are hacks. Since popups and menus exhibit exactly the same symptoms you describe, it doesn't seem like there is going to be an easy to way to accomplish what you want as you've described it.

Instead, I suggest that you consider giving up the mouse capture when the mouse leaves the north client area of the window or some other heuristic such as a specified distance from the control. I know this is probably not ideal but it might be a satisfactory compromise if you want the close button to work badly enough.

like image 119
Rick Sladkey Avatar answered Nov 03 '22 18:11

Rick Sladkey