How can a control handle a Mouse click outside of that control?

chrislarson picture chrislarson · Jul 20, 2011 · Viewed 24.5k times · Source

I'm writing a custom control and I'd like the control to switch from an editing state to it's normal state when a user clicks off of the control. I'm handling the LostFocus event and that helps when a user tabs away or if they click on to another control that's Focusable. But if they don't click on something Focusable, it won't switch out of it's editing state. So I have two solutions in mind:

  • Walk up the tree to the top most element when it goes in to an editing state and add a handler for MouseDownEvent (and handle "handled" events). In the handler I'd kick the control out of it's editing state and remove the handler from the top most element. This seems like a bit of a hack, but it would probably work well.

Example code:

private void RegisterTopMostParentMouseClickEvent()
{
   _topMostParent = this.FindLastVisualAncestor<FrameworkElement>();
   if ( _topMostParent == null )
      return;
   _topMostParent.AddHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ), true );
}

private void UnRegisterTopMostParentMouseClickEvent()
{
   if ( _topMostParent == null )
      return;
   _topMostParent.RemoveHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ) );
   _topMostParent = null;
}
  • Use Mouse.PreviewMouseDownOutsideCapturedElement and add a handler to my control. In the handler I'd kick the control out of it's editing state. But I don't seem to get the event to fire. When does the Mouse.PreviewMouseDownOutsideCapturedElement get kicked off?

Example code:

AddHandler( Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler( EditableTextBlockPreviewMouseDownOutsideCapturedElementEvent ), true );

Answer

JonnyRaa picture JonnyRaa · Jan 25, 2013

Just to clarify the answer provided about mouse focus - it was useful but I had to do some further digging + mucking about to get something that actually worked:

I was trying to implement something like a combobox and needed similar behaviour - to get the drop down to disapear when clicking on something else, without the control having knowledge of what something else was.

I had the following event for a drop down button:

    private void ClickButton(object sender, RoutedEventArgs routedEventArgs)
    {
        //do stuff (eg activate drop down)
        Mouse.Capture(this, CaptureMode.SubTree);
        AddHandler();
    }

The CaptureMode.SubTree means you only get events that are outside the control and any mouse activity in the control is passed through to things as normal. You dont have the option to provide this Enum in UIElement's CaptureMouse, this means you will get calls to HandleClickOutsideOfControl INSTEAD of calls to any child controls or other handlers within the control. This is the case even if you dont subscribe to the events they are using - full Mouse capture is a bit too much!

    private void AddHandler()
    {
        AddHandler(Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(HandleClickOutsideOfControl), true);
    }

You would also need to hang on to + remove the handler at the appropriate points but I've left that out here for the sake of clarity/brevity.

Finally in the handler you need to release the capture again.

    private void HandleClickOutsideOfControl(object sender, MouseButtonEventArgs e)
    {
        //do stuff (eg close drop down)
        ReleaseMouseCapture();
    }