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 the Popup
, and want to allow the user to move the mouse into the area of the Popup
. - The user clicks on an area outside of the
Popup
. - The
Popup
looses Focus
.
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;
};
}
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 the Popup
when the Popup
looses focus and removes the PreviewMouseUp
event
handler from the PopupContainer
. Opened
: Attaches a event handler to the PreviewMouseDown
on the PopupContainer
. This allows the PopupContainer
PreviewMouseDown
event
handler to close the Popup
and remove the event
handler. PreviewMouseUp
: Closes the Popup
on PreviewMouseUp
of the Popup
and removes the PreviewMouseUp
event
handler from the PopupContainer
. MouseLeave
: Closes the Popup
on MouseLeave
of the Popup
and removes the PreviewMouseUp
event
handler from the PopupContainer
. Unloaded
: Removes the PreviewMouseUp
event handler from the PopupContainer
.
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
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.