【问题标题】:How to close a popup when its parent is collapsed?如何在其父项折叠时关闭弹出窗口?
【发布时间】:2015-04-27 15:52:11
【问题描述】:

背景

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

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

这里有一个简短的例子来演示这个问题:

<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 在折叠HeaderedContentControl 时使弹出窗口消失,最好不要让ControlTemplate 知道包含“隐藏”按钮的弹出窗口的可能性?

我尝试过的

  • HeaderedContentControl 添加另一个触发器以观察IsVisible 的变化。 没有合适的EventTrigger 并且其他触发器不能包含元素名称
  • HeaderedContentControl 的子类中向IsVisibleChanged 添加事件处理程序。 事件触发,但Popup.IsOpen 在设置为false 后仍为true

【问题讨论】:

标签: c# wpf popup


【解决方案1】:

这里的根本问题是您使用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 属性值)。

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

【讨论】:

  • 太棒了 - 我没有考虑到动画可能会导致问题。我意识到阅读您的答案后,我对不修改模板的限制有点过分。我的意思是我不想将任何特定于“隐藏”按钮的内容添加到模板中,但是添加一对事件处理程序以在单击时打开并在变得不可见时关闭是可以的。感谢您非常彻底的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-15
  • 1970-01-01
  • 1970-01-01
  • 2011-06-30
  • 1970-01-01
  • 2012-06-03
  • 2012-07-09
相关资源
最近更新 更多