Popup弹出后,因业务需求设置了StaysOpen=true后,移动窗口位置或者改变窗口大小,Popup的位置不会更新。

如何更新位置?

获取当前Popup的Target绑定UserControl所在窗口,位置刷新时,时时更新Popup的位置即可。

1.添加一个附加属性

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

2.窗口移动后触发popup更新

首先,有个疑问,popup首次显示时,为何显示的位置是正确的呢?

通过查看源码,发现,其实popup也是有内置更新popup位置的!

而通过查看UpdatePosition代码,其方法确实是更新popup位置的。源码如下:

 private void UpdatePosition()
{
if (this._popupRoot.Value == null)
return;
PlacementMode placement = this.Placement;
Point[] targetInterestPoints = this.GetPlacementTargetInterestPoints(placement);
Point[] childInterestPoints = this.GetChildInterestPoints(placement);
Rect bounds = this.GetBounds(targetInterestPoints);
Rect rect1 = this.GetBounds(childInterestPoints);
double num1 = rect1.Width * rect1.Height;
int num2 = -;
Vector offsetVector1 = new Vector((double)this._positionInfo.X, (double)this._positionInfo.Y);
double num3 = -1.0;
PopupPrimaryAxis popupPrimaryAxis = PopupPrimaryAxis.None;
CustomPopupPlacement[] customPopupPlacementArray = (CustomPopupPlacement[])null;
int num4;
if (placement == PlacementMode.Custom)
{
CustomPopupPlacementCallback placementCallback = this.CustomPopupPlacementCallback;
if (placementCallback != null)
customPopupPlacementArray = placementCallback(rect1.Size, bounds.Size, new Point(this.HorizontalOffset, this.VerticalOffset));
num4 = customPopupPlacementArray == null ? : customPopupPlacementArray.Length;
if (!this.IsOpen)
return;
}
else
num4 = Popup.GetNumberOfCombinations(placement);
for (int i = ; i < num4; ++i)
{
bool flag1 = false;
bool flag2 = false;
Vector offsetVector2;
PopupPrimaryAxis axis;
if (placement == PlacementMode.Custom)
{
offsetVector2 = (Vector)targetInterestPoints[] + (Vector)customPopupPlacementArray[i].Point;
axis = customPopupPlacementArray[i].PrimaryAxis;
}
else
{
Popup.PointCombination pointCombination = this.GetPointCombination(placement, i, out axis);
Popup.InterestPoint targetInterestPoint = pointCombination.TargetInterestPoint;
Popup.InterestPoint childInterestPoint = pointCombination.ChildInterestPoint;
offsetVector2 = targetInterestPoints[(int)targetInterestPoint] - childInterestPoints[(int)childInterestPoint];
flag1 = childInterestPoint == Popup.InterestPoint.TopRight || childInterestPoint == Popup.InterestPoint.BottomRight;
flag2 = childInterestPoint == Popup.InterestPoint.BottomLeft || childInterestPoint == Popup.InterestPoint.BottomRight;
}
Rect rect2 = Rect.Offset(rect1, offsetVector2);
Rect rect3 = Rect.Intersect(this.GetScreenBounds(bounds, targetInterestPoints[]), rect2);
double num5 = rect3 != Rect.Empty ? rect3.Width * rect3.Height : 0.0;
if (num5 - num3 > 0.01)
{
num2 = i;
offsetVector1 = offsetVector2;
num3 = num5;
popupPrimaryAxis = axis;
this.AnimateFromRight = flag1;
this.AnimateFromBottom = flag2;
if (Math.Abs(num5 - num1) < 0.01)
break;
}
}
if (num2 >= && (placement == PlacementMode.Right || placement == PlacementMode.Left))
this.DropOpposite = !this.DropOpposite;
rect1 = new Rect((Size)this._secHelper.GetTransformToDevice().Transform((Point)this._popupRoot.Value.RenderSize));
rect1.Offset(offsetVector1);
Rect screenBounds = this.GetScreenBounds(bounds, targetInterestPoints[]);
Rect rect4 = Rect.Intersect(screenBounds, rect1);
if (Math.Abs(rect4.Width - rect1.Width) > 0.01 || Math.Abs(rect4.Height - rect1.Height) > 0.01)
{
Point point1 = targetInterestPoints[];
Vector vector1 = targetInterestPoints[] - point1;
vector1.Normalize();
if (!this.IsTransparent || double.IsNaN(vector1.Y) || Math.Abs(vector1.Y) < 0.01)
{
if (rect1.Right > screenBounds.Right)
offsetVector1.X = screenBounds.Right - rect1.Width;
else if (rect1.Left < screenBounds.Left)
offsetVector1.X = screenBounds.Left;
}
else if (this.IsTransparent && Math.Abs(vector1.X) < 0.01)
{
if (rect1.Bottom > screenBounds.Bottom)
offsetVector1.Y = screenBounds.Bottom - rect1.Height;
else if (rect1.Top < screenBounds.Top)
offsetVector1.Y = screenBounds.Top;
}
Point point2 = targetInterestPoints[];
Vector vector2 = point1 - point2;
vector2.Normalize();
if (!this.IsTransparent || double.IsNaN(vector2.X) || Math.Abs(vector2.X) < 0.01)
{
if (rect1.Bottom > screenBounds.Bottom)
offsetVector1.Y = screenBounds.Bottom - rect1.Height;
else if (rect1.Top < screenBounds.Top)
offsetVector1.Y = screenBounds.Top;
}
else if (this.IsTransparent && Math.Abs(vector2.Y) < 0.01)
{
if (rect1.Right > screenBounds.Right)
offsetVector1.X = screenBounds.Right - rect1.Width;
else if (rect1.Left < screenBounds.Left)
offsetVector1.X = screenBounds.Left;
}
}
int x = DoubleUtil.DoubleToInt(offsetVector1.X);
int y = DoubleUtil.DoubleToInt(offsetVector1.Y);
if (x == this._positionInfo.X && y == this._positionInfo.Y)
return;
this._positionInfo.X = x;
this._positionInfo.Y = y;
this._secHelper.SetPopupPos(true, x, y, false, , );
}

