在Windows Vista和Windows 7中,解决DPI虚拟化和DPI感知应用程序的问题

18
我有一个问题,我的一个应用程序(用Delphi编写)在所有系统默认96 DPI设置下都表现正常,但在“150%文本大小”(内部144 dpi)设置下在不同的系统上表现不一致。似乎在某些系统上,我的应用程序的某些文本/字体部分被拉伸了,在其他系统上则没有。我本来认为我的应用程序在某个版本的Windows(Win7)上,在特定DPI下,应该以相同的方式运行。
要么我的应用程序会向Windows表明它不需要DPI虚拟化功能,要么不需要。这一点我理解得很清楚。但我不理解的是,DPI更改如何导致两台机器上的外观不同,这两台机器都运行Windows 7,都在144 dpi下,以相同的固定大小显示相同的字体和窗体。
在DPI虚拟化中是否涉及某些配置相关元素,需要我在Windows中进行检查(注册表等)?否则,您如何排除故障并知道客户端窗口是否正在进行DPI虚拟化?
在Delphi中,如果不想进行缩放,则必须将TForm.Scaled属性设置为false。但我不理解的是,当主窗体的Scaled属性为true时,我无法始终预测结果。
在我的应用程序中最令我困惑的是,我有一个控件在我的大型实际应用程序中表现不良,但在我试图调试该控件的独立应用程序中没有表现不良。为了了解独立应用程序中控件的行为,我被迫制作一个演示应用程序,通过清单文件强制进行DPI感知。然后我可以复制控件绘制故障,尽管以不同的形式。
以下是我在演示应用程序中使用的清单文件,它显示了我的控件在Windows高DPI设置下处理问题的情况。然而,我发现一个奇怪的事情是,一个应用程序可能会...
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
 <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings
         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
  <assemblyIdentity version="14.0.3615.26342" processorArchitecture="*"            
   name="TestProject" type="win32"></assemblyIdentity>
  <description>High DPI Controls Test App</description>
</assembly>

这是我的应用程序中约30个控件被搞乱的其中一个例子,当我在应用程序中禁用DPI虚拟化时。这个特别的问题可以通过关闭表单中的Scaled属性来解决。但是在其他地方,设置TForm.Scaled=false会导致问题,而在某些表单中,它会修复问题:

带有非DPI虚拟化应用程序清单的DPI故障示例,但主窗体已缩放

更新:事实证明,我的一些控件使用了GDI +,在GDI +上下文中的字体行为与正常GDI上下文中的字体行为不同,至少对于某些使用GDI +的第三方控件来说是如此。这是头疼的主要原因。 其次,在VCL中,对于DPI感知的测试覆盖率和定义不清晰。一些VCL控件基于MS Common Controls,并且虽然可以说底层公共控件在高DPI情况下可能工作正常,但并不能保证所有VCL控件包装器都能正常工作。因此,审查应用程序以确保其所有控件都具有高DPI感知性以及在所有可用的windows 7主题中的正确行为:

  1. 启用了aero glass,在96dpi下(在大多数现代硬件上的默认Win7外观)
  2. 基本主题(关闭了aero glass),但启用了xp主题
  3. 经典的win2000外观,其中玻璃关闭了,以及xp级别的主题,
  4. 高对比度白色
  5. 高对比度黑色
  6. 各种非96-DPI设置

......列表还在继续。作为应用程序开发人员,你有相当沉重的负担。无论你是Delphi用户并使用VCL,还是MFC / ATL C ++开发人员,似乎支持所有各种奇特的Windows模式是一个几乎难以承受的负担。因此,大多数人不会费心。我是对的吗?

4个回答

11

您需要使用以下类似的部分来表明您的应用程序是DPI感知的:

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
    <dpiAware>true</dpiAware>
  </asmv3:windowsSettings>
</asmv3:application>

如果您这样做,就不会得到DPI虚拟化。
您不应该使用DPI虚拟化,因此我认为尝试弄清楚它的工作原理没有多大意义。它很容易依赖于显卡驱动程序。我们几乎无法解释虚拟化为什么会表现出这种行为:您甚至没有提供任何屏幕截图、硬件详细信息等等。但是,您根本不应该费心诊断此问题。将其表现为dpiaware,这不是一个问题。
供您参考: 编写高DPI Win32应用程序 http://www.rw-designer.com/DPI-aware

