如何在其 parent 折叠时关闭弹出窗口?

How to close a popup when its parent is collapsed?

背景

我有一个 HeaderedContentControl 的控件模板,它包含一个按钮作为 header,单击它会打开一个包含主要内容的弹出窗口(通过 EventTrigger)。

这按我的预期工作,直到我尝试向折叠控件的弹出窗口添加一个按钮。主按钮(header)消失了,但弹出窗口(内容)保留在原处,直到我从中获取鼠标焦点(通过单击某处)。

这是一个演示该问题的简短示例:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfTest.View"
    Height="300" Width="400" >

    <Window.Resources>
        <ControlTemplate x:Key="ControlTemplate" TargetType="{x:Type HeaderedContentControl}" >
            <Grid Width="100" Height="25" >
                <Button Content="{TemplateBinding Header}" >
                    <Button.Triggers>
                        <EventTrigger RoutedEvent="Button.Click" >
                            <BeginStoryboard>
                                <Storyboard>
                                    <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup"
                                        Storyboard.TargetProperty="(Popup.IsOpen)" >
                                        <DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
                                    </BooleanAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </Button.Triggers>
                </Button>
                <Popup x:Name="Popup" StaysOpen="False" >
                    <ContentPresenter Content="{TemplateBinding Content}" />
                </Popup>
            </Grid>
        </ControlTemplate>
    </Window.Resources>

    <HeaderedContentControl x:Name="MainControl" Template="{StaticResource ControlTemplate}" Header="Show Popup" >
        <HeaderedContentControl.Content>
            <Border Background="White" BorderThickness="1" BorderBrush="Black" >
                <Button Margin="2" Content="Hide All" >
                    <Button.Triggers>
                        <EventTrigger RoutedEvent="Button.Click" >
                            <BeginStoryboard>
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MainControl"
                                        Storyboard.TargetProperty="(UIElement.Visibility)" >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </Button.Triggers>
                </Button>
            </Border>
        </HeaderedContentControl.Content>
    </HeaderedContentControl>
</Window>

在我的真实代码中,ControlTemplate 与控件的用法不在同一位置。它完全在其他地方的 ResourceDictionary 中,并且不应该知道它的弹出窗口的内容 - 它直接将它传递到 ContentPresenter.

问题

有没有办法,最好不让 ControlTemplate 知道弹出窗口包含 "Hide" 按钮的可能性,当 HeaderedContentControl 折叠时弹出窗口消失?

我试过的

这里的根本问题是您使用 Storyboard 来控制弹出状态。这意味着当 Click 事件发生时,您告诉 WPF 开始一个动画 ,它在其中设置弹出窗口的 IsOpen 属性至 true.

请注意,动画没有固定的持续时间,因此实际上永远不会停止。您尝试将 Popup.IsOpen 设置为 false 失败,因为 WPF 仍在为 属性 设置动画,因此立即将其设置回 `true.

作为概念证明,尝试在启动动画后立即暂停它:

<EventTrigger RoutedEvent="Button.Click" >
  <BeginStoryboard Name="ShowPopupStoryBoard">
    <Storyboard>
      <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup"
                            Storyboard.TargetProperty="(Popup.IsOpen)" >
        <DiscreteBooleanKeyFrame KeyTime="0" Value="True" />
      </BooleanAnimationUsingKeyFrames>
    </Storyboard>
  </BeginStoryboard>
  <PauseStoryboard BeginStoryboardName="ShowPopupStoryBoard"/>
</EventTrigger>

通过暂停动画,可以防止 WPF 重置 Popup.IsOpen 属性。因此,您可以继续对 IsVisibleChanged(或其他适当的事件)做出反应,设置 Popup.IsOpen 属性 false,并使其实际产生效果。

不幸的是,这只会暂停动画。并且在重复测试中,上面的内容并不能始终如一地工作——也就是说,它总是在第一次点击时工作,但在随后的点击中(我更改了演示代码以不隐藏按钮),有时会,有时不会——我认为这是关于何时调用各种 EventTrigger 子级的时间问题。

无论如何,Storyboard 似乎是一个重量级的,并且至少在某些明显的方面,显示弹出窗口的机制不合适。我知道将始终如一地工作的替代方法是简单地向模板的 Button 控件的 Click 事件添加一个事件处理程序,并将 Popup.IsOpen 属性 设置为 true 那里。在这种情况下,您只需切换它,而不是对其进行动画处理,因此 WPF 不会做任何事情来试图强制它保持设置为 true 稍后当您想要将它设置回 false.

但最主要的是在可用的机制中选择一些 机制以确保Popup.IsOpen 属性 不会持续进行动画处理。只要 WPF 将 属性 动画化为 true 值,将其设置为 false 就不会产生任何效果(实际上,弹出窗口完全消失有点令人惊讶,即使它失去了焦点……我想失去焦点的优先级高于 IsOpen 属性 值)。

不幸的是,因为这里的主要问题与模板的当前实现有关,我认为与您声明的偏好相反,任何正确的修复都需要您修改模板。我想您可以在弹出窗口的内容对象中添加逻辑来追踪它的故事板并实际停止它。但在那种情况下,这似乎比疾病本身更糟糕。