Comment ouvrir une fenêtre contextuelle WPF lorsqu'un autre contrôle est cliqué, en utilisant uniquement le balisage XAML?

J'ai deux contrôles, un TextBlock et un PopUp. Lorsque l'utilisateur clique (MouseDown) sur le textblock, je veux afficher la fenêtre contextuelle. Je pense que je pourrais le faire avec un EventTrigger sur le Popup, mais je ne peux pas utiliser de setters dans un EventTrigger, Je ne peux que commencer des storyboards. Je veux le faire strictement en XAML, car les deux contrôles sont dans un modèle et je ne sais pas comment je trouverais le popup dans le code.

C'est ce que conceptuellement je veux faire, mais ne peut pas parce que vous ne pouvez pas mettre un setter dans un EventTrigger (comme vous pouvez le faire avec un 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>
...

Quelle est la meilleure façon d'afficher une fenêtre contextuelle strictement en XAML lorsqu'un événement se produit sur un contrôle différent?

54
demandé sur Drew Noakes 2008-12-12 01:19:11

5 réponses

J'ai fait quelque chose de simple, mais ça marche.

J'ai utilisé un ToggleButton typique, que j'ai restylé en tant que textblock en changeant son modèle de contrôle. Ensuite, je viens de lier la propriété IsChecked sur le ToggleButton à la propriété IsOpen sur le popup. Popup a des propriétés comme StaysOpen qui vous permettent de modifier le comportement de fermeture.

Ce qui suit fonctionne dans 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>
79
répondu John Melville 2015-04-19 17:54:50

L'approche suivante est la même que celle de Helge Klein, sauf que le popup se ferme automatiquement lorsque vous cliquez n'importe où en dehors du Popup (y compris le ToggleButton lui-même):

<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" est utilisé dans la liaison IsHitTestVisible de sorte que lorsque vous cliquez à nouveau sur le bouton ToggleButton, le popup se ferme:

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);
    }
}

...ce qui montre la technique pratique de combinant IValueConverter et MarkupExtension en un seul.

J'ai découvert un problème avec ceci technique: WPF est bogué lorsque deux popups sont à l'écran en même temps. Plus précisément, si votre bouton à bascule est sur le "popup overflow" dans une barre d'outils, alors il y aura deux popups ouverts après avoir cliqué dessus. Vous pouvez alors constater que la deuxième fenêtre contextuelle (votre fenêtre contextuelle) restera ouverte lorsque vous cliquez ailleurs sur votre fenêtre. À ce stade, la fermeture de la fenêtre contextuelle est difficile. L'utilisateur ne peut pas cliquer à nouveau sur le bouton ToggleButton pour fermer la fenêtre contextuelle car IsHitTestVisible est false car la fenêtre contextuelle est ouverte! Dans mon app j'ai dû utiliser quelques hacks pour atténuer ce problème, comme le test suivant sur la fenêtre principale, qui dit (dans la voix de Louis Black) "si le popup est ouvert et que l'utilisateur clique quelque part en dehors du popup, fermez le putain de popup.":

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;
    }
};
50
répondu Qwertie 2012-10-31 15:53:23

J'ai eu quelques problèmes avec la partie MouseDown de ceci, mais voici un code qui pourrait vous aider à démarrer.

<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>
8
répondu bendewey 2008-12-11 22:49:10

Que diriez-vous de:

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

Veuillez noter que le Popup renvoie le Button par son nom et vice versa. Donc {[3] } est requis sur les deux, le Popup et le Button.

Il peut en fait être encore simplifié en remplaçant le truc Storyboard par une action SetProperty EventTrigger personnalisée décrite dans cette réponse SO

8
répondu BatteryBackupUnit 2017-05-23 11:54:15

Une autre façon de le faire:

<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>
0
répondu Mike 2014-05-01 09:08:49