如何在单击另一个控件时打开 WPF 弹出窗口,仅使用 XAML 标记?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/361209/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:52:33  来源:igfitidea点击:

How to open a WPF Popup when another control is clicked, using XAML markup only?

wpfxamlpopupeventtrigger

提问by viggity

I've got two controls, a TextBlock and a PopUp. When the user clicks (MouseDown) on the textblock, I want to display the popup. I would think that I could do this with an EventTrigger on the Popup, but I can't use setters in an EventTrigger, I can only start storyboards. I want to do this strictly in XAML, because the two controls are in a template and I don't know how I'd find the popup in code.

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

This is what conceptually I want to do, but can't because you can't put a setter in an EventTrigger (like you can with a DataTrigger):

这就是我在概念上想要做的,但不能,因为你不能在 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>
...

What is the best way to show a popup strictly in XAML when an event happens on a different control?

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

回答by John Melville

I did something simple, but it works.

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

I used a typical ToggleButton, which I restyled as a textblock by changing its control template. Then I just bound the IsChecked property on the ToggleButton to the IsOpen property on the popup. Popup has some properties like StaysOpen that let you modify the closing behavior.

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

The following works in XamlPad.

以下在 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>

回答by Qwertie

The following approach is the same as Helge Klein's, except that the popup closes automatically when you click anywhere outside the Popup (including the ToggleButton itself):

以下方法与 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" is used in the IsHitTestVisible binding so that when you click the ToggleButton again, the popup closes:

IsHitTestVisible 绑定中使用了“BoolInverter”,因此当您再次单击 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);
    }
}

...which shows the handy technique of combining IValueConverter and MarkupExtensionin one.

...它展示了将 IValueConverter 和 MarkupExtension 合二为一的便捷技术。

I did discover one problem with this technique: WPF is buggy when two popups are on the screen at the same time. Specifically, if your toggle button is on the "overflow popup" in a toolbar, then there will be two popups open after you click it. You may then find that the second popup (your popup) will stay open when you click anywhere else on your window. At that point, closing the popup is difficult. The user cannot click the ToggleButton again to close the popup because IsHitTestVisible is false because the popup is open! In my app I had to use a few hacks to mitigate this problem, such as the following test on the main window, which says (in the voice of Louis Black) "if the popup is open and the user clicks somewhere outside the popup, close the friggin' popup.":

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

PreviewMouseDown += (s, e) =>
{
    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;

回答by BatteryBackupUnit

The following uses EventTriggerto show the Popup. This means we don't need a ToggleButtonfor state binding. In this example the Clickevent of a Buttonis used. You can adapt it to use another element/event combination.

以下用于EventTrigger显示Popup. 这意味着我们不需要ToggleButton状态绑定。在这个例子中,使用Click了 a的事件Button。您可以调整它以使用另一个元素/事件组合。

<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>

Please note that the Popupis referencing the Buttonby name and vice versa. So x:Name="..."is required on both, the Popupand the Button.

请注意,Popup是指Button按名称,反之亦然。因此,x:Name="..."需要在两者的PopupButton

It can actually be further simplified by replacing the Storyboardstuff with a custom SetPropertyEventTrigger Action described in this SO Answer

实际上可以通过Storyboard使用此 SO Answer 中SetProperty描述的自定义EventTrigger Action替换这些内容来进一步简化

回答by bendewey

I had some issues with the MouseDown part of this, but here is some code that might get your started.

我在使用 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>

回答by Mike

another way to do it:

另一种方法:

<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>