解决 Popup 位置不随窗口移动更新的问题

内容预览:
  • 好吧,先看源码 1 /// <summary>获取或设置目标原点和弹出项对齐...~
  • 5 /// 默认值为 0~
  • 6 /// </returns> 7 10 public double HorizontalOffset11 {12 ge...~

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 位置不随窗口移动更新的问题 的全部内容。

本站部分内容来源于互联网和用户投稿,如有侵权请联系我们删除,谢谢。
Email:[email protected]


0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论