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

Tengo un Popup definido así:

   

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

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

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

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

Miré un par de opciones y muestras por ahí. Lo que parece funcionar mejor para mí es “golpear” una de las propiedades que hace que Popup se reposicione por sí mismo. La propiedad que utilicé es HorizontalOffset.

Lo ajusto a (sí mismo + 1) y luego lo ajuste al valor original. Lo hago 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 casos donde el control permanece abierto durante otra interacción. Estoy pensando que la sugerencia de Ray Burns de poner esto en una capa de Adorner parece ser un buen enfoque para algunos escenarios.

Solo para agregar a la gran solución anterior de NathanAW , pensé en señalar algún contexto , como dónde ubicar el código C # en este caso. Todavía soy bastante nuevo en WPF, así que tuve problemas al principio para descubrir dónde poner el código de NathanAW. Cuando traté de poner ese código en el constructor para el UserControl que hospedaba mi Window.GetWindow() 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 viendo las cosas en contexto.

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

    (popup content here)   

Luego, en el código subyacente, para evitar que Window.GetWindow() devuelva Null , conecte un controlador al evento Loaded para albergar el código de NathanAW (consulte el comentario de Peter Walke sobre una discusión similar de stackflow, por ejemplo). Aquí está exactamente cómo se veía todo en mi código de usuario detrás de código:

 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 simple: cambie su posición, luego configure:

 IsOpen = false; IsOpen = true; 

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

puede usar una Ventana (con WindowStyle = None) en lugar de Popup que puede resolver su problema.

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 Popup o si 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; } 

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

Este método recursivo genérico (modificado a partir 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 lo encuentra.

 public static T FindLogicalParentOf(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(parent); } 

Llame a este método de ayuda 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.

Descargue la muestra emergente de posición emergente en:

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

El ejemplo de código usa la clase CustomPopupPlacement con un objeto Rect y se une a los desplazamientos horizontales y verticales para mover el Popup.