¿Cómo puedo mover una ventana emergente de WPF cuando se mueve su elemento de anclaje?

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

  •  05-07-2019
  •  | 
  •  

Pregunta

Tengo una ventana emergente definida como esta:

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

He agregado controladores de eventos al elemento myPopupAnchor para los eventos MouseEnter y MouseLeave . Los dos controladores de eventos alternan la visibilidad del elemento emergente.

Mi problema es que la posición de myPopupAnchor solo se lee cuando la ventana emergente se muestra por primera vez, se oculta y luego se vuelve a mostrar. Si el ancla se mueve, la ventana emergente no.

Estoy buscando formas de solucionar esto, quiero una ventana emergente en movimiento. ¿Puedo notificar a WPF que el enlace PlacementTarget ha cambiado y debería leerse de nuevo? ¿Puedo configurar manualmente la posición del menú emergente?

Actualmente, tengo una solución muy cruda que implica cerrar y luego abrir la ventana emergente nuevamente, lo que causa algunos problemas de repintado.

¿Fue útil?

Solución

Miré un par de opciones y muestras por ahí. Lo que parece funcionar mejor para mí es " bump " una de las propiedades que hace que el elemento emergente se reposicione solo. La propiedad que utilicé es HorizontalOffset.

Lo puse en (sí mismo + 1) y luego lo puse de nuevo en el valor original. Hago esto en un controlador de eventos que se ejecuta cuando la ventana se reposiciona.

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

Cuando se mueve la ventana, la ventana emergente se reposicionará. El cambio sutil en HorizontalOffset no se nota porque la ventana y la ventana emergente ya se están moviendo de todos modos.

Todavía estoy evaluando si un control emergente es la mejor opción en los casos en que el control permanece abierto durante otra interacción. Estoy pensando que Ray Burns sugiere poner esto en una La capa Adorner parece ser un buen enfoque para algunos escenarios.

Otros consejos

Solo para agregar a la gran solución de NathanAW anterior, pensé que señalaría algún contexto , como donde para colocar el código C # en este caso. Todavía soy bastante nuevo en WPF, así que tuve problemas al principio para averiguar dónde poner el código de NathanAW. Cuando traté de poner ese código en el constructor para el UserControl que alojaba mi ventana emergente, Window.GetWindow () siempre devolvía Null (por lo que el código '' bump " nunca se ejecutó) . Así que pensé que otros novatos podrían beneficiarse al ver las cosas en contexto.

Antes de mostrar C # en contexto, aquí hay un ejemplo de contexto XAML para mostrar algunos elementos relevantes y sus nombres:

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

Luego, en el código subyacente, para evitar que Window.GetWindow () devuelva Null , conecte un controlador al evento Cargado para alojar el código de NathanAW (consulte < a href = "https://stackoverflow.com/a/304604/718325"> comentario de Peter Walke sobre una discusión similar de stackoverflow, por ejemplo). Así es exactamente como se veía todo en mi código subyacente de 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 desea mover la ventana emergente, hay un truco sencillo: cambie su posición, luego establezca:

IsOpen = false;
IsOpen = true;

Para agregar a la respuesta de Jason Frank, el enfoque Window.GetWindow () no funcionaría si el WPF UserControl está alojado en un WinForms ElementHost. Lo que necesitaba encontrar era el ScrollViewer en el que estaba colocado mi UserControl, ya que ese era el elemento que mostraba las barras de desplazamiento.

Este método recursivo genérico (modificado de otra respuesta) ayudará a encontrar el padre de un tipo particular en el árbol lógico (también es posible usar el árbol visual), y lo devolverá si se encuentra.

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

Llame a este método auxiliar en lugar de Window.GetWindow () y continúe con la respuesta de Jason de suscribirse a los eventos correctos. En el caso de ScrollViewer, es el evento ScrollChanged en su lugar.

Modifiqué el código de Jason, porque la ventana emergente ya está en primer plano si la ventana no está activada. ¿Hay alguna opción en la clase emergente o mi solución está bien?

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

No puedes hacer esto. Cuando se muestra Popup en la pantalla, no se reposiciona si su padre se reposiciona. Ese es el comportamiento del control emergente. compruebe esto: http://msdn.microsoft .com / en-us / library / system.windows.controls.primitives.popup.aspx

puede usar una ventana (con WindowStyle = ninguna) en lugar de una ventana emergente que puede resolver su problema.

Descargue el ejemplo de la posición emergente emergente en:

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

El ejemplo de código utiliza la clase CustomPopupPlacement con un objeto Rect, y se enlaza con desplazamientos horizontales y verticales para mover el elemento emergente.

<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}"
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top