那么,我们有什么办法调用这个私有方法呢?我相信大家都想,找到popup源码开发者,爆了他Y的!

有一种方法,叫反射,反射可以获取类的任一个字段或者属性。

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

通过反射,我们获取到UpdatePosition方法,并调用执行。

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

下面是详细的属性更改事件实现:

     private static void OnPopupPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Popup pop = d as Popup;
//旧值取消LocationChanged监听
if (e.OldValue is DependencyObject previousPlacementTarget)
{
Window window = Window.GetWindow(previousPlacementTarget);
var element = previousPlacementTarget as FrameworkElement;
if (window != null)
{
CancelEventsListeningInWindow(window);
}
if (element != null)
{
element.SizeChanged -= ElementSizeChanged;
element.LayoutUpdated -= ElementLayoutUpdated;
}
} //新值添加LocationChanged监听
if (e.NewValue is DependencyObject newPlacementTarget)
{
Window window = Window.GetWindow(newPlacementTarget);
var element = newPlacementTarget as FrameworkElement;
//窗口已加载
if (window != null)
{
RegisterEventsInWindow(window);
}
//窗口未加载,则等待控件初始化后,再获取窗口
else if (element != null)
{
element.Loaded -= ElementLoaded;
element.Loaded += ElementLoaded;
}
//元素大小变换时,变更Popup位置
if (element != null)
{
element.SizeChanged -= ElementSizeChanged;
element.SizeChanged += ElementSizeChanged;
element.LayoutUpdated -= ElementLayoutUpdated;
element.LayoutUpdated += ElementLayoutUpdated;
}
void ElementLoaded(object sender, RoutedEventArgs e3)
{
element.Loaded -= ElementLoaded;
window = Window.GetWindow(newPlacementTarget);
if (window != null)
{
RegisterEventsInWindow(window);
}
}
}
void RegisterEventsInWindow(Window window)
{
window.LocationChanged -= WindowLocationChanged;
window.LocationChanged += WindowLocationChanged;
window.SizeChanged -= WindowSizeChanged;
window.SizeChanged += WindowSizeChanged;
}
void CancelEventsListeningInWindow(Window window)
{
window.LocationChanged -= WindowLocationChanged;
window.SizeChanged -= WindowSizeChanged;
}
void WindowLocationChanged(object s1, EventArgs e1)
{
UpdatePopupLocation();
}
void WindowSizeChanged(object sender, SizeChangedEventArgs e2)
{
UpdatePopupLocation();
}
void ElementSizeChanged(object sender, SizeChangedEventArgs e3)
{
UpdatePopupLocation();
}
void ElementLayoutUpdated(object sender, EventArgs e4)
{
UpdatePopupLocation();
}
void UpdatePopupLocation()
{
if (pop != null && pop.IsOpen)
{
//通知更新相对位置
var method = typeof(Popup).GetMethod("UpdatePosition", BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(pop, null);
}
}
}

