原文:WPF 获取元素(Visual)相对于屏幕设备的缩放比例,可用于清晰显示图片

我们知道,在 WPF 中的坐标单位不是屏幕像素单位,所以如果需要知道某个控件的像素尺寸,以便做一些与屏幕像素尺寸相关的操作,就需要经过一些计算(例如得到屏幕的 DPI)。

更繁琐的是,我们的控件可能外面有一些其他的控件做了 RenderTransform 进行了一些缩放,于是了解到屏幕像素单位就更不容易了。

本文将提供一套计算方法,帮助计算某个 WPF 控件相比于屏幕像素尺寸的缩放比例,用于进行屏幕像素级别的渲染控制。


一个 WPF 控件会经历哪些缩放?

如下图,我画了一个屏幕,屏幕里面有一个 WPF 窗口,WPF 窗口里面有一个或者多个 ViewBox 或者设置了 RenderTransform 这样的缩放的控件,一层层嵌套下有我们的最终控件。

于是,我们的控件如何得知此时相比于屏幕像素的缩放比呢?换句话说,如何得知此时此控件的显示占了多少个屏幕像素的宽高呢?

分别计算所有的缩放

从上面的图中,我们可以得知,有两种不同种类的缩放:

  1. 屏幕到 WPF 窗口的缩放
  2. WPF 窗口内部的缩放

屏幕到 WPF 窗口的缩放

我们知道 WPF 的单位叫做 DIP 设备无关单位。不过,我更希望引入 UWP 中的有效像素单位。实际上 WPF 和 UWP 的像素单位含义是一样的,只是 WPF 使用了一个画饼式的叫法,而 UWP 中的叫法就显得现实得多。

你可以阅读我的另一篇博客了解到有效像素单位:

有效像素主要就是考虑了 DPI 缩放。于是实际上我们就是在计算 DPI 缩放。

// visual 是我们准备找到缩放量的控件。
var ct = PresentationSource.FromVisual(visual)?.CompositionTarget;
var matrix = ct == null ? Matrix.Identity : ct.TransformToDevice;
  • 1
  • 2
  • 3

这里,我们使用的是 PresentationSource.FromVisual(visual)?.CompositionTarget 因为不同屏幕可能存在不同的 DPI。

WPF 窗口内部的缩放

WPF 窗口内部的缩放,肯定不会是一层层自己去叠加。

实际上 WPF 提供了方法 TransformToAncestor 可以计算一个两个具有父子关系的控件的相对变换量。

于是我们需要找到 WPF 窗口中的根元素,可以通过不断查找可视化树的父级来找到根。

// VisualRoot 方法用于查找 visual 当前的可视化树的根,如果 visual 已经显示,则根会是窗口中的根元素。
var root = VisualRoot(visual);
var transform = ((MatrixTransform)visual.TransformToAncestor(root)).Value;
  • 1
  • 2
  • 3

我封装的源码

为了方便使用,我进行了一些封装。

要获取某个 Visual 相比于屏幕的缩放量,则调用 GetScalingRatioToDevice 方法即可。

代码已经上传至 gits:https://gist.github.com/walterlv/6015ea19c9338b9e45ca053b102cf456

using System;
using System.Windows;
using System.Windows.Media; namespace Walterlv
{
public static class VisualScalingExtensions
{
/// <summary>
/// 获取一个 <paramref name="visual"/> 在显示设备上的尺寸相对于自身尺寸的缩放比。
/// </summary>
public static Size GetScalingRatioToDevice(this Visual visual)
{
return visual.GetTransformInfoToDevice().size;
} /// <summary>
/// 获取一个 <paramref name="visual"/> 在显示设备上的尺寸相对于自身尺寸的缩放比和旋转角度(顺时针为正角度)。
/// </summary>
public static (Size size, double angle) GetTransformInfoToDevice(this Visual visual)
{
if (visual == null) throw new ArgumentNullException(nameof(visual)); // 计算此 Visual 在 WPF 窗口内部的缩放(含 ScaleTransform 等)。
var root = VisualRoot(visual);
var transform = ((MatrixTransform)visual.TransformToAncestor(root)).Value;
// 计算此 WPF 窗口相比于设备的外部缩放(含 DPI 缩放等)。
var ct = PresentationSource.FromVisual(visual)?.CompositionTarget;
if (ct != null)
{
transform.Append(ct.TransformToDevice);
}
// 如果元素有旋转,则计算旋转分量。
var unitVector = new Vector(1, 0);
var vector = transform.Transform(unitVector);
var angle = Vector.AngleBetween(unitVector, vector);
transform.Rotate(-angle);
// 计算考虑了旋转的综合缩放比。
var rect = new Rect(new Size(1, 1));
rect.Transform(transform); return (rect.Size, angle);
} /// <summary>
/// 寻找一个 <see cref="Visual"/> 连接着的视觉树的根。
/// 通常,如果这个 <see cref="Visual"/> 显示在窗口中,则根为 <see cref="Window"/>;
/// 不过,如果此 <see cref="Visual"/> 没有显示出来,则根为某一个包含它的 <see cref="Visual"/>。
/// 如果此 <see cref="Visual"/> 未连接到任何其它 <see cref="Visual"/>,则根为它自身。
/// </summary>
private static Visual VisualRoot(Visual visual)
{
if (visual == null) throw new ArgumentNullException(nameof(visual)); var root = visual;
var parent = VisualTreeHelper.GetParent(visual);
while (parent != null)
{
if (parent is Visual r)
{
root = r;
}
parent = VisualTreeHelper.GetParent(parent);
}
return root;
}
}
}
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

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

