WPF中DPI的问题
先搞清楚一下几个概念:
- DPI:dots per inch ,每英寸的点数。我们常说的鼠标DPI,是指鼠标移动一英寸的距离滑过的点数;打印DPI,每英寸的长度打印的点数;扫描DPI,每英寸扫描了多少个点。(更多请参考百度百科http://baike.baidu.com/view/49853.htm)
- 像素:pixel,picute和element的缩写。像素可以简单的理解为DPI里面的点。例如,显示器的分辨率为1024像素*768像素,就是说显示器的横向可显示1024个点(像素),纵向科研可以显示768个点(像素)。有的显示器每个显示点排列的比较紧密,1英寸的长度内可以排列的点就多一些,有的排列比较疏松,点就少一些,所以像素和英寸之间是没有直接的关系。(更多请参考百度百科http://baike.baidu.com/view/575.htm)
- 分辨率:如上例,但是我们常说调整一下显示器的分辨率,是啥意思呢?显示器有一个自然的分辨率,就是显示器的最大能耐,比如说,显示器的自然分辨率为1600*1200,那么长度小于1600、宽度小于1200的分辨率都可以显示的,1900*1300这样子的分辨率就不行。
- Set Custom Text Size(DPI):WIN7这个DPI我个人觉得,和上述的DPI不是一回事,不知道为啥微软把这个东东也叫DPI。首先,显示设备的物理DPI是不太可能改变的;其次,朋友们发现,这个DPI的默认值是96,也就是1英寸打印96个像素,当我们把DPI调整为125后,1英寸打印125个点。我们的大多数应用程序界面是按照96DPI设计的,到了125DPI后,1英寸的长度不再是1英寸,应该小一点,实际上,没有变小,反而变大了,所以,我觉得和上述的dpi不是一回事,最起码不是改变物理DPI
WPF单位
WPF窗口和所有之内的元素都是使用“独立设备单位”进行测量的。一个独立设备单位被定义为一英寸的96分之一。要理解它在实际应用中的含义,需要考虑一个例子。
假设你在WPF中创建了一个96×96单位大小的按钮,如果你使用的是标准的Windows DPI设置(96dpi),那么每一个独立设备单位对应一个实际的物理象素。这是因为WPF采用如下的计算方式:
[物理单位大小]=[独立设备单位大小] × [系统DPI点数]
=1/96 英寸 * 96 点每英寸
= 1 象素
本质上,WPF假定使用96个象素来组成一英寸是通过Windows的系统DPI的设置而获得的。无论如何,实际值取决于你的显示设备。
举例来讲,一个20英寸的液晶显示器的最大分辨率是1600×1200象素。通过下面的简单计算,就可以计算出这个显示器的象素密度。
[系统每英寸点数]=Sqrt(1600×1600 +1200×1200) 象素 /19 英寸 (Sqrt为开根号)
=100点每英寸
这种情况下的象素密度是100dpi,这实际上比Windows假定的值要高一些。结果,这个显示器按96×96象素显示的按钮比一英寸要小一些。
另一方面,15英寸的液晶显示器的分辨率为1024×768,它的象素密度降到了大约85dpi每英寸,所以96×96的按钮实际上比一英寸要大一些。
这两种情形中,如果减少屏幕尺寸(选择800×600分辨率),按钮会相应成比例的变大,这是因为系统的DPI设置仍然是96dpi的缘故。换句话说,Windows仍然假定使用96象素大小作为一英寸。即使在分辨率较低,象素数已经远远少了很多的情形下。
系统DPI
目前为止,WPF的按钮工作原理和应用程序中的其他用户界面元素的工作原理是严格一致的。不同的是在你改变系统DPI设置时的结果。在早期的Windows中,这个特性偶尔被称之为“大字体”。这是因为系统DPI影响了系统字体的大小,但是其他细节却没有甚么改变。
这恰恰是WPF不同的地方。WPF考虑系统特有的DPI设置,如果将系统DPI改为120dpi,WPF会假定它需要120个象素去填充一英寸的空间。WPF使用如下的计算去得出如何将逻辑单位转换为物理设备的象素。
[Physical Unit Size]= [独立设备单位尺寸]×[系统DPI]
=1/96 英寸 ×120 dpi
=1.25 象素
换句话说,当你设置系统DPI为120dpi的时候,WPF渲染引擎假定一独立设备单位等于1.25象素大小。如果显示一个96×96的按钮,物理尺寸实际上是120象素×120象素,这是期待中的结果――一个在标准显示器中显示为一英寸的按钮,在更高象素密度的显示器中仍然显示为一英寸。
这种自动缩放的功能如果只被应用到按钮中,则实际上是没有多大用处的。但是WPF使用独立设备单位来显示所有的东西,包括形状,控件,文本和放进窗体中的任何元素。结果,系统的DPI可以被更改为任何需要的值,WPF会自动无缝的调整应用程序的尺寸。如何更改DPI取决于所用的系统,具体步骤就不赘述了。
位图与矢量图
当使用普通的控件时,可以利用WPF的分辨率的独立性。WPF会自动关注所有的控件以保证它们都有正确的尺寸。可是,如果你想将图片合并到你的应用程序,那就不能够漫不经心了。举例来说,在传统的应用程序中,开发人员使用很小的位图到工具条指令。在WPF应用程序中,这种方式却不理想,因为会根据系统的DPI的设置而放大和缩小,位图有可能显示的很模糊。取而代之,在设计WPF应用程序界面时,即使最小的图标也通常设计为矢量图。矢量图被定义为一个图形集,正因为如此,它们可以被缩放到任意尺寸大小。
很难去高估分辨率独立性的重要性。乍看起来它是一个简单的(straightforward)的一流的(elegant)解决方案,解决了一个历史悠久的问题。然而,为了设计的用户界面能够富有弹性。开发人员需要一种新的思维方法。
--=====================================华丽分隔符===============================================
WPF程序中的单位是与设备无关的单位,每个单位是1/96英寸,如果电脑的DPI设置为96(每个英寸96个像素),那么此时每个WPF单位对应一个像素,不过如果电脑的DPI设备为120(每个英寸120个像素),那此时每个WPF单位对应应该是120/96=1.25个像素
一般在程序中我们常常需要得到当前屏幕的宽和高,常见做法有:
1.System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height
这两个方法可以返回当前屏幕选择的分辨率,该分辨率是以像素为单位,在DPI为96的情况下我们可以利用它们来做一些控件的定位,因为此时WPF单位对应一个像素,而当DPI非96的情况下,用该分辨率来做定位就会发现误差了,因此此时每个WPF单位并不是对应于一个像素
2.SystemParameters.PrimaryScreenWidth
SystemParameters.PrimaryScreenHeight
这两个方法可以返回当前屏幕的宽和高,它是与设备无关的单位(1/96英寸),因此用它来做控件的定位,在DPI改变的情况下,也不会发生定位上的误差
3.SystemParameters.WorkArea.Size.Width
SystemParameters.WorkArea.Size.Height
这两个方法可以返回当前屏幕工作区的宽和高(除去任务栏),它也是与设备无关的单位,通常我们可以结合2和3来得到任务栏的高度
--=====================================华丽分隔符===============================================
WPF单位真的与分辨率无关吗?
WPF从发布之日起,一直将“分辨率无关(resolution independence)”作为其亮点,声称使用WPF制作的用户界面在轻巧的Ultra-Mobile PC的屏幕上和在50英寸的电视机上都能很好地显示。微软之所以称WPF具备“分辨率无关”这一特性,主要是因为WPF的坐标单位设计成为以1/96英寸为一个逻辑像素单位,而不是与设备相关的像素单位。
但是微软本身对WPF“分辨率无关”这一特性没有作更多的具体解释,导致用户会产生很多误解。
误解之一
改变显示器的分辨率设置,同一个WPF的用户界面和绘制的图形尺寸不会变化。
这个可以用一个非常简单的实验证明该结论是错误的。新建一个WPF应用程序窗口,其中高度为400DIUs(DIU:Device independent unit,设备无关单位),宽度为600DIUs,让这个窗口分别在分辨率设置为1280 * 1024和800*600的环境下运行,如下图所示,两个窗口的尺寸是明显不一样的。
误解之二
改变显示的DPI设置,同一个WPF的用户界面和绘制的图形尺寸不会变化。
显示的DPI设置,在XP系统下是通过右键——属性——设置选项卡——高级,可以调用出来,如下图所示:
图 2 显示属性DPI设置
这个也可以用同样的方法进行证明该结论是错误的。仍然是高度为400DIUs[1],宽度为600DIUs的窗口分别运行在96DPI和192DPI两种设置环境下。从下图也可以明显看出窗口的尺寸是不一样的。
误解之三
在不同屏幕上,如果DPI设置相同,则同一个WPF的用户界面和绘制的图形尺寸不会变化。
在这个地方有必要对屏幕的DPI设置进行一下解释说明。DPI设置是指屏幕上每英寸多少个像素,比如当前设置为96DPI,即屏幕上96个像素为1英寸。一般的Windows XP系统有正常尺寸(96DPI)、大尺寸(120DPI)和自定义尺寸三种选项。既然WPF的坐标单位是以1/96英寸为一个逻辑像素单位,那么我们有理由相信,如果两个显示器的DPI设置是相同的,那么同一个WPF的用户界面和绘制的图形尺寸不会变化。很遗憾,这样的结论依旧是一个错误。
CalvinP.Schrotenboer 也用一个实验证明这是一个错误。实验环境如表 1,比如桌面LCD显示器实际屏幕宽度和高度(像素单位)为1600 x 1200,这个和普通的分辨率设置需要区分,这是显示设备的最大分辨率或者说是物理分辨率,即物理上该显示器屏幕上是1600 x 1200个像元,英文中又称这种分辨率为“native resolution(原生分辨率)”。由于两个屏幕物理尺寸也不一样,所以实际的物理DPI可以通过表中的计算公式得到。实际的物理DPI和操作系统的DPI设置是没有什么联系的。
表 1实验环境
实验环境 |
系统一 |
系统二 |
显示器类型 |
桌面LCD显示器 |
笔记本LCD显示器 |
屏幕宽度和高度 (像素单位) |
1600 x 1200 |
1400 x 1050 |
屏幕宽度和高度 (英寸单位) |
17.0 x 12.75 |
12.0 x 9.0 |
实际的物理DPI |
纵向:1600 / 17.0 = 94DPI 横向:1200 / 12.75 = 94DPI |
纵向:1400 / 12 = 117DPI 横向:1050 / 9 = 117DPI |
操作系统的DPI设置 |
96DPI |
96DPI |
在两个不同系统当中运行同一个WPF应用程序,该程序了绘制了一条长为384DIUs的直线,换算成英寸即为384/96= 4英寸。结果在两个系统当中的实际尺寸如下图所示:
问题出在哪儿了?
其实从表 1当中就能看出一些端倪,原因正是在于实际的物理DPI和操作系统设置的DPI不一致造成的。WPF无法知道当前使用设备实际的物理DPI为多少,相反通过操作系统的API函数获得操作系统的DPI值,然后简单地认为这就是实际的物理DPI值。比如在桌面LCD显示器上,实际一个物理像元的尺寸为1/94英寸,由于操作系统设置为96DPI,因此WPF还固执地以为一个实际的像元为1/96英寸,因此线段长度为1/94 * 384 = 4.08英寸。笔记本显示器实际一个物理像元的尺寸为1/117英寸,因此线段长度为1/117 * 384 = 3.28英寸。这个值和我们测量的结果正好相符。
那么我们有理由推测,如果将操作系统的DPI设置成实际的物理DPI,则能做到真正的“分辨率独立”,即在两个不同显示器上显示的线段长度都为4英寸,如图 5所示:
WPF的“分辨率无关”到现在为止已经是山高月小,水落石出。那么我们还要接着讨论另一个问题,在显示器上存在这样的问题,那么是否在打印机上也存在这样的问题呢?仍然可以用一个实验证明。同样绘制一个4英寸的直线,分别在DPI设置为96DPI和120DPI下进行打印,得到的打印结果尺寸相同。如下图所示:
结论
通过上面几个实验分析,我们可以得到如下两个结论:
(1) WPF在打印得时候可以做到“分辨率无关”,即同一个WPF用户界面和绘制的图形尺寸在任何一台打印机上输出都是一致的;
(2) 当显示器实际象元的物理尺寸和系统设置的DPI保持一致的时候,WPF可以在显示器上做到“分辨率无关”,即同一个WPF用户界面和绘制的图形尺寸在任何一台显示器(实际象元的物理尺寸和系统设置的DPI保持一致)上输出都是一致的。反之则无法保证。
更多的讨论
“分辨率无关”这样一个概念,由于微软本身讨论得不多,的确容易造成误解。最为详细地讨论了WPF当中“分辨率无关”的是CalvinP.Schrotenboer 的一篇博文“Is WPF Really Resolution Independent?”。当然Charles Peztold也在自己的博客当中讨论过这个问题。另外在微软的论坛上StephenW,Charles Peztold等人也就WPF的“分辨率无关”和“设备无关”作了比较深入的讨论。
用户固然可以不理睬这些,但是对于一个程序员来说,尤其是一个正在做绘图程序的程序员,尤其尤其是一个还需要打印输出的绘图程序员,是需要清楚这其中细节的。而且了解细节本身也是一件很愉快的事情。
参考文档
http://www.cnblogs.com/xiaokang088/archive/2011/03/02/1969237.html
http://www.cnblogs.com/helloj2ee/archive/2009/04/21/1440709.html
http://www.cnblogs.com/dayrain/archive/2008/11/02/1324870.html
WPF中DPI的问题的更多相关文章
- 基于C#在WPF中使用斑马打印机进行打印【转】
原文链接:http://ju.outofmemory.cn/entry/132476 最近在项目中接手了一个比较有挑战性的模块——用斑马打印机将需要打印的内容打印出来.苦苦折腾了两天,总算有所收获,就 ...
- WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
Windows Community Toolkit 再次更新到 5.0.以前可以在 WPF 中使用有限的 UWP 控件,而现在有了 WindowsXamlHost,则可以使用更多 UWP 原生控件了. ...
- 再论WPF中的UseLayoutRounding和SnapsToDevicePixels
原文:再论WPF中的UseLayoutRounding和SnapsToDevicePixels 版权声明:.net/web/医疗技术的木子纵横的个人分享 https://blog.csdn.net/m ...
- WPF中的3D Wireframe
原文:WPF中的3D Wireframe WPF不支持画三维线,但开发人员提供了ScreenSpaceLines3D 类用于实现这个功能.我已经在程序中实现并成功显示3D Wireframe,并能够进 ...
- 【WPF】DPI对控件定位产生的影响
原文:[WPF]DPI对控件定位产生的影响 需求 程序界面上是一个Window,当用户点击桌面上除此Window之外的任何地方,都要把这个window隐藏掉.程序有个托盘图标,点击托盘图标不能隐藏wi ...
- WPF中的文本度量
关于WPF中的文本度量,需要了解以下几个问题: WPF中支持一些常用的度量单位:px(device independent pixels).in(inches).cm(centimeters).pt( ...
- 在WPF中使用依赖注入的方式创建视图
在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...
- MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息
MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...
- MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信
MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...
随机推荐
- B5:责任链模式 Chain Of Responsibility
使多个对象都有机会处理处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着该链处理请求,直到有一个对象能够处理它为止. 相当于switch/case,在客户端指定了每一链 ...
- CentOS系统使用yum安装配置MariaDB数据库
http://www.server110.com/mariadb/201310/2670.html 1.在 /etc/yum.repos.d/ 下建立 MariaDB.repo,内容如下:[azure ...
- 插件化注解处理API(Pluggable Annotation Processing API)
Java奇技淫巧-插件化注解处理API(Pluggable Annotation Processing API) 参考资料 JDK6的新特性之六:插入式注解处理API(Pluggable Annota ...
- Apache 使用gzip、deflate 压缩页面加快网站访问速度
Apache 使用gzip 压缩页面加快网站访问速度 介绍: 网页压缩来进一步提升网页的浏览速度,它完全不需要任何的成本,只不过是会让您的服务器CPU占用率稍微提升一两个百分点而已或者更少. 原理 ...
- mui 选项卡
方法一:通过css 实现选项卡 <div id="slider" class="mui-slider"> <div id="slid ...
- C++中string.find()函数与string::npos
先说说string::npos参数: npos 是一个常数,用来表示不存在的位置,类型一般是std::container_type::size_type 许多容器都提供这个东西.取值由实现决定,一般是 ...
- jQuery 如何存储,获取和删除 Cookies
jQuery.cookie = function(name, value, options) { if (typeof value != 'undefined') { options = option ...
- 使用 %matplotlib inline 出错?
%matplotlib inline 是一个魔法函数(Magic Functions).官方给出的定义是:IPython有一组预先定义好的所谓的魔法函数(Magic Functions),你可以通过命 ...
- Nginx负载均衡和LVS负载均衡的比较分析(转)
Nginx负载均衡和LVS负载均衡的比较分析 作者:匿名 来源:ChinaZ源码报导 浏览:1032次 2011-12-6 15:12:27 字号:大 中 小 [摘要]Nginx是一个高性能的 HTT ...
- PHP+Redis 实例 页面缓存
前提分析! 上面的图,我分为了三个层级去做页面缓存,其实不一定要三个层面都实现的,如果你做了页面级的,项目初期是够了,作为接口级,基本可以解决很多吞吐量. 对于上面的三个层级,我用了同一个方法去做. ...