Search code examples
c#wpfpopup

WPF Popup - How to close a WPF popup when the ESC key is pressed


I have a popup named "ContextPopup" defined in a ResourceDictionary as follows:

    <ControlTemplate x:Key="UXIconComboBoxControlTemplate" TargetType="{x:Type controls:UXIconComboBox}" >
    <ControlTemplate.Resources>
        <converters:IconComboBoxDropdownHorizontalOffsetMultiConverter x:Key="ComboBoxDropdownHorizontalOffsetConverter"/>
    </ControlTemplate.Resources>
    <Grid 
        x:Name="rootGrid"
        Width="{TemplateBinding HitAreaWidth}" 
        Height="{TemplateBinding HitAreaHeight}" 
        Background="{TemplateBinding Background}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <controls:ExtendedHitAreaButton 
            Grid.Row="0"
            x:Name="OpenPopupIconButton"
            IsDefault="True"
            Style="{StaticResource UXIconComboBoxButtonStyle}">
             <controls:ExtendedHitAreaButton.Triggers>
                <EventTrigger RoutedEvent="Button.Click">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <BooleanAnimationUsingKeyFrames 
                                    Storyboard.TargetName="ContextPopup" 
                                    Storyboard.TargetProperty="IsOpen">
                                    <DiscreteBooleanKeyFrame KeyTime="0:0:0.25" Value="True" />
                                </BooleanAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </controls:ExtendedHitAreaButton.Triggers>
        </controls:ExtendedHitAreaButton>

       <Popup 
           x:Name="ContextPopup"
           Grid.Row="1" 
           Placement="Bottom"
           PlacementTarget="{Binding ElementName=OpenPopupIconButton}"
           StaysOpen="False"
           PreviewKeyDown="ContextPopup_PreviewKeyDown">
           <Popup.HorizontalOffset>
               <MultiBinding Converter="{StaticResource ComboBoxDropdownHorizontalOffsetConverter}">
                   <Binding ElementName="OpenPopupIconButton" Path="ActualWidth"/>
                   <Binding ElementName="PopupListBox" Path="ActualWidth"/>
               </MultiBinding>
           </Popup.HorizontalOffset>
            <ListBox 
                x:Name="PopupListBox" 
                ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"
                ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource TemplatedParent}}"
                Loaded="PopupListBox_OnLoaded"
                KeyboardNavigation.IsTabStop="True"
                KeyboardNavigation.TabNavigation="Continue"
                BorderThickness="0"/>
       </Popup>
    </Grid>
    <ControlTemplate.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding IconComboBoxSemanticColorType}" Value="Default"/>
                <Condition Binding="{Binding Path=IsOpen, ElementName=ContextPopup}" Value="True" />
            </MultiDataTrigger.Conditions>
            <Setter Property="Background" Value="{DynamicResource BrushIconComboBox_Popup_Open_Background}" />
        </MultiDataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

The Popup has a PreviewKeyDown method called "ContextPopup_PreviewKeyDown" that is defined in a ResourceDictionary code-behind file as follows:

private void ContextPopup_PreviewKeyDown(object sender, KeyEventArgs e)
{
    var popupControl = sender as Popup;

    if (e.Key == Key.Escape)
    {
        popupControl.IsOpen = false;
    }
}

When the popup is displayed, I press the ESC button and have verified that the ContextPopup_PreviewKeyDown method is called and the popupControl.IsOpen property is set to false, but the popup does not close.

Can anyone explain why the popup does not close and what to do about it.

Thanks for any help.


Solution

  • You can't modify the property value as long as a Storyboard is still animating it. Either stop the Storyboard explicitly or apply another animation to modify the value.

    The following example uses the VisualStateManager to trigger the animations.

    <ControlTemplate TargetType="{x:Type local:UXIconComboBox}">
      <Border Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}">
        <VisualStateManager.VisualStateGroups>
          <VisualStateGroup x:Name="PopupStates">
            <VisualState x:Name="PopupClosed">
              <Storyboard>
                <BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_ContextPopup"
                                                Storyboard.TargetProperty="IsOpen">
                  <DiscreteBooleanKeyFrame KeyTime="0:0:0.25"
                                           Value="False" />
                </BooleanAnimationUsingKeyFrames>
              </Storyboard>
            </VisualState>
            <VisualState x:Name="PopupOpen">
              <Storyboard>
                <BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_ContextPopup"
                                                Storyboard.TargetProperty="IsOpen">
                  <DiscreteBooleanKeyFrame KeyTime="0:0:0.25"
                                           Value="True" />
                </BooleanAnimationUsingKeyFrames>
              </Storyboard>
            </VisualState>
          </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    
        <Grid>
          <Button x:Name="PART_OpenPopupIconButton" />
          <Popup x:Name="PART_ContextPopup"
                 AllowsTransparency="True">
            <TextBox Text="Hello World!" />
          </Popup>
        </Grid>
      </Border>
    </ControlTemplate>
    
    public class UXIconComboBox : Control
    {
      private ButtonBase? PART_OpenPopupIconButton { get; set; }
      private UIElement? PART_ContextPopup { get; set; }
    
      static UXIconComboBox() 
      {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(UXIconComboBox), new FrameworkPropertyMetadata(typeof(UXIconComboBox)));
      }
    
      public override void OnApplyTemplate()
      {
        base.OnApplyTemplate();
    
        this.PART_OpenPopupIconButton = GetTemplateChild("PART_OpenPopupIconButton") as ButtonBase;
        this.PART_OpenPopupIconButton.Click += OpenPopupIconButton_Click;
    
        this.PART_ContextPopup = GetTemplateChild("PART_ContextPopup") as UIElement;
        this.PART_ContextPopup.PreviewKeyDown += ContextPopup_PreviewKeyDown;
      }
    
      protected void ContextPopup_PreviewKeyDown(object sender, KeyEventArgs e)
      {
        if (e.Key == Key.Escape)
        {
          _ = VisualStateManager.GoToState(this, "PopupClosed", true);
        }
      }
    
      private void OpenPopupIconButton_Click(object sender, System.Windows.RoutedEventArgs e) 
        => _ = VisualStateManager.GoToState(this, "PopupOpen", true);
    }