C#工作总结(五):Winform与WPF程序处理DPI问题

您所在的位置:网站首页 wpf使用winform控件遮挡 C#工作总结(五):Winform与WPF程序处理DPI问题

C#工作总结(五):Winform与WPF程序处理DPI问题

2023-09-05 01:37| 来源: 网络整理| 查看: 265

一.引子

在Window7中,有一个可以“调整字体大小”的功能。这个功能只要在桌面的空白区域,点击右键,然后在弹出的菜单中选择“个性化”,就会弹出如下的界面。如图1:

图1

图1

点击“显示”之后就会在右侧的区域内看到是“使阅读屏幕上的内容更容易”。在其下有三个选项,分别是“较小(S)-100%(默认)”、“中等(M)-125%和较大(L)-150%”。在正常的状态下,当前屏幕处于“较小(S)-100%(默认)”的这个选项。如下图2:

图2

 

当然如果你不知道这个的功能除了看上面的文字解释。还可以直接尝试换一个选项试一下效果。这里就不多解释它的作用效果是怎么样的了。当你选择之后,在重启过电脑,再次打开系统的时候。首先可以看到屏幕的高宽发生了一些变化,也可以看到屏幕上的内容被放大了很多。

其实在背后,是屏幕的dpi在作怪。

这个除了会影响我们的使用电脑的时候的感受,对于我们开发人员来说,会对我们开发人员的程序造成很大的影响。不但会使程序中的内容大小不统一,还会造成控件显示布局错乱,甚至会造成程序无法正常运行的问题。

下面我们就此问题进行一下研究和讨论。这里我们称这个调整为“SCT

S DPI”调整。

二.目录

在进行很深入的探究之前,我们先对一些比较基本的概念进行一下阐述。以便对之后的讨论更好的叙述。为了使大家看的方便,这边无论内容是多是少,还是分了一个目录进行阐述,以便有一个清晰的条理。

目录

一.引子

二.目录

三.一些基本的概念

四. WPF和Winform的DPI的区别、总结以及一些注意的事项

        1. WPF和Winform在设置了SCTS DPI时的行为可能会不一样

        2.WPF和Winform的SCTS DPI的区别:

        3.WPF和Winform的控件和窗体之间互相嵌套规则:

        4.关于Webbrower在“SCTS DPI”下的放大问题

        5.关于Image在“SCTS DPI”下的放大问题

四.如何获取DPI

        1. Winform环境

        2.WPF环境

五. WPF和Winform的DPI处理事项和技巧

        (1)关于WPF中使用WindowsFormsHost承载Winform控件时大小的问题

        (2)winform中通过获取屏幕大小来限制窗体的全屏显示

        (3)WPF中通过获取屏幕大小来限制窗体的全屏显示

        (3)WPF中可以使用绑定和转换器来处理

三.一些基本的概念

这里有一篇对基础知识讲的很不错的文章,来自博客园杨新华的文章《WPF中的DPI问题》 。

四. WPF和Winform的DPI的区别、总结以及一些注意的事项

这边为了描述方便概念,把上面的个性化中的放大设置统称为“SCTS DPI”,这也是从上面第二点中的文章中“Set Custom Text Size(DPI)”用大写字母的简称。

除此之外,引入一个“Winform域”的概念。就是在Winform窗体或者控件的直级管辖的子控件或者多级管辖但不使用任何WPF的“程序域”叫做“Winform域”。同理“WPF域”也类似定义。下面我就来讲讲相关事宜:

1. WPF和Winform在设置了SCTS DPI时的行为可能会不一样

在说区别之前要明确一个概念,就是你在系统个性化中设置的“100%”、“125%”和“150%”就真的会在WPF和Winform的窗体或者控件中,对应放大“125%”或者“150%”吗?答案当然是不是。

这个“SCTS DPI”在WPF和Winform中会有不同的作用结果。在WPF中,你放大的“SCTS DPI”可以看做是WPF窗体或者控件的放大倍数,由于WPF的机制,使得它的放大完全是按照设置的“125%”或者“150%”的值对应倍数放大。

相对于Winform的话,Winform就不一定会在“125%”或者“150%”的情况下放大成对应的倍数。经过测试发现,在我公司的虚拟机上跑的时候,的确会放大为对应的倍数;而在单机模式下(不使用虚拟机的情况下),无论是选择“125%”和“150%”,Winform的窗体或者控件都不会放大。

最后,经过多次尝试得出如下结论。放大的倍数和在Winform域或者WPF域中的dpi有直接关系,而不是直接等同于选了什么“SCTS DPI”就是放大多少,需要通过下面的计算而得到。有如下公式:

                                                 放大倍数=在Winform域或者WPF域中的dpi / 96

