web-dev-qa-db-fra.com

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 bloc de texte, je souhaite 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 réglages dans un EventTrigger, je ne peux que démarrer des story-boards. Je veux faire cela strictement en XAML, parce que 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 je veux faire conceptuellement, mais je ne le peux pas car vous ne pouvez pas mettre de setter dans un EventTrigger (comme vous le pouvez 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>
...

Quel est le meilleur moyen d'afficher un popup strictement en XAML lorsqu'un événement se produit sur un contrôle différent?

55
viggity

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

J'ai utilisé un ToggleButton typique, que j'ai redéfini comme un bloc de texte en modifiant son modèle de contrôle. Ensuite, je viens de lier la propriété IsChecked du ToggleButton à la propriété IsOpen de la fenêtre contextuelle. 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>
80
John Melville

L'approche suivante est la même que celle de Helge Klein, à la différence près que la fenêtre se ferme automatiquement lorsque vous cliquez n'importe où en dehors de la fenêtre (incluant le bouton de bascule 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 Toggle, la fenêtre contextuelle 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);
    }
}

... qui montre la technique pratique de combinant IValueConverter et MarkupExtension in one.

J'ai découvert un problème avec cette technique: WPF est bogué lorsque deux fenêtres contextuelles apparaissent à l'écran en même temps. Plus précisément, si votre bouton bascule se trouve dans la "fenêtre contextuelle de débordement" dans une barre d’outils, deux fenêtres contextuelles s’ouvriront lorsque vous cliquez dessus. Vous pouvez alors constater que la deuxième fenêtre contextuelle (votre fenêtre contextuelle) restera ouverte lorsque vous cliquerez ailleurs sur votre fenêtre. À ce stade, il est difficile de fermer la fenêtre contextuelle. L'utilisateur ne peut pas cliquer à nouveau sur le bouton Toggle pour fermer la fenêtre contextuelle car IsHitTestVisible est false car la fenêtre contextuelle est ouverte! Dans mon application, j'ai dû utiliser quelques hacks pour résoudre ce problème, comme le test suivant sur la fenêtre principale, qui indique (dans la voix de Louis Black) "si la fenêtre contextuelle est ouverte et que l'utilisateur clique quelque part en dehors de celle-ci, fermez le friggin '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
Qwertie

J'ai eu quelques problèmes avec la partie MouseDown de cela, 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
bendewey

Que diriez-vous:

<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 la Popup fait référence à la Button par son nom et vice versa. Donc, x:Name="..." est requis pour les deux variables, Popup et Button.

Il peut en réalité être simplifié davantage en remplaçant le paramètre Storyboard par une action personnalisée SetProperty EventTrigger décrite dans ce SO Réponse

8
BatteryBackupUnit

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
Mike