当前位置:编程学习 > VB >>

[原创]高DPI下界面错乱的解决方法和原理

这是一个界面问题,所以发到控件区-_-!

  先继续说个普遍现象吧。许多程序软件(包括金山毒霸,搜狗浏览器,江民,微软自身,大脚在内软件,在高DPI环境下均出现界面混乱、显示残缺的现象)。并且据微软统计,所有用户中约有45%的用户采用高DPI(高于默认96DPI)显示(虽然表示怀疑)。这就出现了个严重的问题,原本在96DPI下编写的程序,一旦到了非96DPI的客户机中,就会出现界面问题。如果你长期做客户端软件开发(哪怕小工具,外挂等等,只要有收集反馈的机制和达到一定用户量时),不难发现,收到越来越多的客户投诉此类问题。


  后来查询了MSDN和看了CSDN里面的帖子,都是来询问的,没有合适的解决方式。

【实验:】
一般很少或者不开发产品的朋友可能不会发现。你可以在标准96DPI(一般情况下都是)下,新建一个窗体,载入一个小图片,窗体大小调整为刚好能容纳下图片。生成EXE文件。修改DPI值为120(即125%缩放,XP下桌面-属性-高级里面找,WIN7>屏幕分辨率->放大或缩小其他项目->中等)。系统会要求注销,注销后再进入系统运行EXE程序。马上会看到恶心的结果:窗体被扩大,而图片大小依旧,造成多出来的窗体部分空白。丑陋。如果在加上几个其他空间,丑陋效果更明显。


【普及个知识:】
屏幕坐标计量单位,分为逻辑和物理坐标(单位),物理坐标为像素点、厘米等;逻辑坐标为缇、DPI等DPI是指单位面积内像素的多少。例如,在96默认DPI下1英寸屏幕有96点像素,如果在120DPI下1英寸有120个像素点。缇是VB6中默认的计量单位,96DPI下15缇等于1像素,120DPI下12缇等于1像素。因此,就造成了不同DPI下窗体控件大小会随着DPI值增加而放大,原本设计时就比较大的窗体在高DPI下甚至会超出屏幕边界,还有其他更多

问题。



【解决方式:百度了半天】
软件产品用户渐渐增加,越来越多的用户反映界面混乱错位的问题(当然,他们不知道什么是DPI)。甚至出现了吐槽者。百度了半天,发现这问题不少,但均无人能解决,或者说懂的都捂着假装不知道吧。找不到答案。翻阅了微软MSDN,它的解决办法是,让程序识别DPI,之后屏蔽一个修改系统DPI的函数,大概是这个意思,代码居然是用NET命名空间的,无VB6或者VC6代码。又去逛了CSDN,发现有三个人提问过,均无满意正解答案。换关键词百度谷歌无数,什么都没有。无奈去请教大学界面设计老师,老师说:我们知道有这种问题,但是一般都禁止高DPI的机器运行,故意提示不兼容。- -!!



【解决思路】
断断续续想了很久,经过研究,终于发现导致界面错乱的所有原理原因和解决办法。
1.原理原因:界面错乱的关键在于DPI变化后,系统会将程序可视化界面和控件按比例放大。那么使用逻辑坐标(计量单位)就变得不可靠,因为他们都是通过物理单位换算而成,均会受到DPI变化影响,特别是VB6计量单位--缇。完全就是于DPI有着直接的换算关系(96DPI下15缇等于1像素,120DPI下12缇等于1像素)。那么什么单位才可靠呢?答案是--像素、厘米、毫米!锁住界面大小不让缩放的最终就是要锁定三个东西:高度像素值、宽度像素值、控件所位于窗体内的坐标像素值


2.以上这三者被锁定固定大小,界面就将“稳定”不再受到系统DPI干扰。那么:缇(逻辑单位)--像素(物理单位)--DPI(逻辑单位) 这三者间又是怎么换算的?公式还是:96DPI下15缇等于1像素,120DPI下12缇等于1像素;DPI每增加1,就放大1.041666倍。(1440/DPI值=X,X缇=1像素;1.041666是1.014666666666667的约值,如何计算活动省略,知道即可)


3.不仅仅界面和控件放大了,连字体都给放大了!96DPI下的9磅(默认)字体,同比120DPI下的9磅字体小了很多很多。要固定到96DPI 9磅字体的大小,就要使用到这上面这句“DPI每增加1,就放大1.041666倍”。判断当前客户机DPI,减去标准的96,所得值乘以1.041666,所得值为倍数,单位(%)。如120DPI-96=24,24X1.041666%约等于25%,再加上1就是125%。即120DPI比96DPI放大了125%