实际上某些程序的行为会影响winform的放大倍数,如下代码:

this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Font = new System.Drawing.Font("Arial", 14F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel, ((byte)(0)));  2.WPF和Winform的SCTS DPI的区别:

(1)首先第一个区别就是,WPF在SCTS DPI下的放大完全是由于WPF的“独立设备单位”进行测量“独立”分辨率计算方式造成的,长和宽在对应的“125%”和“150%”会出现放大对应的1.25倍和1.5倍;而Winform的放大并不是如此,而是系统为了“迎合”看不清文字的“老年人”按照一定的算法规则故意放大的。

(2)WPF在SCTS DPI下,长和宽的放大的系数完全是一致的,即在“125%”的情况下长和宽都会放大1.25倍,而在“150%”的情况下长和宽会放大1.5倍;而Winform在长度上确实也满足这个规则,而在宽度上并不是满足这个规则,而是以另外的一个系数进行放大。WPF和Winform的放大情况如下表1和表2:

表1.不同SCTS DPI下的WInform的放大系数情况表

 

长度(高度)放大系数

宽度放大系数

100%(当前dpi/96)

11125%(当前dpi/96)1.254/3

150%(当前dpi/96)

1.51.5

 

 

 

 

 

表2.不同SCTS DPI下的WPF的放大系数情况表

 

长度(高度)放大系数

宽度放大系数

100%(当前dpi/96)

11125%(当前dpi/96)1.251.25

150%(当前dpi/96)

1.51.5

 

 

 

 

 

注:上面的百分比,在第1点中已经说了,这里是通过dpi的计算得到的。

3.WPF和Winform的控件和窗体之间互相嵌套规则:

(1)Winform中的直接控件的规则按照上面Winform的表的规则来进行放大;

(2)WPF中的直接控件规则按照上面的WPF的表的规则来进行放大;

(3)Winform中有WPF Control,对于WPF Control来说,大小什么按照Winform的规则来处理。而其内部的元素按照WPF的规则来放大。

(4)Winform中有Winform Control,对于他们来说都按照Winform的表来进行放大;WPF中有WPF Control也是同理按WPF规则放大。

(5)WPF中通过了WindowsFormsHost来承载在Winform Control,那么对于WindowsFormsHost来说它会按照WPF的规则来,而里面的Winform Control会按照Winform的规则来进行放大。

(6)无论是控件如何相互嵌套,都会按照(1)~(5)的规则进行解释和处理;

4.关于Webbrower在“SCTS DPI”下的放大问题

无论是WPF还是Winform,在“SCTS DPI”下,Webbrower控件都会按照上面说的规则进行放大和缩小。但是对于Webbrower中的内容来说,它不会受到“SCTS DPI”的影响,即在任何的情况下,都会按照正常的大小显示。

5.关于Image在“SCTS DPI”下的放大问题

对于加载的图片来说,当然是会按照上面讲的规则来进行放大。但是这个不是讨论的重点。由于放大图片的时候,会导致图片失真,所以完全可以使用缩小图片或者使用矢量图的方法来规避这个问题。

四.如何获取DPI

在上面的第三点的讨论中,我们提到了通过dpi的计算来得到,那么我们应该用什么技术来获取dpi呢?下面我们就来讨论各种方法。

DPI的获取要分为WPF和Winform两种环境进行讨论:

1. Winform环境

★(1)通过窗体或者控件的句柄来获得(推荐使用)

首先引入using System.Drawing.dll ;

代码如下:

Graphics currentGraphics = Graphics.FromHwnd(form.Handle); double dpixRatio = currentGraphics.DpiX/96; double dpiyRatio = currentGraphics.DpiY/96;

(2)通过Graphics来获取

首先引入using System.Drawing.dll ;

代码如下:

using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero)) { float dpiX = graphics.DpiX; float dpiY = graphics.DpiY; }

(3)用ManagementClass来获取

首先引入using System.Management.dll;

代码如下:

