• Popup 解决位置不随窗口/元素FrameworkElement 移动更新的问题


    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         //旧值取消LocationChanged监听
     5         if (e.OldValue is DependencyObject previousPlacementTarget)
     6         {
     7             Window window = Window.GetWindow(previousPlacementTarget);
     8             var element = previousPlacementTarget as FrameworkElement;
     9             if (window != null)
    10             {
    11                 CancelEventsListeningInWindow(window);
    12             }
    13             if (element != null)
    14             {
    15                 element.SizeChanged -= ElementSizeChanged;
    16                 element.LayoutUpdated -= ElementLayoutUpdated;
    17             }
    18         }
    19 
    20         //新值添加LocationChanged监听
    21         if (e.NewValue is DependencyObject newPlacementTarget)
    22         {
    23             Window window = Window.GetWindow(newPlacementTarget);
    24             var element = newPlacementTarget as FrameworkElement;
    25             //窗口已加载
    26             if (window != null)
    27             {
    28                 RegisterEventsInWindow(window);
    29             }
    30             //窗口未加载,则等待控件初始化后,再获取窗口
    31             else if (element != null)
    32             {
    33                 element.Loaded -= ElementLoaded;
    34                 element.Loaded += ElementLoaded;
    35             }
    36             //元素大小变换时,变更Popup位置
    37             if (element != null)
    38             {
    39                 element.SizeChanged -= ElementSizeChanged;
    40                 element.SizeChanged += ElementSizeChanged;
    41                 element.LayoutUpdated -= ElementLayoutUpdated;
    42                 element.LayoutUpdated += ElementLayoutUpdated;
    43             }
    44             void ElementLoaded(object sender, RoutedEventArgs e3)
    45             {
    46                 element.Loaded -= ElementLoaded;
    47                 window = Window.GetWindow(newPlacementTarget);
    48                 if (window != null)
    49                 {
    50                     RegisterEventsInWindow(window);
    51                 }
    52             }
    53         }
    54         void RegisterEventsInWindow(Window window)
    55         {
    56             window.LocationChanged -= WindowLocationChanged;
    57             window.LocationChanged += WindowLocationChanged;
    58             window.SizeChanged -= WindowSizeChanged;
    59             window.SizeChanged += WindowSizeChanged;
    60         }
    61         void CancelEventsListeningInWindow(Window window)
    62         {
    63             window.LocationChanged -= WindowLocationChanged;
    64             window.SizeChanged -= WindowSizeChanged;
    65         }
    66         void WindowLocationChanged(object s1, EventArgs e1)
    67         {
    68             UpdatePopupLocation();
    69         }
    70         void WindowSizeChanged(object sender, SizeChangedEventArgs e2)
    71         {
    72             UpdatePopupLocation();
    73         }
    74         void ElementSizeChanged(object sender, SizeChangedEventArgs e3)
    75         {
    76             UpdatePopupLocation();
    77         }
    78         void ElementLayoutUpdated(object sender, EventArgs e4)
    79         {
    80             UpdatePopupLocation();
    81         }
    82         void UpdatePopupLocation()
    83         {
    84             if (pop != null && pop.IsOpen)
    85             {
    86                 //通知更新相对位置
    87                 var method = typeof(Popup).GetMethod("UpdatePosition", BindingFlags.NonPublic | BindingFlags.Instance);
    88                 method?.Invoke(pop, null);
    89             }
    90         }
    91     }

    值得注意的是,原有的绑定目标源要记得取消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. 元素移动/大小变化后,触发更新

    当popup的PlaceTarget绑定一个控件或者一个Grid后,FrameworkElement大小变化/位置变化时,popup位置更新(同上)

    元素大小变化时:

    1     else if (newPlacementTarget is FrameworkElement frameworkElement)
    2     {
    3         frameworkElement.SizeChanged -= ElementOnSizeChanged;
    4         frameworkElement.SizeChanged += ElementOnSizeChanged;
    5     }

    也可以直接监听LayoutUpdated事件,元素大小/位置变化时,LayoutUpdated都会触发。注意:LayoutUpdated触发有点频繁。

    1     else if (newPlacementTarget is FrameworkElement frameworkElement)
    2     {
    3         frameworkElement.LayoutUpdated -= ElementOnLayoutUpdated;
    4         frameworkElement.LayoutUpdated += ElementOnLayoutUpdated;
    5     }

     4.界面设置绑定目标源

    1     <Popup x:Name="FirstShowPopup" PlacementTarget="{Binding ElementName=TestButton}" Placement="Custom"
    2         CustomPopupPlacementCallback="{easiUi:Placement Align=RightCenter,OutOfScreenEnabled=True}" PopupAnimation="Fade"
    3         AllowsTransparency="True" StaysOpen="True" HorizontalOffset="-16" VerticalOffset="4"
    4         helper:PopupHelper.LocationUpdatedOnTarget="{Binding ElementName=TestButton}"
    5         helper:PopupHelper.TopmostInCurrentWindow="True">
    6     </Popup>
  • 相关阅读:
    染色问题的算法(转)
    函数的定义域和定义区间的区别(转)
    详解c++指针的指针和指针的引用(转)
    母函数(转)
    别人整理的DP大全(转)
    HDU 1430 魔板(康托展开+BFS+预处理)
    ZZULIOJ 1726 迷宫(BFS+小坑)
    NBUT 1635 Explosion(最小顶点覆盖)
    NBUT 1602 Mod Three(线段树单点更新区间查询)
    NBUT 1028 该减肥了(简单递推)
  • 原文地址:https://www.cnblogs.com/kybs0/p/7835353.html
Copyright © 2020-2023  润新知