1. 程式人生 > >解決 Popup 位置不隨窗口移動更新的問題

解決 Popup 位置不隨窗口移動更新的問題

class helper ima NPU back set 需求 eve top

Popup彈出後,因業務需求設置了StaysOpen=true後,移動窗口位置或者改變窗口大小,Popup的位置不會更新。

如何更新位置?

獲取當前Popup的Target綁定UserControl所在窗口,位置刷新時,時時更新Popup的位置即可。

1.添加一個附加屬性

1 /// <summary>
2 /// Popup位置更新
3 /// </summary>
4 public static readonly DependencyProperty PopupPlacementTargetProperty =
5     DependencyProperty.RegisterAttached("
PopupPlacementTarget", typeof(DependencyObject), typeof(PopupHelper), new PropertyMetadata(null, OnPopupPlacementTargetChanged));

2.窗口移動後觸發popup更新

首先,有個疑問,popup首次顯示時,為何顯示的位置是正確的呢?

通過查看源碼,發現,其實popup也是有內置更新popup位置的!

技術分享圖片

而通過查看UpdatePosition代碼,其方法確實是更新popup位置的。源碼如下:

技術分享圖片
  1 private void UpdatePosition()
  2
{ 3 if (this._popupRoot.Value == null) 4 return; 5 PlacementMode placement = this.Placement; 6 Point[] targetInterestPoints = this.GetPlacementTargetInterestPoints(placement); 7 Point[] childInterestPoints = this.GetChildInterestPoints(placement); 8 Rect bounds = this
.GetBounds(targetInterestPoints); 9 Rect rect1 = this.GetBounds(childInterestPoints); 10 double num1 = rect1.Width * rect1.Height; 11 int num2 = -1; 12 Vector offsetVector1 = new Vector((double)this._positionInfo.X, (double)this._positionInfo.Y); 13 double num3 = -1.0; 14 PopupPrimaryAxis popupPrimaryAxis = PopupPrimaryAxis.None; 15 CustomPopupPlacement[] customPopupPlacementArray = (CustomPopupPlacement[])null; 16 int num4; 17 if (placement == PlacementMode.Custom) 18 { 19 CustomPopupPlacementCallback placementCallback = this.CustomPopupPlacementCallback; 20 if (placementCallback != null) 21 customPopupPlacementArray = placementCallback(rect1.Size, bounds.Size, new Point(this.HorizontalOffset, this.VerticalOffset)); 22 num4 = customPopupPlacementArray == null ? 0 : customPopupPlacementArray.Length; 23 if (!this.IsOpen) 24 return; 25 } 26 else 27 num4 = Popup.GetNumberOfCombinations(placement); 28 for (int i = 0; i < num4; ++i) 29 { 30 bool flag1 = false; 31 bool flag2 = false; 32 Vector offsetVector2; 33 PopupPrimaryAxis axis; 34 if (placement == PlacementMode.Custom) 35 { 36 offsetVector2 = (Vector)targetInterestPoints[0] + (Vector)customPopupPlacementArray[i].Point; 37 axis = customPopupPlacementArray[i].PrimaryAxis; 38 } 39 else 40 { 41 Popup.PointCombination pointCombination = this.GetPointCombination(placement, i, out axis); 42 Popup.InterestPoint targetInterestPoint = pointCombination.TargetInterestPoint; 43 Popup.InterestPoint childInterestPoint = pointCombination.ChildInterestPoint; 44 offsetVector2 = targetInterestPoints[(int)targetInterestPoint] - childInterestPoints[(int)childInterestPoint]; 45 flag1 = childInterestPoint == Popup.InterestPoint.TopRight || childInterestPoint == Popup.InterestPoint.BottomRight; 46 flag2 = childInterestPoint == Popup.InterestPoint.BottomLeft || childInterestPoint == Popup.InterestPoint.BottomRight; 47 } 48 Rect rect2 = Rect.Offset(rect1, offsetVector2); 49 Rect rect3 = Rect.Intersect(this.GetScreenBounds(bounds, targetInterestPoints[0]), rect2); 50 double num5 = rect3 != Rect.Empty ? rect3.Width * rect3.Height : 0.0; 51 if (num5 - num3 > 0.01) 52 { 53 num2 = i; 54 offsetVector1 = offsetVector2; 55 num3 = num5; 56 popupPrimaryAxis = axis; 57 this.AnimateFromRight = flag1; 58 this.AnimateFromBottom = flag2; 59 if (Math.Abs(num5 - num1) < 0.01) 60 break; 61 } 62 } 63 if (num2 >= 2 && (placement == PlacementMode.Right || placement == PlacementMode.Left)) 64 this.DropOpposite = !this.DropOpposite; 65 rect1 = new Rect((Size)this._secHelper.GetTransformToDevice().Transform((Point)this._popupRoot.Value.RenderSize)); 66 rect1.Offset(offsetVector1); 67 Rect screenBounds = this.GetScreenBounds(bounds, targetInterestPoints[0]); 68 Rect rect4 = Rect.Intersect(screenBounds, rect1); 69 if (Math.Abs(rect4.Width - rect1.Width) > 0.01 || Math.Abs(rect4.Height - rect1.Height) > 0.01) 70 { 71 Point point1 = targetInterestPoints[0]; 72 Vector vector1 = targetInterestPoints[1] - point1; 73 vector1.Normalize(); 74 if (!this.IsTransparent || double.IsNaN(vector1.Y) || Math.Abs(vector1.Y) < 0.01) 75 { 76 if (rect1.Right > screenBounds.Right) 77 offsetVector1.X = screenBounds.Right - rect1.Width; 78 else if (rect1.Left < screenBounds.Left) 79 offsetVector1.X = screenBounds.Left; 80 } 81 else if (this.IsTransparent && Math.Abs(vector1.X) < 0.01) 82 { 83 if (rect1.Bottom > screenBounds.Bottom) 84 offsetVector1.Y = screenBounds.Bottom - rect1.Height; 85 else if (rect1.Top < screenBounds.Top) 86 offsetVector1.Y = screenBounds.Top; 87 } 88 Point point2 = targetInterestPoints[2]; 89 Vector vector2 = point1 - point2; 90 vector2.Normalize(); 91 if (!this.IsTransparent || double.IsNaN(vector2.X) || Math.Abs(vector2.X) < 0.01) 92 { 93 if (rect1.Bottom > screenBounds.Bottom) 94 offsetVector1.Y = screenBounds.Bottom - rect1.Height; 95 else if (rect1.Top < screenBounds.Top) 96 offsetVector1.Y = screenBounds.Top; 97 } 98 else if (this.IsTransparent && Math.Abs(vector2.Y) < 0.01) 99 { 100 if (rect1.Right > screenBounds.Right) 101 offsetVector1.X = screenBounds.Right - rect1.Width; 102 else if (rect1.Left < screenBounds.Left) 103 offsetVector1.X = screenBounds.Left; 104 } 105 } 106 int x = DoubleUtil.DoubleToInt(offsetVector1.X); 107 int y = DoubleUtil.DoubleToInt(offsetVector1.Y); 108 if (x == this._positionInfo.X && y == this._positionInfo.Y) 109 return; 110 this._positionInfo.X = x; 111 this._positionInfo.Y = y; 112 this._secHelper.SetPopupPos(true, x, y, false, 0, 0); 113 }
View Code

那麽,我們有什麽辦法調用這個私有方法呢?我相信大家都想,找到popup源碼開發者,爆了他Y的!

有一種方法,叫反射,反射可以獲取類的任一個字段或者屬性。

反射,可以參考:https://www.cnblogs.com/vaevvaev/p/6995639.html

通過反射,我們獲取到UpdatePosition方法,並調用執行。

1 var mi = typeof(Popup).GetMethod("UpdatePosition", BindingFlags.NonPublic | BindingFlags.Instance);
2 mi.Invoke(pop, null);

下面是詳細的屬性更改事件實現:

 1 private static void OnPopupPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 2 {
 3     Popup pop = d as Popup;
 4 
 5     //舊值取消LocationChanged監聽
 6     if (e.OldValue is DependencyObject previousPlacementTarget)
 7     {
 8         Window window = Window.GetWindow(previousPlacementTarget);
 9         if (window != null)
10         {
11             window.LocationChanged -= WindowLocationChanged;
12         }
13     }
14 
15     //新值添加LocationChanged監聽
16     if (e.NewValue is DependencyObject newPlacementTarget)
17     {
18         Window window = Window.GetWindow(newPlacementTarget);
19         if (window != null)
20         {
21             window.LocationChanged -= WindowLocationChanged;
22             window.LocationChanged += WindowLocationChanged;
23         }
24     }
25     void WindowLocationChanged(object s1, EventArgs e1)
26     {
27         if (pop != null && pop.IsOpen)
28         {
29             //通知更新相對位置
30             var mi = typeof(Popup).GetMethod("UpdatePosition", BindingFlags.NonPublic | BindingFlags.Instance);
31             mi.Invoke(pop, null);
32         }
33     }
34 }

值得註意的是,原有的綁定目標源要記得取消LocationChanged事件訂閱,新的綁定目標源保險起見,也要提前註銷再添加事件訂閱。

另:通知popup位置更新,也可能通過如下的黑科技:

1     //通知更新相對位置
2     var offset = pop.HorizontalOffset;
3     pop.HorizontalOffset = offset + 1;
4     pop.HorizontalOffset = offset;

為何改變一下HorizontalOffset就可行呢?因為上面最終並沒有改變HorizontalOffset的值。。。

原來。。。好吧,先看源碼

 1     /// <summary>獲取或設置目標原點和彈出項對齊之間的水平距離點。</summary>
 2     /// <returns>
 3     ///   目標原點和 popup 對齊點之間的水平距離。
 4     ///    有關目標原點和 popup 對齊點的信息,請參閱 Popup 放置行為。
 5     ///    默認值為 0。
 6     /// </returns>
 7     [Bindable(true)]
 8     [Category("Layout")]
 9     [TypeConverter(typeof (LengthConverter))]
10     public double HorizontalOffset
11     {
12       get
13       {
14         return (double) this.GetValue(Popup.HorizontalOffsetProperty);
15       }
16       set
17       {
18         this.SetValue(Popup.HorizontalOffsetProperty, (object) value);
19       }
20     }
21 
22     private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
23     {
24       ((Popup) d).Reposition();
25     }

是的,最終調用了Reposition,而Reposition方法中有調用UpdatePosition更新popup位置

所以以上,更新popup位置的捷徑是更新HorizontalOffset。

3.界面設置綁定目標源

解決 Popup 位置不隨窗口移動更新的問題