WPF Behavior to close a Popup with MouseLeave





5.00/5 (7 votes)
Normally a popup will stay open until explicitly closed. The Behavior will close the Popup if the mouse is clicked within or outside the Popup area or leaves the Popup area.
Introduction
I was using a Popup
to implement a ListBox
that would appear after a ToggleButton
with the current selection is pressed. Unfortunately, it was after I had implemented everything that I discovered that closing the Popup
was not that straight forward.
The desired functionality of having the Popup
close is the following:
- The user clicks on the
Popup
. - The mouse leaves the area of the
Popup
. One of the concerns was that the mouse may not initially be in the area of thePopup
, and want to allow the user to move the mouse into the area of thePopup
. - The user clicks on an area outside of the
Popup
. - The
Popup
loosesFocus
.
Background
The following is the code for the behavior:
public static class ClosePopupBehavior
{
public static ContentControl GetPopupContainer(DependencyObject obj)
{
return (ContentControl)obj.GetValue(PopupContainerProperty);
}
public static void SetPopupContainer(DependencyObject obj, ContentControl value)
{
obj.SetValue(PopupContainerProperty, value);
}
public static readonly DependencyProperty PopupContainerProperty =
DependencyProperty.RegisterAttached("PopupContainer",
typeof(ContentControl), typeof(ClosePopupBehavior)
, new PropertyMetadata(OnPopupContainerChanged));
private static void OnPopupContainerChanged(DependencyObject d
, DependencyPropertyChangedEventArgs e)
{
var popup = (Popup)d;
var contentControl = e.NewValue as ContentControl;
popup.LostFocus += (sender, args) =>
{
var popup1 = (Popup)sender;
popup.IsOpen = false;
if (contentControl != null)
contentControl.PreviewMouseDown -= ContainerOnPreviewMouseDown;
};
popup.Opened += (sender, args) =>
{
var popup1 = (Popup)sender;
popup.Focus();
SetWindowPopup(contentControl, popup1);
contentControl.PreviewMouseDown -= ContainerOnPreviewMouseDown;
contentControl.PreviewMouseDown += ContainerOnPreviewMouseDown;
};
popup.PreviewMouseUp += (sender, args) =>
{
popup.IsOpen = false;
if (contentControl != null)
contentControl.PreviewMouseDown -= ContainerOnPreviewMouseDown;
};
popup.MouseLeave += (sender, args) =>
{
popup.IsOpen = false;
if (contentControl != null)
contentControl.PreviewMouseDown -= ContainerOnPreviewMouseDown;
};
popup.Unloaded += (sender, args) =>
{
popup.IsOpen = false;
if (contentControl != null)
contentControl.PreviewMouseDown -= ContainerOnPreviewMouseDown;
};
}
//This is really to handle touch panel, Not sure if it is needed since handle LostFocus.
//Remove the contentControl stuff if it is not needed
private static void ContainerOnPreviewMouseDown(object sender
, MouseButtonEventArgs mouseButtonEventArgs)
{
var popup = GetWindowPopup((DependencyObject)sender);
popup.IsOpen = false;
((FrameworkElement)sender).PreviewMouseUp -= ContainerOnPreviewMouseDown;
}
private static Popup GetWindowPopup(DependencyObject obj)
{
return (Popup)obj.GetValue(WindowPopupProperty);
}
private static void SetWindowPopup(DependencyObject obj, Popup value)
{
obj.SetValue(WindowPopupProperty, value);
}
private static readonly DependencyProperty WindowPopupProperty =
DependencyProperty.RegisterAttached("WindowPopup",
typeof(Popup), typeof(ClosePopupBehavior));
}
The behavior is initiated by providing a ContentControl
in the PopupContainer
DependencyProperty
. This does not have to be a Window
, it can be any ContentControl
, which can be a Window
or UserControl
.
The change event handler for this DependencyProperty
then attaches event handlers for the following events
:
LostFocus
: Closes thePopup
when thePopup
looses focus and removes thePreviewMouseUp
event
handler from thePopupContainer
.Opened
: Attaches a event handler to thePreviewMouseDown
on thePopupContainer
. This allows thePopupContainer
PreviewMouseDown
event
handler to close thePopup
and remove theevent
handler.PreviewMouseUp
: Closes thePopup
onPreviewMouseUp
of thePopup
and removes thePreviewMouseUp
event
handler from thePopupContainer
.MouseLeave
: Closes thePopup
onMouseLeave
of thePopup
and removes thePreviewMouseUp
event
handler from thePopupContainer
.Unloaded
: Removes thePreviewMouseUp
event handler from thePopupContainer
.
The code is probably a bit too overprotected in the sense that some of these event
handlers are probably not needed, and the handling of the PreviewMouseDown
is probably not needed except I was not sure about the case of touch interaction.
Using the Code
The Behavior
is only useable on a Popup
Control
. All that is needed is to include the behavior in the XAML for the Popup
, and reference a ContentControl
containing the Popup
:
<Popup local:ClosePopupBehavior.PopupContainer="{Binding ElementName=Window}"
IsOpen="{Binding ElementName=ToggleButton1,
Path=IsChecked}"
PlacementTarget="{Binding ElementName=ToggleButton1}">
<Border Width="200"
Height="100"
Background="LightBlue" />
</Popup>
The Sample
The bottom ToggleButton
opens a PopUp
which does not have the behavior attached. The Popup
has a Background
Color
of Pink
. You will notice that the only way to close the Popup
is to press the ToggleButton
again. Hopefully, the ToggleButton
is not hidden.
The top ToggleButton
opens a PopUp
which does have the behavior attached. The Popup
has a Background
Color
of LightBlue
.
History
- 04/03/2018: Initial version