WPF Popup问题:按ESC键无法关闭弹窗的原因及解决方法
问题:Popup设置IsOpen=false后无法关闭(ESC键触发)
在ResourceDictionary中定义了UXIconComboBoxControlTemplate,其中包含名为ContextPopup的Popup控件,通过按钮点击的Storyboard动画打开弹窗;同时在代码后置文件中实现了ContextPopup_PreviewKeyDown方法,按下ESC键时设置popupControl.IsOpen = false。已确认方法被调用,但弹窗始终无法关闭。
相关代码
XAML模板代码
<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>
代码后置事件处理
private void ContextPopup_PreviewKeyDown(object sender, KeyEventArgs e) { var popupControl = sender as Popup; if (e.Key == Key.Escape) { popupControl.IsOpen = false; } }
原因分析
问题核心在于Storyboard动画对依赖属性的优先级高于代码直接赋值。你通过BooleanAnimationUsingKeyFrames将ContextPopup.IsOpen设置为True,动画默认的FillBehavior为HoldEnd,这意味着动画结束后会一直保持对IsOpen属性的控制权。此时即使通过代码设置IsOpen=false,也会被动画的属性值覆盖,导致弹窗无法关闭。
解决办法
方法一:停止对应的Storyboard动画
给打开弹窗的Storyboard命名,在ESC事件处理中先停止动画,再设置IsOpen=false:
- 修改XAML中的Storyboard,添加
x:Name:
<BeginStoryboard x:Name="OpenPopupStoryboard"> <Storyboard> <BooleanAnimationUsingKeyFrames Storyboard.TargetName="ContextPopup" Storyboard.TargetProperty="IsOpen"> <DiscreteBooleanKeyFrame KeyTime="0:0:0.25" Value="True" /> </BooleanAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard>
- 修改事件处理代码:
private void ContextPopup_PreviewKeyDown(object sender, KeyEventArgs e) { var popupControl = sender as Popup; var templateParent = popupControl.TemplatedParent as UIElement; if (e.Key == Key.Escape) { // 找到并停止Storyboard var storyboard = templateParent.FindResource("OpenPopupStoryboard") as Storyboard; storyboard?.Stop(popupControl); popupControl.IsOpen = false; } }
方法二:改用Trigger直接设置IsOpen(替代Storyboard)
如果不需要延迟打开的动画效果,直接用Trigger设置IsOpen,避免动画持有属性控制权:
<controls:ExtendedHitAreaButton.Triggers> <EventTrigger RoutedEvent="Button.Click"> <Setter TargetName="ContextPopup" Property="IsOpen" Value="True"/> </EventTrigger> </controls:ExtendedHitAreaButton.Triggers>
方法三:设置动画的FillBehavior为Stop
让动画结束后不再保持属性值,这样代码赋值就能生效:
<Storyboard FillBehavior="Stop"> <BooleanAnimationUsingKeyFrames Storyboard.TargetName="ContextPopup" Storyboard.TargetProperty="IsOpen"> <DiscreteBooleanKeyFrame KeyTime="0:0:0.25" Value="True" /> </BooleanAnimationUsingKeyFrames> </Storyboard>
注意:此方法下动画结束后IsOpen会自动回到初始值(默认是false),如果需要保持弹窗打开,需要在动画结束后通过其他方式(比如Trigger)维持IsOpen=true,否则弹窗会自动关闭。
内容的提问来源于stack exchange,提问作者cpdev
相关产品推荐
相关产品推荐

