none
Popup, Popup.StaysOpen, ToggleButton and Data Binding -- Helpful Tip

    General discussion

  • On a recent WPF project we had need of a Popup that would display when a ToggleButton was Checked, not display when Unchecked, and also disappear whenever the user clicked anywhere in the view.

     

    The solution was straightforward: Use Popup.StaysOpen=False, and use data binding on the Popup.IsOpen property to the ToggleButton.IsChecked property (in the below code example, assume a ToggleButton named ExpandPresetList):

     

     

    <Popup

    Name="PresetPopup"

    IsOpen="{Binding ElementName=ExpandPresetList, Path=IsChecked}"

    PlacementTarget="{Binding ElementName=DefaultAudiogramPresetsContainer}"

    AllowsTransparency="True"

    PopupAnimation="Slide"

    StaysOpen="False"

    />

     

    Well, this worked great, until you try to use the ToggleButton to no longer show the Popup. What would happen is that the Popup.StaysOpen property would collide with the data binding to the Popup.IsOpen property, and cause the Popup to never close when you click on the ToggleButton.

     

    The solution was to bind to the ToggleButton.MouseEnter and ToggleButton.MouseLeave properties and modify the Popup.StaysOpen property whenever the user was using the ToggleButton.

     

    Example:

    <ToggleButton

    Name="ExpandPresetList"

    Width="13"

    Height="13"

    VerticalAlignment="Center"

    HorizontalAlignment="Right"

    Margin="2.5 0 2.5 0"

    Style="{StaticResource roundExpandGraphicToggleButtonStyle}"

    MouseEnter="ExpandPresetList_MouseEnter"

    MouseLeave="ExpandPresetList_MouseLeave"

    />

     

    The event handlers for MouseEnter and MouseLeave look as follows:

     

    private void ExpandPresetList_MouseEnter(object sender, MouseEventArgs e)

    {

    PresetPopup.StaysOpen = true;

    }

     

    private void ExpandPresetList_MouseLeave(object sender, MouseEventArgs e)

    {

    PresetPopup.StaysOpen = false;

    }

     

    The above examples let you use a Popup, data bound to a ToggleButton and have the Popup disappear whenever the user clicks anywhere, as well as use the ToggleButton to make the Popup disappear.

     

    This seemed like an elegant solution, does anyone have a good alternate solution, maybe one that doesn't involve code, and can be done in pure XAML? I didn't see any properties on Popup that would assist in resolving the data binding event storm that results when using Popup.StaysOpen and ToggleButton.IsChecked.

    Tuesday, March 18, 2008 1:01 PM

