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

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

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

Ответы [ 5 ]

82 голосов
/ 30 декабря 2008

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

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

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

Как насчет:

<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, описанное в этом ответе SO

8 голосов
/ 12 декабря 2008

У меня были некоторые проблемы с частью 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>
0 голосов
/ 01 мая 2014

другой способ сделать это:

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