Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I move a WPF Popup when its anchor element moves?

Tags:

binding

wpf

popup

I have a Popup defined like this:

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

I have added event handlers to the myPopupAnchor element for the events MouseEnter and MouseLeave. The two event handlers toggle the popup's visibility.

My problem is the position of myPopupAnchor is only read when the popup is first shown, or hidden and then shown again. If the anchor moves, the popup does not.

I'm looking for ways to work around this, I want a moving Popup. Can I notify WPF that the PlacementTarget binding has changed and should be read again? Can I manually set the popup's position?

Currently, I have a very crude workaround that involves closing and then opening the popup again, which causes some repainting issues.

like image 478
Mizipzor Avatar asked Oct 21 '09 11:10

Mizipzor


2 Answers

I looked at a couple options and samples out there. The thing that seems to work best for me is to "bump" one of the properties that causes the Popup to reposition itself on its own. The property that I used is HorizontalOffset.

I set it to (itself + 1) and then set it back the original value. I do this in an event handler that runs when the window is repositioned.

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

When the window is moved, the popup will reposition. The subtle change in the HorizontalOffset is not noticed because the window and popup are already moving anyway.

I'm still evaluating whether a popup control is the best option in cases where the control stays open during other interaction. I'm thinking that Ray Burns suggestion to put this stuff in an Adorner layer seems like a good approach for some scenarios.

like image 50
NathanAW Avatar answered Sep 19 '22 13:09

NathanAW


Just to add on to NathanAW's great solution above, I thought I'd point out some context, such as where to place the C# code in this case. I'm still pretty new to WPF so I struggled at first to figure out where to put NathanAW's code. When I tried putting that code in the constructor for the UserControl that hosted my Popup, Window.GetWindow() always returned Null (so the "bump" code never executed). So I thought that other newbies might benefit from seeing things in context.

Before showing the C# in context, here's some example XAML context to show some relevant elements and their names:

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

Then in the code-behind, to avoid having Window.GetWindow() return Null, wire up a handler to the Loaded event to house NathanAW's code (see Peter Walke's comment on a similar stackoverflow discussion for example). Here's exactly how it all looked in my UserControl code-behind:

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;             };         }     } } 
like image 26
Jason Frank Avatar answered Sep 19 '22 13:09

Jason Frank