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位置。
是以以上,更新HorizontalOffset,是更新popup位置的一種捷徑。
3.界面設定綁定目标源
XXXXX
作者:
唐宋元明清2188出處:
http://www.cnblogs.com/kybs0/本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連接配接,否則保留追究法律責任的權利。