Comment puis-je déplacer un pop-up WPF lorsque son élément d'ancrage se déplace?

StackOverflow https://stackoverflow.com/questions/1600218

  •  05-07-2019
  •  | 
  •  

Question

J'ai un Popup défini comme ceci:

<Popup
    Name="myPopup"
    StaysOpen="True"
    Placement="Bottom"
    PlacementRectangle="0,20,0,20"
    PlacementTarget="{Binding ElementName=myPopupAnchor}">
    <TextBlock ... />
</Popup>

J'ai ajouté des gestionnaires d'événements à l'élément myPopupAnchor pour les événements MouseEnter et MouseLeave . Les deux gestionnaires d'événements basculent la visibilité de la fenêtre contextuelle.

Mon problème est que la position de myPopupAnchor n'est lue que lorsque la fenêtre contextuelle est affichée pour la première fois, ou masquée puis affichée à nouveau. Si l'ancre se déplace, le popup ne le fait pas.

Je cherche des moyens de contourner ce problème, je veux un Popup en mouvement. Puis-je informer WPF que la liaison PlacementTarget a été modifiée et doit être relue? Puis-je définir manuellement la position de la popup?

Actuellement, j'ai une solution très grossière qui consiste à fermer puis à rouvrir la fenêtre contextuelle, ce qui entraîne certains problèmes de repeinte.

Était-ce utile?

La solution

J'ai regardé quelques options et échantillons. Ce qui me semble le mieux, c’est de "cogner". une des propriétés qui amène le Popup à se repositionner tout seul. La propriété que j'ai utilisée est HorizontalOffset.

Je le règle sur (lui-même + 1), puis je rétablis la valeur d'origine. Je le fais dans un gestionnaire d'événements qui s'exécute lorsque la fenêtre est repositionnée.

// Reference to the PlacementTarget.
DependencyObject myPopupPlacementTarget;

// Reference to the popup.
Popup myPopup; 

Window w = Window.GetWindow(myPopupPlacementTarget);
if (null != w)
{
    w.LocationChanged += delegate(object sender, EventArgs args)
    {
        var offset = myPopup.HorizontalOffset;
        myPopup.HorizontalOffset = offset + 1;
        myPopup.HorizontalOffset = offset;
    };
}

Lorsque la fenêtre est déplacée, la popup se repositionne. Le changement subtil dans HorizontalOffset n’est pas remarqué car la fenêtre et le popup se déplacent déjà de toute façon.

J'évalue toujours si un contrôle contextuel est la meilleure option dans les cas où le contrôle reste ouvert pendant une autre interaction. Je pense que la suggestion de Ray Burns de mettre ces éléments dans un La couche Adorner semble être une bonne approche pour certains scénarios.

Autres conseils

Juste pour ajouter la grande solution de NathanAW ci-dessus, je pensais que je signalerais un contexte , tel que pour placer le code C # dans ce cas. Comme je suis encore assez nouveau pour WPF, j'ai d'abord eu du mal à trouver où placer le code de NathanAW. Lorsque j'ai essayé de placer ce code dans le constructeur de UserControl qui hébergeait mon Popup, Window.GetWindow () renvoyait toujours Null (le code "bump" ne s'est donc jamais exécuté). . J'ai donc pensé que d'autres débutants pourraient bénéficier de voir les choses dans leur contexte.

Avant d'afficher le C # dans son contexte, voici un exemple de contexte XAML pour afficher certains éléments pertinents et leurs noms:

<UserControl x:Class="MyNamespace.View1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

    <TextBlock x:Name="popupTarget" />
    <Popup x:Name="myPopup"
           Placement="Bottom"
           PlacementTarget="{Binding ElementName=popupTarget}" >
         (popup content here)
    </Popup>
</UserControl>

Ensuite, dans le code-behind, pour éviter que Window.GetWindow () renvoie Null , connectez un gestionnaire à l'événement Loaded pour héberger le code de NathanAW (voir < a href = "https://stackoverflow.com/a/304604/718325"> Le commentaire de Peter Walke sur une discussion similaire à une pile de flux, par exemple). Voici exactement à quoi cela ressemblait dans mon code-behind UserControl:

public partial class View1 : UserControl
{
    // Constructor
    public View1()
    {
        InitializeComponent();

        // Window.GetWindow() will return Null if you try to call it here!             

        // Wire up the Loaded handler instead
        this.Loaded += new RoutedEventHandler(View1_Loaded);
    }