using (ManagementClass mc = new ManagementClass("Win32_DesktopMonitor")) { using (ManagementObjectCollection moc = mc.GetInstances()) { int PixelsPerXLogicalInch = 0; // dpi for x int PixelsPerYLogicalInch = 0; // dpi for y foreach (ManagementObject each in moc) { PixelsPerXLogicalInch = int.Parse((each.Properties["PixelsPerXLogicalInch"].Value.ToString())); PixelsPerYLogicalInch = int.Parse((each.Properties["PixelsPerYLogicalInch"].Value.ToString())); } } } 2.WPF环境

由于WPF获取句柄的方式和Winform有很大的区别。

所以这边以窗体和控件进行分别的讨论:

(1)WPF窗体获取dpi

WindowInteropHelper winHelper = new WindowInteropHelper((MainWindow)App.Current.MainWindow); IntPtr mainWindowHandle = winHelper.Handle; Graphics currentGraphics = Graphics.FromHwnd(mainWindowHandle); double currentDpiX = currentGraphics.DpiX; double currentDpiY = currentGraphics.DpiY; double dpiXRatio = currentDpiX / 96;    double dpiYRatio = currentDpiY / 96;   

注:这种方法必须有App.xaml的存在才可以使用。

(2)WPF控件获取dpi

通过当前WPF控件对象获取

代码如下:

PresentationSource source = PresentationSource.FromVisual(this); double dpiX, dpiY; if (source != null) { dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11; dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22; } double dpixRatio = dpiX/96; double dpiyRatio = dpiY/96;

注:由于使用了PresentationSource.FromVisual,所以必须在加载完控件之后才能使用。即最好放在Control的Loaded事件中使用。

通过WPF的空间的句柄获得

代码如下:

IntPtr hwnd = ((HwndSource)PresentationSource.FromVisual(wpfControl)).Handle Graphics currentGraphics = Graphics.FromHwnd(hwnd); double dpixRatio = currentGraphics.DpiX/96; double dpiyRatio = currentGraphics.DpiY/96;

注:同中的注意要点。

★无任何的前提的方法(来自StackFlow)(推荐使用)

 代码如下:

var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static); var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static); var dpiX = (int)dpiXProperty.GetValue(null, null); var dpiY = (int)dpiYProperty.GetValue(null, null); var dpixRatio = dpiX/96; var dpiyRatio = dpiY/96;

 

五. WPF和Winform的DPI处理事项和技巧

当通过程序获得dpi的放大系数之后(即除以96),我们可以对应上面的表格获得Winform或者WPF下面,长和宽的不同的放大系数(一定要WPF和Winform分两次获得dpi)。

在获得系数之后,就可以通过除以相应的系数来实现对窗体大小的还原。在这个过程中有的地方需要注意。

(1)关于WPF中使用WindowsFormsHost承载Winform控件时大小的问题

在承载时,要在Winform控件中使用如下的代码进行调整:

代码如下:

this.AutoScaleMode = AutoScaleMode.Inherit; this.BackgroundImageLayout = ImageLayout.Stretch;

注:如果不使用,那么Host中的空间会出现变形的问题。而且放大的大小比放大之后来的更加的大。

(2)winform中通过获取屏幕大小来限制窗体的全屏显示

代码如下:

System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height (3)WPF中通过获取屏幕大小来限制窗体的全屏显示

代码如下:

SystemParameters.WorkArea.Size.Width SystemParameters.WorkArea.Size.Height

注:返回当前屏幕工作区的宽和高(除去任务栏)

(3)WPF中可以使用绑定和转换器来处理

转换器代码如下(转载自博客园水手《WPF 屏蔽DPI改变对程序的影响的解决方案》):

DPIConverter类

[ValueConversion(typeof(object), typeof(object))] public class DPIConverter : IvalueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { WindowInteropHelper winHelper = new WindowInteropHelper((MainWindow)App.Current.MainWindow); IntPtr mainWindowHandle = winHelper.Handle; Graphics currentGraphics = Graphics.FromHwnd(mainWindowHandle); double currentDpiX = currentGraphics.DpiX; double dpiXRatio = currentDpiX / 96;    if (targetType == typeof(GridLength)) { double data = double.Parse(parameter.ToString()); GridLength gridLength = new GridLength(data / dpiXRatio, GridUnitType.Pixel); return gridLength; } if (targetType == typeof(Thickness)) { Thickness margin = (Thickness)parameter; if (margin != null) { margin.Top = margin.Top / dpiXRatio; margin.Left = margin.Left / dpiXRatio; margin.Right = margin.Right / dpiXRatio; margin.Bottom = margin.Bottom / dpiXRatio; return margin; } } if (targetType == typeof(double)) { double fontSize = double.Parse(parameter.ToString()); return fontSize / dpiXRatio; } if(targetType==typeof(System.Windows.Point)) { System.Windows.Point point=(System.Windows.Point)parameter; point.X=point.X/dpiXRatio; point.Y=point.Y/dpiXRatio; return point; } return null; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } }

上端使用先用资源字典引入直接进行绑定即可

代码如下:

Xaml

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3