前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >准确判断一个 WPF 控件 / UI 元素当前是否显示在屏幕内

准确判断一个 WPF 控件 / UI 元素当前是否显示在屏幕内

作者头像
walterlv
发布2023-10-22 11:35:46
4890
发布2023-10-22 11:35:46
举报

你的 WPF 窗口是可以拖到屏幕外面去的,所以拉几个元素到屏幕外很正常。你的屏幕可能有多个。你的多个屏幕可能有不同的 DPI。你检测的元素可能带有旋转。

各种各样奇怪的因素可能影响你检查此元素是否在屏幕内,本文包你一次性解决,绝对准确判断。

本文将说三种不同的判定方法,分偷懒版、日常版和苛刻版:

  • 如果你只是写个 demo 啥的,用偷懒版就够了,代码少性能高。
  • 如果你在项目/产品中使用,使用日常版就好。
  • 如果你的用户群体天天喷你 bug 多,那么用苛刻版更好。

偷懒版

如果你只想写个 demo,那么此代码足以。

判断 UI 元素的位置,其右侧是否在屏幕最左侧,其底部是否在屏幕最上面;或者其左侧是否在屏幕最右侧,其顶部是否在屏幕最下面。

偷懒版
偷懒版

1 2 3 4 5 6 7 8 9

private static bool IsOutsideOfScreen(FrameworkElement target) { var topLeft = target.PointToScreen(new Point()); var bottomRight = target.PointToScreen(new Point(target.ActualWidth, target.ActualHeight)); return bottomRight.X < SystemParameters.VirtualScreenLeft || bottomRight.Y < SystemParameters.VirtualScreenTop || topLeft.X > SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth || topLeft.Y > SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight; }

日常版(推荐)

如果你检测的元素自带了旋转,那么以上方法就不能准确判断了。

现在,我们需要检查这个元素的整个边界区域,即便是旋转后。于是,现在,我们要判断元素边界点所在的矩形区域了。

日常版
日常版

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

/// <summary> /// 判断一个可视化对象是否在屏幕外面无法被看见。 /// </summary> /// <param name="target">要判断的可视化元素。</param> /// <returns>如果元素在屏幕外面,则返回 true;如果元素在屏幕里或者部分在屏幕里面,则返回 false。</returns> private static bool IsOutsideOfScreen(FrameworkElement target) { try { var bounds = GetPixelBoundsToScreen(target); var screenBounds = GetScreenPixelBounds(); var intersect = screenBounds; intersect.Intersect(bounds); return intersect.IsEmpty; } catch (InvalidOperationException) { // 此 Visual 未连接到 PresentationSource。 return true; } Rect GetPixelBoundsToScreen(FrameworkElement visual) { var pixelBoundsToScreen = Rect.Empty; pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, 0))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, 0))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, visual.ActualHeight))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, visual.ActualHeight))); return pixelBoundsToScreen; } Rect GetScreenPixelBounds() { return new Rect(SystemParameters.VirtualScreenLeft, SystemParameters.VirtualScreenTop, SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight); } }

苛刻版

现在,更复杂的场景来了。

如果用户有多台显示器,而且大小还不一样,那么依前面的判定方法,下图中 C 控件虽然人眼看在屏幕外,但计算所得是在屏幕内。

更复杂的,是多台显示器还不同 DPI 时,等效屏幕尺寸的计算更加复杂。更恐怖的是,WPF 程序声明支持的 DPI 级别不同,计算也会有一些差别。想要写一种支持所有支持级别的代码更加复杂。但本文可以。

苛刻版
苛刻版

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

/// <summary> /// 判断一个可视化对象是否在屏幕外面无法被看见。 /// </summary> /// <param name="target">要判断的可视化元素。</param> /// <returns>如果元素在屏幕外面,则返回 true;如果元素在屏幕里或者部分在屏幕里面,则返回 false。</returns> private bool IsOutsideOfScreen(FrameworkElement target) { var hwndSource = (HwndSource)PresentationSource.FromVisual(target); if (hwndSource is null) { return true; } var hWnd = hwndSource.Handle; var targetBounds = GetPixelBoundsToScreen(target); var screens = System.Windows.Forms.Screen.AllScreens; return !screens.Any(x => x.Bounds.IntersectsWith(targetBounds)); System.Drawing.Rectangle GetPixelBoundsToScreen(FrameworkElement visual) { var pixelBoundsToScreen = Rect.Empty; pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, 0))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, 0))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, visual.ActualHeight))); pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, visual.ActualHeight))); return new System.Drawing.Rectangle( (int)pixelBoundsToScreen.X, (int)pixelBoundsToScreen.Y, (int)pixelBoundsToScreen.Width, (int)pixelBoundsToScreen.Height); } }

在下面这段代码中,即便是 WPF 项目,我们也需要引用 Windows Forms,用于获取屏幕相关的信息。

如果是 SDK 风格的项目,则在 csproj 中添加如下代码:

1 2 3 4 5 6 7 8 9 10

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net5.0</TargetFramework> <UseWPF>true</UseWPF> ++ <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> </Project>

如果是传统风格的项目,则直接添加 System.Windows.Forms 程序集的引用就好。

因为 WPF 的坐标单位是“设备无关单位”(我更倾向于叫有效像素,见 有效像素(Effective Pixels)),所以在系统对窗口有缩放行为的时候,多屏不同 DPI 的计算相当复杂,所以这里我们使用纯 Win32 / Windows Forms 方法在来计算屏幕与 UI 元素之间的交叉情况,并且避免在任何时候同时将多个屏幕的坐标进行加减乘除(避免单位不一致的问题)。所以这段代码对任何 WPF 的 DPI 配置都是有效且准确的。

关于 DPI 感知设置的问题,可阅读我的其他博客:

此代码的唯一的缺点是,在 WPF 项目里面要求引用 Windows Forms。

功能比较

不知道用哪个?看下表吧!

代码版本

偷懒版

日常版

苛刻版

基础判断屏幕内外

✔️

✔️

✔️

高分屏(非 96 DPI)

✔️

✔️

✔️

整齐排列的多屏

✔️

✔️

✔️

元素带有旋转

✔️

✔️

多屏尺寸不统一

✔️

多屏有不同 DPI(WPF 感知系统 DPI)

✔️

多屏有不同 DPI(WPF 感知屏幕 DPI)

✔️

多屏有不同 DPI(WPF 感知屏幕 DPI V2)

✔️

纯 WPF 代码(无需引用 Windows Forms)

✔️

✔️

元素形状不规则

性能

较好

一般

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/detect-whether-a-wpf-visual-is-inside-screen.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected])

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 偷懒版
  • 日常版(推荐)
  • 苛刻版
  • 功能比较
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档