值得注意的是,原有的绑定目标源要记得取消LocationChanged事件订阅,新的绑定目标源保险起见,也要提前注销再添加事件订阅。

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

     //通知更新相对位置
var offset = pop.HorizontalOffset;
pop.HorizontalOffset = offset + ;
pop.HorizontalOffset = offset;

为何改变一下HorizontalOffset就可行呢?因为上面最终并没有改变HorizontalOffset的值。。。

原来。。。好吧,先看源码

     /// <summary>获取或设置目标原点和弹出项对齐之间的水平距离点。</summary>
/// <returns>
/// 目标原点和 popup 对齐点之间的水平距离。
/// 有关目标原点和 popup 对齐点的信息,请参阅 Popup 放置行为。
/// 默认值为 0。
/// </returns>
[Bindable(true)]
[Category("Layout")]
[TypeConverter(typeof (LengthConverter))]
public double HorizontalOffset
{
get
{
return (double) this.GetValue(Popup.HorizontalOffsetProperty);
}
set
{
this.SetValue(Popup.HorizontalOffsetProperty, (object) value);
}
} private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Popup) d).Reposition();
}

是的,最终调用了Reposition,而Reposition方法中有调用UpdatePosition更新popup位置。

所以以上,更新HorizontalOffset,是更新popup位置的一种捷径。

3. 元素移动/大小变化后,触发更新

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

元素大小变化时:

     else if (newPlacementTarget is FrameworkElement frameworkElement)
{
frameworkElement.SizeChanged -= ElementOnSizeChanged;
frameworkElement.SizeChanged += ElementOnSizeChanged;
}

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

     else if (newPlacementTarget is FrameworkElement frameworkElement)
{
frameworkElement.LayoutUpdated -= ElementOnLayoutUpdated;
frameworkElement.LayoutUpdated += ElementOnLayoutUpdated;
}

4.界面设置绑定目标源

     <Popup x:Name="FirstShowPopup" PlacementTarget="{Binding ElementName=TestButton}" Placement="Custom"
CustomPopupPlacementCallback="{easiUi:Placement Align=RightCenter,OutOfScreenEnabled=True}" PopupAnimation="Fade"
AllowsTransparency="True" StaysOpen="True" HorizontalOffset="-16" VerticalOffset="4"
helper:PopupHelper.LocationUpdatedOnTarget="{Binding ElementName=TestButton}"
helper:PopupHelper.TopmostInCurrentWindow="True">
</Popup>