这是否意味着,当我使用此清单时,Delphi将忽略窗体的PixelsPerInch属性? - user532231
1
@daemon_x 不,它并不意味着那样。阅读文章以了解其含义!它们比我讲得更好。 - David Heffernan
这是一个相当棘手的问题。主要是 TMS AdvSmoothListBox 出现了难以解释的严重故障。字体似乎被放大了很多,而项目的高度没有改变,导致严重的“剪裁”。 - Warren P
1
@Warren 你用这种奇怪的自定义组件,现在吃亏了吧!! ;-) TListBox 真是太好用了! - David Heffernan
事实证明,TMS Smooth控件(顺便说一下非常流畅)使用GDI+。在同一窗体中,GDI+和高DPI环境与常规基于GDI的VCL控件以一些令人惊讶(并不总是美观)的方式相同。 - Warren P
显示剩余3条评论

9

实际上这是一个不同的问题。

您的表单不应该随着用户的 DPI 增大而变大,而应该随着字体大小的增大而变大。

Delphi 的默认表单字体8pt Tahoma
平均 8pt Tahoma 字符的大小为: 6.08px * 13px

从 Windows Vista 开始,默认字体是 9pt Segoe UI
平均 9pt Segoe UI 字符的大小为: 6.81px * 15px

enter image description here

您的 Windows 应用程序应该遵循用户的字体偏好设置(如 IconTitleFont)。

我的 Windows 字体偏好设置是 12pt Segoe UI,其 平均 字符大小为:8.98px * 21px

enter image description here

这意味着如果您使用 Tahoma 8pt(高度为 13px)设计了您的表单,则需要将表单及其所有子控件缩放 162%:

scaleFactor = (userFontSize / designedFontSize)
            = 21px / 13px
            = 1.615

如果你仔细观察,就会发现改变DPI只是改变字体大小的一种特殊情况。你的8pt字体仍然是8pt,但是8pt会转换成更多的像素。如果你运行131dpi(Windows 7中的136%缩放设置),那么:

9pt Segoe UI,  96dpi = 6.81px x 15px
9pt Segoe UI, 131dpi = 8.98px x 21px

enter image description here

注意: 我选择 131dpi12pt 作为示例,这不是巧合。在工作中我使用 96dpi 和 12pt,在家中则使用 9pt 和 131dpi。两者的像素字体高度都相同,为 21px。


最终需要根据大小差异调用 ScaleControl

procedure StandardizeFormFont(Form: TForm);    
var
   iconTitleFontName: string;
   iconTitleFontSizePixels: Integer;
   currentFontSizePixels: Integer;
begin
   GetIconTitleFont(iconTitleFontName, iconTitleFontSizePixels);

   //Change font face first
   //Some fonts are inheriently taller than others
   //(even at the same point size)
   Form.Font.Name := iconTitleFontName;     

   //Get the form's current font height (in pixels)
   currentFontSizePixels := Form.Font.Height; //negative number, but so is iconTitleFontSizePixels

   Form.ScaleBy(iconTitleFontSizePixels, currentFontSizePixels);
end;

实际上,这段代码非常简单。需要手动更新许多子控件:

  • 列表视图列需要变宽
  • ParentFont = false 的控件需要调整字体
  • TImage 控件需要将其图像拉伸到新的大小
  • 工具栏需要使用更大的图像

实际上,我们使用了一个超级高级版本的 StandardizeFormFont,递归地遍历窗体上的所有控件,并尽力根据每个控件的情况进行调整。

每个 Delphi 控件都应该重写其 ScaleControl 方法,并进行必要的调整:

protected
   procedure ChangeScale(M, D: Integer); override;

7
“自定义 DPI 设置”窗口有一个“使用 Windows XP 样式的 DPI 缩放”。这可能解释了不同的行为。

