Подтвердить что ты не робот

Как открыть всплывающее окно WPF при нажатии другого элемента управления, используя только разметку XAML?

У меня есть два элемента управления: TextBlock и PopUp. Когда пользователь нажимает (MouseDown) на текстовом блоке, я хочу отобразить всплывающее окно. Я бы подумал, что могу сделать это с помощью EventTrigger в Popup, но я не могу использовать сеттеры в EventTrigger, я могу только начинать раскадровки. Я хочу сделать это строго в XAML, потому что два элемента управления находятся в шаблоне, и я не знаю, как найти всплывающее окно в коде.

Это то, что концептуально я хочу делать, но не могу, потому что вы не можете поместить setter в EventTrigger (как вы можете с помощью 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, когда событие происходит с другим элементом управления?

4b9b3361

Ответ 1

Я сделал что-то простое, но оно работает.

Я использовал типичный ToggleButton, который я рестайлинг как текстовый блок, изменив его шаблон управления. Затем я просто привязал свойство IsChecked к ToggleButton к свойству 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>

Ответ 2

Следующий подход такой же, как у Хельге Кляйна, за исключением того, что всплывающее окно закрывается автоматически, когда вы щелкаете в любом месте за пределами всплывающего окна (включая сам 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);
    }
}

... который показывает удобную технику объединения IValueConverter и MarkupExtension в одном.

Я обнаружил одну проблему с этой техникой: WPF глючит, когда на экране одновременно появляются два всплывающих окна. В частности, если ваша кнопка переключения находится в "всплывающем окне переполнения" на панели инструментов, то после нажатия на нее откроется два всплывающих окна. Затем вы можете обнаружить, что второе всплывающее окно (ваше всплывающее окно) останется открытым, когда вы щелкнете в любом другом месте своего окна. В этот момент закрыть всплывающее окно сложно. Пользователь не может щелкнуть ToggleButton снова, чтобы закрыть всплывающее окно, потому что IsHitTestVisible имеет значение false, потому что всплывающее окно открыто! В моем приложении мне пришлось использовать несколько хаков, чтобы смягчить эту проблему, например, следующий тест в главном окне, который говорит (голосом Луи Блэка) "если всплывающее окно открыто и пользователь щелкает где-то вне всплывающего окна, закройте чертово всплывающее окно. "

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;

Ответ 3

Как насчет:

<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="..." требуется для обоих: Popup и Button.

На самом деле его можно упростить, заменив материал Storyboard на пользовательское SetProperty событие EventTrigger Action, описанное в этом SO-ответе

Ответ 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>