All replies

  • Hi Dan,

    Thanks for sharing!  Came in very handy, had a somewhat similar situation, and I was trying to set StaysOpen = false in the MouseLeftButtonUp, which did not work 100%...

    Regards,

    Dan

    Saturday, April 19, 2008 1:42 AM
  • I work with Dan Edgar on this project and we just solved another, nasty bug ... in regards to this Popup behavior. I hope this post helps someone else avoid the pain I had to incur while solving it.

    The problem asserts itself only when the Popup is expanded (and StaysOpen=false).

    Here is how you reproduce it:
    1) the user toggles the ToggleButton (which expands the Popup)
    2) the user (accidentally) moves the mouse off the ToggleButton
    3) the user goes to toggle the ToggleButton (to close the Popup manually)

    At this point, the Popup collapses, and then immediately expands (the same symptoms as the original symptoms above and what made us add the MouseEnter and MouseLeave event handlers).

    Here is what is happening:
    If you have StaysOpen set to false, the Popup internally captures the mouse (so that it knows if the user has clicked elsewhere ... so that it can collapse the Popup).

    However, what that does is causes us not to get the MouseEnter above (and thus the necessary and needed StaysOpen=true behavior for manually toggling the Popup open and closed).

    Therefore, when the user goes to toggle the Poup closed manually (step 3 above), the captured mouse tells the Popup to collapse ... however, then the ToggleButton kicks in and retoggles the Popup open.

    Here is the solution:
    You need to make the ToggleButton not respond to the mouse when the Popup is expanded. In this way, only the StaysOpen=false, mouse capture functionality kicks in ... and the Popup collapses ... and doesn't expand again.

    How do you do this? You need to bind the IsHitTestVisible property of the ToggleButton to the !IsOpen property of the Popup.

    Wait, you say. There is no such thing as the !IsOpen property. Of course not, and for that you need a converter. See below.

    <appResources:GraphicToggleButton  
        Name="ExpandPresetList" 
        Width="13" 
        Height="13" 
        VerticalAlignment="Center" 
        HorizontalAlignment="Right" 
        Margin="2.5 0 2.5 0" 
        Style="{StaticResource roundExpandGraphicToggleButtonStyle}" 
       IsHitTestVisible="{Binding ElementName=PresetPopup, Path=IsOpen, Mode=OneWay, Converter={StaticResource boolInverterConverter}}" 
    />

    public class BoolInverterConverter : IValueConverter  
    {
        #region IValueConverter Members  
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
        {  
            if (value is bool)  
            {  
                return !((bool)value);  
            }  
            return null;  
        }  
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)  
        {  
            throw new NotImplementedException();  
        }
        #endregion  
    }  
     
    Friday, September 05, 2008 9:04 PM
  • Textbook! Fit my problem like a glove!
    Thursday, March 12, 2009 11:49 PM
  • Nice fix.  The only (minor) issue I have now is Styling/Animation based on the mouse hovering on the toggle button since it's taken out of hit test consideration.

    Thanks to Dan and Cory -- glad to know I'm not the only one.
    Thursday, April 23, 2009 4:20 PM
  • Thank you, Dan, for your post.  It helped me quickly identify the problem.  However, I find the proposed solutions troublesome, so I came up with an alternative.

    The problem is caused by the behaviour associated with setting Popup.StaysOpen to false.  My solution involves tackling the problem at its source by simply not setting StaysOpen to false.  I then substitute with my own behaviour:

    rootWindow = Window.GetWindow( this );
    rootWindow.PreviewMouseLeftButtonDown += rootWindow_PreviewMouseLeftButtonDown;
    
    /// <summary>
    /// Closes the popup when the user clicks outside of this control.
    /// This approach is necessary since setting Popup.StaysOpen to false
    /// doesn't play well with binding Popup.IsOpen to ToggleButton.IsChecked.
    /// </summary> void rootWindow_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArgs e ) { if ( !IsMouseOver ) IsChecked = false; }

    Wednesday, November 04, 2009 8:38 PM
  • Hi,

    This is a nice solution. But, what about keyboard support?

    When ToggleButton has focus and space bar is pressed, first press opens the popup

    but second and later presses does not close the popup.

    Popup is opened again and again.

    You cannot close it via keyboard.

    But enter key works as expected.

    Also, in this implementation, popup is closed whenever a point inside the popup is clicked.

    What should i do to prevent popup when its inside is clicked?

    Any ideas?
    behappy
    Friday, March 12, 2010 9:03 AM
  • I know I'm about a year late to the WPF popup bug party, but I wanted to expand on the solutions above. The hit test solution did not work for me because I had animations - glow effects on the toggle button on rollover and those glow effects were VITAL! so I tried AdrianAlexander's solution. However, this still didn't quite work for me. The key was expanding the IsMouseOver condition to also check the ToggleButton:

      this.PreviewMouseDown += new MouseButtonEventHandler(MainWindow_PreviewMouseDown);
    
      void MainWindow_PreviewMouseDown(object sender, MouseButtonEventArgs e)
      {
       if (!ApplicationMenu.IsMouseOver && !ApplicationButton.IsMouseOver)
        ApplicationButton.IsChecked = false;
      }
    

     And here is the XAML:

        <ToggleButton x:Name="ApplicationButton" HorizontalAlignment="Left" Style="{StaticResource ApplicationButton}" />
    
        <Popup x:Name="ApplicationMenu" PlacementTarget="{Binding ElementName=ApplicationButton}" Placement="Relative" HorizontalOffset="12" VerticalOffset="36" AllowsTransparency="True" StaysOpen="True" IsOpen="{Binding ElementName=ApplicationButton, Path=IsChecked, Mode=TwoWay}">
         <local:ApplicationMenu />
        </Popup>
    

     

    Thursday, September 01, 2011 2:45 PM
  • Didnt work out of the box for me, because I had links with commands on them in my popup, and they were not fired when the previewleftmouseup closed the popup. So I used the MouseLeftButtonUp instead. Also, I closed the popup in all cases, even when inside the control.

    This worked for me:

     

          Window window = Application.Current.MainWindow;

          window.MouseLeftButtonUp += this.OnMouseLeftButtonUp;

     

     

            /// <summary>

            /// Closes the popup when the user clicks it

            /// </summary>

            /// <remarks>

            /// This is loosely based on code from http://social.msdn.microsoft.com/Forums/en/wpf/thread/f0502813-9c4f-4b45-bab8-91f98971e407

            /// This approach is necessary since setting Popup.StaysOpen to false

            /// doesn't play well with binding Popup.IsOpen to ToggleButton.IsChecked.

            /// </remarks>

            void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)

            {

                 this.ButtonOne.IsChecked = false;

            }

    Tuesday, September 20, 2011 8:03 AM
  • Your solution is very cool. This is a common issue for us to handle popup via togglebutton. However, there are code behind in your solution.

    I have an idea to set staysopen property simple in xaml and this trick make it more elegant:

    StaysOpen="{Binding ElementName=ExpandPresetList,Path=IsMouseOver}"

    Tuesday, December 03, 2013 2:54 AM
  • Thnx worked for me!
    Tuesday, February 11, 2014 6:11 PM