Click here to Skip to main content
15,867,934 members
Articles / Desktop Programming / WPF
Tip/Trick

WPF Behavior to close a Popup with MouseLeave

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
3 Apr 2018CPOL2 min read 22K   367   9   4
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:

  1. The user clicks on the Popup.
  2. 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.
  3. The user clicks on an area outside of the Popup.
  4. The Popup looses Focus.

Background

The following is the code for the behavior:

C#
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 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:

XML
<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

Image 1

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.

Image 2

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
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.

Comments and Discussions

 
QuestionPOPup Pin
Member 1402292125-Feb-19 0:27
Member 1402292125-Feb-19 0:27 
QuestionSome suggestions on the Popup Behavior Pin
ly_he11-Aug-18 17:13
ly_he11-Aug-18 17:13 
Questionah I recall the heady days of WPF Pin
Sacha Barber4-Apr-18 23:06
Sacha Barber4-Apr-18 23:06 
QuestionPlease tag your article Pin
BillWoodruff3-Apr-18 22:04
professionalBillWoodruff3-Apr-18 22:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.