【问题标题】:How to open a WPF Popup when another control is clicked, using XAML markup only?单击另一个控件时如何仅使用 XAML 标记打开 WPF 弹出窗口?
【发布时间】:2008-12-11 22:19:11
【问题描述】:

我有两个控件,一个 TextBlock 和一个 PopUp。当用户在文本块上单击 (MouseDown) 时,我想显示弹出窗口。我认为我可以使用弹出窗口上的 EventTrigger 来做到这一点,但我不能在 EventTrigger 中使用设置器,我只能启动故事板。我想在 XAML 中严格执行此操作,因为这两个控件位于一个模板中,我不知道如何在代码中找到弹出窗口。

这在概念上是我想做的,但不能,因为你不能在 EventTrigger 中放置一个 setter(就像你可以使用 DataTrigger 一样):

<TextBlock x:Name="CCD">Some text</TextBlock>

<Popup>
    <Popup.Style>
        <Style>
            <Style.Triggers>
                <EventTrigger SourceName="CCD" RoutedEvent="MouseDown">
                    <Setter Property="Popup.IsOpen" Value="True" />
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Popup.Style>
...

当事件发生在不同的控件上时,在 XAML 中严格显示弹出窗口的最佳方式是什么?

【问题讨论】:

    标签: wpf xaml popup eventtrigger


    【解决方案1】:

    我做了一些简单的事情,但它确实有效。

    我使用了一个典型的 ToggleButton,通过更改其控件模板将其重新设置为文本块。然后我只是将 ToggleButton 上的 IsChecked 属性绑定到弹出窗口上的 IsOpen 属性。 Popup 有一些属性,如 StaysOpen,可让您修改关闭行为。

    以下工作在 XamlPad 中。

     <StackPanel>
      <ToggleButton Name="button"> 
        <ToggleButton.Template>
          <ControlTemplate TargetType="ToggleButton">
            <TextBlock>Click Me Here!!</TextBlock>
          </ControlTemplate>      
        </ToggleButton.Template>
      </ToggleButton>
      <Popup IsOpen="{Binding IsChecked, ElementName=button}" StaysOpen="False">
        <Border Background="LightYellow">
          <TextBlock>I'm the popup</TextBlock>
        </Border>
      </Popup> 
     </StackPanel>
    

    【讨论】:

    • 另请参阅@Qwertie 的方法 - 受此启发的更有用的版本,当您使用 alt-tab 或在弹出窗口外部单击时会自动关闭弹出窗口
    【解决方案2】:

    以下方法与 Helge Klein 的方法相同,只是当您单击 Popup 外部的任意位置(包括 ToggleButton 本身)时,弹出窗口会自动关闭:

    <ToggleButton x:Name="Btn" IsHitTestVisible="{Binding ElementName=Popup, Path=IsOpen, Mode=OneWay, Converter={local:BoolInverter}}">
        <TextBlock Text="Click here for popup!"/>
    </ToggleButton>
    
    <Popup IsOpen="{Binding IsChecked, ElementName=Btn}" x:Name="Popup" StaysOpen="False">
        <Border BorderBrush="Black" BorderThickness="1" Background="LightYellow">
            <CheckBox Content="This is a popup"/>
        </Border>
    </Popup>
    

    “BoolInverter”用于 IsHitTestVisible 绑定,因此当您再次单击 ToggleButton 时,弹出窗口关闭:

    public class BoolInverter : MarkupExtension, IValueConverter
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool)
                return !(bool)value;
            return value;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(value, targetType, parameter, culture);
        }
    }
    

    ...将combining IValueConverter and MarkupExtension 的便捷技术合二为一。

    我确实发现了这种技术的一个问题:当屏幕上同时出现两个弹出窗口时,WPF 会出现问题。具体来说,如果您的切换按钮位于工具栏中的“溢出弹出窗口”上,则单击它后会打开两个弹出窗口。然后,您可能会发现,当您单击窗口上的其他任何位置时,第二个弹出窗口(您的弹出窗口)将保持打开状态。那时,关闭弹出窗口很困难。用户无法再次单击 ToggleButton 以关闭弹出窗口,因为 IsHitTestVisible 为 false,因为弹出窗口已打开!在我的应用程序中,我不得不使用一些技巧来缓解这个问题,例如主窗口上的以下测试,它说(以 Louis Black 的声音)“如果弹出窗口打开并且用户点击弹出窗口之外的某个位置,关闭该死的弹出窗口。”:

    PreviewMouseDown += (s, e) =>
    {
        // Workaround for popup not closing automatically when 
        // two popups are on-screen at once.
        if (Popup.IsOpen)
        {
            Point p = e.GetPosition(Popup.Child);
            if (!IsInRange(p.X, 0, ((FrameworkElement)Popup.Child).ActualWidth) ||
                !IsInRange(p.Y, 0, ((FrameworkElement)Popup.Child).ActualHeight))
                Popup.IsOpen = false;
        }
    };
    // Elsewhere...
    public static bool IsInRange(int num, int lo, int hi) => 
        num >= lo && num <= hi;
    

    【讨论】:

    • 这很有效 - 包括 alt-tabbing 到另一个应用程序或单击窗口中的其他位置
    • StaysOpen="false" 是组件外自动关闭行为的关键。为什么哦为什么默认为true??
    • @ChrisDolan 我对默认值之类的事情感到非常恼火,这些事情基本上违背了我所知道的所有 UX 原则。所以,我为Popup 做了一个隐式样式,并设置了我喜欢的默认值,包括StaysOpen=False
    • @Qwertie 这很酷,但是当我与弹出窗口中的元素交互时,这导致弹出窗口关闭。 I set up a question,如果你碰巧知道我做错了什么。
    • 我们能看到 IsInRange() 方法吗?
    【解决方案3】:

    以下使用EventTrigger 表示Popup。这意味着我们不需要ToggleButton 来进行状态绑定。 在此示例中,使用了 ButtonClick 事件。您可以调整它以使用其他元素/事件组合。

    <Button x:Name="OpenPopup">Popup
        <Button.Triggers>
            <EventTrigger RoutedEvent="Button.Click">
                <EventTrigger.Actions>
                    <BeginStoryboard>
                        <Storyboard>
                            <BooleanAnimationUsingKeyFrames 
                                     Storyboard.TargetName="ContextPopup" 
                                     Storyboard.TargetProperty="IsOpen">
                                <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
                            </BooleanAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>
        </Button.Triggers>
    </Button>
    <Popup x:Name="ContextPopup"
           PlacementTarget="{Binding ElementName=OpenPopup}"
           StaysOpen="False">
        <Label>Popupcontent...</Label>
    </Popup>
    

    请注意Popup 按名称引用Button,反之亦然。所以x:Name="..." 需要PopupButton

    实际上可以通过将Storyboard 替换为SetProperty EventTrigger Action 描述的自定义in this SO Answer 来进一步简化

    【讨论】:

    • 如果有人想在模板中使用它,例如DataGridTemplateColumnStoryboard.TargetName 将无法找到Popup。请改用Storyboard.Target
    【解决方案4】:

    我在其中的 MouseDown 部分遇到了一些问题,但这里有一些代码可以帮助您入门。

    <Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <Control VerticalAlignment="Top">
                <Control.Template>
                    <ControlTemplate>
                        <StackPanel>
                        <TextBox x:Name="MyText"></TextBox>
                        <Popup x:Name="Popup" PopupAnimation="Fade" VerticalAlignment="Top">
                            <Border Background="Red">
                                <TextBlock>Test Popup Content</TextBlock>
                            </Border>
                        </Popup>
                        </StackPanel>
                        <ControlTemplate.Triggers>
                            <EventTrigger RoutedEvent="UIElement.MouseEnter" SourceName="MyText">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)">
                                            <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
                                        </BooleanAnimationUsingKeyFrames>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                            <EventTrigger RoutedEvent="UIElement.MouseLeave" SourceName="MyText">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <BooleanAnimationUsingKeyFrames Storyboard.TargetName="Popup" Storyboard.TargetProperty="(Popup.IsOpen)">
                                            <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"/>
                                        </BooleanAnimationUsingKeyFrames>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Control.Template>
            </Control>
        </Grid>
    </Window>
    

    【讨论】:

      【解决方案5】:

      另一种方法:

      <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                          <StackPanel>
                              <Image Source="{Binding ProductImage,RelativeSource={RelativeSource TemplatedParent}}" Stretch="Fill" Width="65" Height="85"/>
                              <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                              <Button x:Name="myButton" Width="40" Height="10">
                                  <Popup Width="100" Height="70" IsOpen="{Binding ElementName=myButton,Path=IsMouseOver, Mode=OneWay}">
                                      <StackPanel Background="Yellow">
                                          <ItemsControl ItemsSource="{Binding Produkt.SubProducts}"/>
                                      </StackPanel>
                                  </Popup>
                              </Button>
                          </StackPanel>
                      </Border>
      

      【讨论】:

        猜你喜欢
        • 2020-06-03
        • 1970-01-01
        • 2022-07-11
        • 2018-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多