发布了382 篇原创文章 · 获赞 232 · 访问量 47万+

WPF 获取元素(Visual)相对于屏幕设备的缩放比例,可用于清晰显示图片的更多相关文章

  1. 获取元素top值,屏幕滚动到当前元素

    var top = $(this).offset().top; $('html , body').animate({scrollTop: top-100},10);

  2. jquery获取元素到屏幕底的可视距离

    jquery获取元素到屏幕底的可视距离 要打对号的图里的height(我自称为可视高度:滚动条未滑到最底端)  不是打叉图里的到页面底部(滚动条到最底部时的height)(offset().top方法 ...

  3. js获取元素到屏幕左上角的距离

    开发过程中经常会遇到 获取元素到屏幕左上角的距离, 当我们使用jQuery开发时,我们可以使用 $.offset()来获取准确的距离. 如果我们的项目中并没有引入jQuer的话,跟希望通过原生方法实现 ...

  4. WPF 获取鼠标屏幕位置、窗口位置、控件位置

    原文:WPF 获取鼠标屏幕位置.窗口位置.控件位置 public struct POINT { public int X; public int Y; public POINT(int x, int ...

  5. [WPF]获取鼠标指针下的元素

    原文:[WPF]获取鼠标指针下的元素   [WPF]获取鼠标指针下的元素 周银辉 以前写过一些GetElementUnderMouse之类的函数,要用到坐标换算而显得有些麻烦(特别是当元素有XXXTr ...

  6. 关于js获取元素在屏幕中的位置的方法

    针对我们获取元素在页面中的位置的问题,我们还是用老师一峰老师的方法来解决吧 下面上HTML代码 <div class="left_footer"> <p data ...

  7. WPF中元素拖拽的两个实例

    今天结合之前做过的一些拖拽的例子来对这个方面进行一些总结,这里主要用两个例子来说明在WPF中如何使用拖拽进行操作,元素拖拽是一个常见的操作,第一个拖拽的例子是将ListBox中的子元素拖拽到ListV ...

  8. WPFの获取任意元素的位置

    如果布局在Grid中: 方法一: //_stackPanel为子元素,_grid为父元素 Point point = _stackPanel.TranslatePoint(new Point(0, 0 ...

  9. [WPF]获取控件间的相对位置

    原文:[WPF]获取控件间的相对位置 [WPF]获取控件间的相对位置                             周银辉 我们知道WPF有着比较灵活的布局方式,关于某个控件的坐标,Canv ...

随机推荐

  1. linux jar/war包 后台运行

    1. 基础版,当前ssh窗口锁定,按CTRL+C打断程序运行:或关闭窗口,程序退出 java -jar flowable-modeler.war 2. 改进版,当前ssh窗口不锁定,窗口关闭时,程序终 ...

  2. 在spring boot里使用undertow而非tomcat

    参考https://examples.javacodegeeks.com/enterprise-java/spring/tomcat-vs-jetty-vs-undertow-comparison-o ...

  3. Redis的内存回收策略和内存上限(阿里)

    还有一篇文章 讲解guava如何删除过期数据的,与redis不同,guava没有维护线程删除过期key,只是在设置 key 或者 读取key的时候,顺带删除参考:GuavaCache简介(一)是轻量级 ...

  4. 冰多多团队-第六次Scrum会议

    冰多多团队-第六次Scrum会议 工作情况 团队成员 已完成任务 待完成任务 zpj ASR bug修复 接入IAT模块 牛雅哲 完成语音识别->词典->termux的接口设计,熟悉了语法 ...

  5. SLF4J使用,slf4j-api、slf4j-log4j12以及log4j的关系

    SLF4J不同于其他日志类库,与其它有很大的不同.SLF4J(Simple logging Facade for Java)不是一个真正的日志实现,而是一个抽象层( abstraction layer ...

  6. 关于 ElementUI 通知组件 notification 重叠问题的解决方案

    转载链接:https://blog.csdn.net/csdn_yudong/article/details/101271214 ElementUI 通知组件(notification) 多个时会重叠 ...

  7. [转]JsonCpp遍历json

    #include "json.h" typedef Json::Writer JsonWriter; typedef Json::Reader JsonReader; typede ...

  8. systemctl start docker失败,提示start request repeated too quickly for docker.service

    情景说明 本来服务器docker服务运行的很好,但客户重启了服务器-于是服务有些问题,遂进入到服务器再次启动docker及服务.不料提示上面的错误-- 解决办法 尝试1 Google了一圈,发现说法很 ...

  9. apache httpd 从2.2升级到2.4的过程及中间遇到的坑

    背景描述: 最近集团在做安全扫描,扫出了http的漏洞,一看是监控nagios涉及到的httpd,于是就考虑将httpd升级到最新的版本,在升级的过程中,真是遇到了很多的坑,弄了2天终于搞定了,现在梳 ...

  10. 9个PNG透明图片免费下载网站推荐

    9个PNG透明图片免费下载网站推荐 酷站推荐 2017.08.06 13:47 png格式的图片因为去掉了的背景,方便使用在任何颜色的背景,所以对于从事设计师的朋友来说,经常会用到png透明图片.相信 ...