    /// Provides a way to "dock" the Popup control to the Window
    ///  so that the popup "sticks" to the window while the window is dragged around.
    void View1_Loaded(object sender, RoutedEventArgs e)
    {
        Window w = Window.GetWindow(popupTarget);
        // w should not be Null now!
        if (null != w)
        {
            w.LocationChanged += delegate(object sender2, EventArgs args)
            {
                var offset = myPopup.HorizontalOffset;
                // "bump" the offset to cause the popup to reposition itself
                //   on its own
                myPopup.HorizontalOffset = offset + 1;
                myPopup.HorizontalOffset = offset;
            };
            // Also handle the window being resized (so the popup's position stays
            //  relative to its target element if the target element moves upon 
            //  window resize)
            w.SizeChanged += delegate(object sender3, SizeChangedEventArgs e2)
            {
                var offset = myPopup.HorizontalOffset;
                myPopup.HorizontalOffset = offset + 1;
                myPopup.HorizontalOffset = offset;
            };
        }
    }
}
    private void ppValues_Opened(object sender, EventArgs e)
    {
        Window win = Window.GetWindow(YourControl);
        win.LocationChanged += new EventHandler(win_LocationChanged);            
    }
    void win_LocationChanged(object sender, EventArgs e)
    {
        if (YourPopup.IsOpen)
        {                
            var mi = typeof(Popup).GetMethod("UpdatePosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            mi.Invoke(YourPopup, null);
        }
    }

Si vous souhaitez déplacer la fenêtre contextuelle, il existe une astuce simple: changez sa position, puis définissez:

IsOpen = false;
IsOpen = true;

Pour ajouter à la réponse de Jason Frank, l'approche Window.GetWindow () ne fonctionnerait pas si le contrôle utilisateur WPF était hébergé dans un élément WinForms ElementHost. Il me fallait trouver le ScrollViewer dans lequel mon UserControl était placé, car c’était l’élément affichant les barres de défilement.

Cette méthode récursive générique (modifiée par une autre réponse) aidera à trouver le parent d'un type particulier dans l'arborescence logique (il est également possible d'utiliser l'arborescence visuelle), et à le renvoyer s'il est trouvé.

public static T FindLogicalParentOf<T>(DependencyObject child) where T: FrameworkElement
    {
        DependencyObject parent = LogicalTreeHelper.GetParent(child);

        //Top of the tree
        if (parent == null) return null;

        T parentWindow = parent as T;
        if (parentWindow != null)
        {
            return parentWindow;
        }

        //Climb a step up
        return FindLogicalParentOf<T>(parent);
    }

Appelez cette méthode d'assistance au lieu de Window.GetWindow () et continuez avec la réponse de Jason consistant à s'abonner aux événements appropriés. Dans le cas de ScrollViewer, il s'agit plutôt de l'événement ScrollChanged.

J'ai modifié le code de Jason, car la fenêtre contextuelle est déjà au premier plan si la fenêtre n'est pas activée. Existe-t-il une option dans la classe Popup ou i est ma solution ok?

private void FullLoaded(object sender, RoutedEventArgs e) {
Window CurrentWindow = Window.GetWindow(this.Popup);
if (CurrentWindow != null) {

    CurrentWindow.LocationChanged += (object innerSender, EventArgs innerArgs) => {
        this.RedrawPopup();
    };

    CurrentWindow.SizeChanged += (object innerSender, SizeChangedEventArgs innerArgs) => {
        this.RedrawPopup();
    };

    CurrentWindow.Activated += (object innerSender, EventArgs innerArgs) => {
        if (this.m_handleDeActivatedEvents && this.m_ShowOnActivated) {
            this.Popup.IsOpen = true;
            this.m_ShowOnActivated = false;
        }
    };

    CurrentWindow.Deactivated += (object innerSender, EventArgs innerArgs) => {
        if (this.m_handleDeActivatedEvents && this.Popup.IsOpen) {
            this.Popup.IsOpen = false;
            this.m_ShowOnActivated = true;
        }
    };

}
}

    private void RedrawPopup() {
        double Offset = this.Popup.HorizontalOffset;
        this.Popup.HorizontalOffset = Offset + 1;
        this.Popup.HorizontalOffset = Offset;
    }

Vous ne pouvez pas faire cela. Lorsque Popup est affiché à l'écran, il ne se repositionne pas si son parent est repositionné. C'est le comportement du contrôle Popup. vérifiez ceci: http://msdn.microsoft .com / fr-us / library / system.windows.controls.primitives.popup.aspx

vous pouvez utiliser une fenêtre (avec WindowStyle = None) au lieu de Popup qui pourrait résoudre votre problème.

Téléchargez l'exemple de position de fenêtre contextuelle à l'adresse suivante:

http://msdn.microsoft. com / fr-us / library / ms771558 (v = VS.90) .aspx

L'exemple de code utilise la classe CustomPopupPlacement avec un objet Rect et se lie aux décalages horizontaux et verticaux pour déplacer le menu contextuel.

<Popup Name="popup1" Placement="Bottom" AllowsTransparency="True"
       IsOpen="{Binding ElementName=popupOpen, Path=IsChecked}"
       HorizontalOffset="{Binding ElementName=HOffset, Path=Value, Mode=TwoWay}"
       VerticalOffset="{Binding ElementName=VOffset, Path=Value, Mode=TwoWay}"
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top