解决 Popup 位置不随窗口移动更新的问题的更多相关文章

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

    原文:Popup 解决位置不随窗口/元素FrameworkElement 移动更新的问题 Popup弹出后,因业务需求设置了StaysOpen=true后,移动窗口位置或者改变窗口大小,Popup的位 ...

  2. 解决Popup StayOpen=true时,永远置顶的问题

    Popup设置了StayOpen=true时,会置顶显示. 如弹出了Popup后,打开QQ窗口,Popup显示在QQ聊天界面之上. 怎么解决问题? 获取绑定UserControl所在的窗口,窗口层级变 ...

  3. Vivado_MicroBlaze_问题及解决方法_汇总(不定时更新)

    Vivado_MicroBlaze_问题及解决方法_汇总(不定时更新) 标签: Vivado 2015-07-03 14:35 4453人阅读 评论(0) 收藏 举报  分类: 硬件(14)  版权声 ...

  4. 解决popup不随着window一起移动的问题

    原文:解决popup不随着window一起移动的问题 当我们设置Popup的StayOpen="True"时,会发现移动窗体或者改变窗体的Size的时候,Popup并不会跟随着一起 ...

  5. 解决 WPF 嵌套的子窗口在改变窗口大小的时候闪烁的问题

    原文:解决 WPF 嵌套的子窗口在改变窗口大小的时候闪烁的问题 因为 Win32 的窗口句柄是可以跨进程传递的,所以可以用来实现跨进程 UI.不过,本文不会谈论跨进程 UI 的具体实现,只会提及其实现 ...

  6. IOS8解决获取位置坐标信息出错(Error Domain=kCLErrorDomain Code=0)(转)

    标题:IOS8解决获取位置坐标信息出错(Error Domain=kCLErrorDomain Code=0) 前几天解决了在ios8上无法使用地址位置服务的问题,最近在模拟器上调试发现获取位置坐标信 ...

  7. [ucgui] 对话框5——鼠标位置和移动窗口

    >_<" 这节主要是获取鼠标的位置和把窗口设置为可以移动.其中设置窗口可以移动用FRAMEWIN_SetMoveable(hFrameWin, 1)就行了.而获得鼠标位置则是利用 ...

  8. Vue解决同一页面跳转页面不更新

    问题分析:路由之间的切换,其实就是组件之间的切换,不是真正的页面切换.这也会导致一个问题,就是引用相同组件的时候,会导致该组件无法更新. 方案一:使用 watch 进行监听 watch: { /* = ...

  9. [转帖]升级 Ubuntu,解决登录时提示有软件包可以更新的问题

    升级 Ubuntu,解决登录时提示有软件包可以更新的问题 2017年12月05日 11:58:17 阅读数:2953更多 个人分类: ubuntu Connecting to ... Connecti ...

随机推荐

  1. xftp上传文件失败,执行程序发现磁盘满了:No space left on device

    参考链接 No space left on device 解决Linux系统磁盘空间满的办法http://www.cnblogs.com/aspirant/p/3604801.html如何解决linu ...

  2. Archaius 原理

    Archaius 原理 Archaius是什么? Archaius提供了动态修改配置的值的功能,在修改配置后,不需要重启应用服务.其核心思想就是轮询配置源,每一次迭代,检测配置是否更改,有更改重新更新 ...

  3. 查看eclipse ADT SDK JDK版本号

    一.查看eclipsea版本号: 启动eclipse,Help > About Eclipse SDK,在eclipse SDK对话框下面就有Eclipse SDK Version:4.2.0这 ...

  4. express学习(二)—— Post()类型和中间件

    1.数据:GET.POST 2.中间件:使用.写.链式操作 GET-无需中间件 req.query POST-需要"body-parser" server.use(bodyPars ...

  5. Codeforces Round #441 (Div. 2, by Moscow Team Olympiad) B. Divisiblity of Differences

    http://codeforces.com/contest/876/problem/B 题意: 给出n个数,要求从里面选出k个数使得这k个数中任意两个的差能够被m整除,若不能则输出no. 思路: 差能 ...

  6. Codeforces Round #441 (Div. 2, by Moscow Team Olympiad) A. Trip For Meal

    http://codeforces.com/contest/876/problem/A 题意: 一个人一天要吃n次蜂蜜,他有3个朋友,他第一次总是在一个固定的朋友家吃蜂蜜,如果说没有吃到n次,那么他就 ...

  7. 最新IP数据库 存储优化 查询性能优化 每秒解析上千万

    高性能IP数据库格式详解 每秒解析1000多万ip  qqzeng-ip-ultimate.dat 3.0版 编码:UTF8     字节序:Little-Endian 返回规范字段(如:亚洲|中国| ...

  8. poj1094-Sorting It All Out-拓扑排序

    题意: 1).给你一些大写字母,共n个:大写字母间有m条关系: 2).举例:关系:A<B,意思就是A要排在B的前面(也就是说可能B排在A的前面 3).输出:有三种情况: 1.n个字母在前 i 条 ...

  9. bcrypt对密码加密的一些认识(学习笔记)

    学习nodejs和mongoDB的时候,接触了用户注册和登录的一些知识. 1.关于增强用户密码的安全性 用户的密码肯定不能保存为明文,避免撞库攻击. 撞库攻击:撞库是一种针对数据库的攻击方式,方法是通 ...

  10. ubuntu 添加右键打开方式,无法添加程序打开方式

    最近把工作环境迁移到ubuntu,装了WPS for Linux ,说实话确实是十分良心啊!运行效率奇高,绿色无广告,并且和windows版本无异,感觉就可以抛弃自带的libreoffice了. 但是 ...