+1 这很有道理。然而,这又是一个避免它的理由!并不是我过于执着于这一点! - David Heffernan
很好的观点。有什么想法可以通过编程方式读取吗? - Warren P
我加了这张图片,因为我之前完全不知道有这个选项。 - Warren P
2
Windows Vista增加了新的兼容选项。与其相信应用程序可以在高dpi设置下工作,它们只是欺骗应用程序(告诉你正在以96dpi运行),并使用视频卡为您缩放所有表单。这个新的有用功能可以通过提供dpiAware清单条目(如David所指出的)或恢复到“旧”的Windows XP样式DPI缩放(没有dpi缩放!)来关闭。 - Ian Boyd

6

原来当系统DPI值改变不再是默认的96 DPI值时,我的应用程序出现了一些问题,这些问题大致可以分为三类:

  1. 一些应用程序控件使用GDI,而另一些控件则使用GDI+。在我使用的控件中,GDI和GDI+字体在不同DPI下呈现有所不同。
  2. 我使用Delphi框架中的VCL。在这个Delphi VCL框架中,一些窗体具有TForm.Scaled=true,而另一些则具有TForm.Scaled=false。因为需要考虑缩放后每个控件的情况,所以在缩放窗体中很常见会发生你作为UI设计师会觉得“丑陋”或不可接受的事情。关闭缩放后,你会得到一个被Windows 7本身拉伸的窗体,在高DPI设置下(DPI虚拟化模式),或者看起来很小,因此忽略了用户对你的96 DPI UI的144 DPI版本的“请求”。其他人可能在其他语言中使用其他框架,甚至可能使用像Visual C++的对话框编辑器这样的过时工具,在其中使用“对话框单位”设计对话框,这是另一种试图将通用对话框布局与像素的1:1对应分离的方式。缩放、拉伸和布局控件是必须按照平台要求解决的UI设计的一般部分。在我的情况下,VCL非常好地让我设计一个96 DPI的玻璃使能的Aero应用程序,并且在其他DPI评级上表现良好,但是我的大多数自定义控件都不行。因此,如果你关心高DPI支持,当你尝试使高DPI支持工作时,你的工作会更加困难,这就是坚持使用VCL提供的控件的另一个原因:如果你关心高DPI支持,你的工作会更加困难。
  3. DPI虚拟化受到显式包含在应用程序中的清单设置的控制。由于我的应用程序已经有了一个自定义清单(而不是在项目设置中点击启用Windows主题复选框时包含的那个),我能够打开和关闭这个DPI虚拟化,并在两种情况下测试我的应用程序。我发现我的应用程序没有准备好在没有DPI虚拟化的情况下运行,因此我将Windows恢复到其默认行为。如果其他人使用100% VCL控件,并且窗体使用Form缩放或其他技术适当调整大小(如DevExpress的VCL ExpressLayout控件)以适应各种字体大小和DPI设置,则他们的应用程序很容易在禁用DPI虚拟化的情况下工作。在我看来,最终VCL足够功能强大,但是对于真正的工业级解决方案,需要一个比VCL更先进的框架,以全面处理像“高DPI环境”这样的问题,并且第三方控件通常不能像当前的VCL在这些情况下工作得那么好。这样的框架问题在WPF框架(Microsoft)和Java(wing)中非常明显,但不是VCL的经典“Win16/Win32公共控件框架”的一部分。
总的来说,现在这些变化并不比旧时代复杂,当时Windows XP和其他版本的Windows提供了“字体大小”的选择,而现在,Windows 7 UI体验试图将字体大小选项深深地隐藏起来,取而代之的是一个“文本大小”更改用户界面,在表面下修改系统DPI。这两种改变用户体验的方式都会导致几乎每个用户都遇到至少一个主要商业应用程序出现外观或功能问题。随着超高点距显示器在消费PC领域越来越普及,这个问题可能会变得越来越严重,需要围绕更合适的框架进行UI设计。

2
点1基本上归结为GDI字体不是完全线性缩放的事实:如果您将高度按_f_因子更改,则新字符串的宽度不会完全是原始字符串宽度的_f_倍。这是由于hinting,当度量与像素大小相同时,使字体更易读。使用GDI+,hinting通常被禁用,这导致线性缩放(或至少更线性)。缺点是GDI+文本可能会有点模糊,因为它更依赖于抗锯齿。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接