【总结】
96DPI下获得窗体、控件高度宽度和坐标(left,top)的缇数。除以15便是其物理单位---像素值。之后到客户机上,立即获得其DPI值,获得值为X,将1440除以X获得一个值。这时,窗体、控件高度宽度坐标(LEFT,TOP)缇数就应该为:(原缇数/15)*(1440/现DPI值);(解释:)这样所得到的就是设计时窗体、控件高宽和所在坐标的物理值(像素),之后再根据客户机DPI将这个物理值计算成合适的新缇数。


【结束】
可以给客户们交差了。吐槽的终于有结果。如果朋友们还没发现这个问题,可能是你们没有做大用户量的客户端程序或者没有收集用户反馈吧。一旦用户多,这问题还不少。还有一种可能!就是高手在潜水!



【注意事项】
1.DPI设置方式:(win7:桌面->屏幕分辨率->放大或缩小其他项目->中等/较小。这两个选择,较小为96默认DPI,中等为120DPI)WINXP设置差不多如此。
2.一定要在标准默认96DPI下编写程序设置好界面后,在其他数值的DPI环境下调试才能成功。



【已解决的问题】
1.锁定窗体、控件大小
2.锁定到96DPI下9磅默认字体的大小
3.自适应各种不同DPI



【未解决的问题、缺陷】
1.字体大小锁定误差过大(±0.2磅),仔细对比还是看得出来的
2.窗体边缘和窗体标题栏依旧被放大,变宽变厚
3.DLL参数使用变体类型,VC等其他语言估计调用有难度


【代码部分】
WIN32_DLL部分(使用amicSetup插件)。这里把锁定界面功能写成标准DLL,以便程序调用方便。包含在附件包
EXE调用部分,也包含在附件包
两个源码均有非常详细的注释,调用方法行行注释。



【交流讨论】
  首先,看帖回帖是美德。就针对于这个问题,如果有需要或者想交流共同解决这个问题,请回复即可。如有任何疑问和问题均可直接回复。
联系邮箱:thfyhome@163.com
【附件/源码、测试例子下载地址】
http://www.thfyhome.com/dpi.rar
--------------------编程问答-------------------- 楼主辛苦……学习了…… --------------------编程问答-------------------- 附件没法下载呀 --------------------编程问答-------------------- 把界面元素的layout都做成运行后调整的,比如在formload事件中.
然后针对不同DPI作几个不同的layout配置, 程序运行时先检测DPI再加载相应的layout.

还有一种就是将所有的界面元素排版统统用窗体位置比例来加载.
当然这也需要在程序加载时计算比例.

最最简陋的方案是按照高DPI+系统大字体来配置界面.
其他DPI和字体方案的环境中有时会显得丑陋,但是绝对不会被遮挡内容,保证了使用.


以上三种方案我都用过, 也都没啥难度. --------------------编程问答-------------------- 楼主写的真不错。。。。。。。。学习了。。 --------------------编程问答-------------------- Dpi_Ratio.BAS
---------------------------------------------
Option Explicit
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long

Public DpiRatio As Single

Public Sub DpiRatioIni()
    Dim DPI As Long
    DPI = GetDeviceCaps(GetDC(0), 88)
    DpiRatio = 1440 / DPI / 15
End Sub

FORM
--------------------------------------
Private Sub Form_Initialize()
    On Error Resume Next
    Call DpiRatioIni
    If DpiRatio <> 1 Then
        With Me
            .Left = .Left * DpiRatio
            .Width = .Width * DpiRatio
            .Top = .Top * DpiRatio
            .Height = .Height * DpiRatio
            .FontSize = .FontSize * DpiRatio
        End With
        
        Dim obj
        For Each obj In Controls
            With obj
                .Left = .Left * DpiRatio
                .Width = .Width * DpiRatio
                .Top = .Top * DpiRatio
                .Height = .Height * DpiRatio
                .FontSize = .FontSize * DpiRatio
            End With
        Next
    End If
End Sub --------------------编程问答-------------------- Option Explicit
Private Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As Any) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long

Private Sub Form_Initialize()
    On Error Resume Next
    Dim DC0 As Long, DPI As Long, DpiRatio As Single
    DC0 = CreateDC("DISPLAY", vbNullString, vbNullString, 0)
    DPI = GetDeviceCaps(DC0, 88)
    If DPI <> 0 Then
        DpiRatio = 1440 / DPI / 15
        If DpiRatio <> 1 Then
            With Me
                .Left = .Left * DpiRatio
                .Width = .Width * DpiRatio
                .Top = .Top * DpiRatio
                .Height = .Height * DpiRatio
                .FontSize = .FontSize * DpiRatio
            End With
            
            Dim obj
            For Each obj In Controls
                With obj
                    .Left = .Left * DpiRatio
                    .Width = .Width * DpiRatio
                    .Top = .Top * DpiRatio
                    .Height = .Height * DpiRatio
                    .FontSize = .FontSize * DpiRatio
                End With
            Next
        End If
    End If
End Sub
补充:VB ,  控件
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,