How can a control handle a Mouse click outside of that control?
Asked Answered
B

4

31

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 );
Bekelja answered 20/7, 2011 at 12:20 Comment(0)
G
16

Capture the mouse. When an object captures the mouse, all mouse related events are treated as if the object with mouse capture perform the event, even if the mouse pointer is over another object.

Globose answered 20/7, 2011 at 12:24 Comment(3)
So this worked perfectly. All I had to do was Capture the mouse when I entered editing mode, then WPF automatically takes focus away from the element when you click outside of it. After I lose focus I just had to be careful to ReleaseMouseCapture(). Thanks!Bekelja
The only other thing I had to be careful about was when you hit the windows key or another app launches, I would lose mouse capture without it stopping my control from being in an editing state. So I had to handle the IsMouseCapturedChanged event. E.g., private void CustomControlIsMouseCapturedChanged( object sender, DependencyPropertyChangedEventArgs e ) { if ( (bool)e.NewValue == false) { IsEditing = false; } }Bekelja
due to mousecaption all other controls will not be clickable or the click wont happen on them. Even elements are not any more highlighted when having the mouse over them. How to solve that? This behavior is more than unlucky.Upwards
O
41

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();
    }
Ot answered 25/1, 2013 at 14:5 Comment(2)
Some controls like TextBox tend to release caputre when clicked on so you will have to reattach the handler each time control rises LostMouseCapture event. Just my observation.Emergency
@Emergency Not quite right. Event subscription remains active so you need to repeat Mouse.Capture(...). Just my observationCompartmentalize
G
16

Capture the mouse. When an object captures the mouse, all mouse related events are treated as if the object with mouse capture perform the event, even if the mouse pointer is over another object.

Globose answered 20/7, 2011 at 12:24 Comment(3)
So this worked perfectly. All I had to do was Capture the mouse when I entered editing mode, then WPF automatically takes focus away from the element when you click outside of it. After I lose focus I just had to be careful to ReleaseMouseCapture(). Thanks!Bekelja
The only other thing I had to be careful about was when you hit the windows key or another app launches, I would lose mouse capture without it stopping my control from being in an editing state. So I had to handle the IsMouseCapturedChanged event. E.g., private void CustomControlIsMouseCapturedChanged( object sender, DependencyPropertyChangedEventArgs e ) { if ( (bool)e.NewValue == false) { IsEditing = false; } }Bekelja
due to mousecaption all other controls will not be clickable or the click wont happen on them. Even elements are not any more highlighted when having the mouse over them. How to solve that? This behavior is more than unlucky.Upwards
T
3

I usually get the parent window and add a preview handler, even if already handled. Sometimes when MouseCapture is not enough, this technique comes handy:

Window.GetWindow(this).AddHandler
(
    UIElement.MouseDownEvent,
    (MouseButtonEventHandler)TextBox_PreviewMouseDown,
    true
);
Thornie answered 5/10, 2017 at 10:38 Comment(0)
S
0

I would approach this a different way - have the form that contains the control remove focus from the control when a user clicks on another part of the form.

Having the control actually loose focus is far cleaner than attempting to have the control "simulate" focus being lost in certain situations, when in fact it hasn't. Bear in mind that unless the control has really lost focus it will still accept things like keyboard input.

Serge answered 20/7, 2011 at 12:25 Comment(4)
So are you saying that I make the form focusable? Because focus won't be taken away automatically unless it's shifted to another element, correct? For example, this won't take focus from the TextBox: <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="FocusTest.MainWindow" x:Name="Window" Width="640" Height="480"> <Grid> <TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Width="75"/> </Grid> </Window>Bekelja
@Bekelja You can set another control (for example a label, possibly one with no text) to be in focus - see Is there a way to cause an UI element to loose focusSerge
That's certainly fine with me, I can still just base whether or not the control is in an editing state by whether it has focus or not. But I still need a way to reliably make the control lose focus when a user clicks outside of the control.Bekelja
@Bekelja Handle the MouseClick event on the form - if the user clicks on the control then the control will recieve the event first and so can handle it, preventing it from being bubbled to the form handler. Clicks elsewhere on the form will be handled by this event however, which is where you can move focus to your dummy focus control.Serge

© 2022 - 2024 — McMap. All rights reserved.