每个会话是否可以通过编程方式启用/禁用DPI缩放?

12
我的应用程序是使用pygame包装SDL编写的Python代码,但我想这可能是一个更普遍的与Windows API有关的问题。在我的一些Python应用程序中,我希望即使在高分辨率下也能在Windows 10下进行像素级控制。例如,如果我的Surface Pro 3具有2160x1440的本机分辨率,则我希望能够以这些尺寸进入全屏模式并呈现完全相同尺寸的全屏图像。这样做的障碍是“DPI缩放”。在Windows的“设置”->“显示”下,默认值为“更改文本、应用程序和其他项目的大小”的值为“150%(推荐)”,结果是我只看到了2/3的图像。我已经发现如何解决这个问题...
  1. 通过将滑块向下移动到100%,可以在整个系统范围内实现(但对于大多数其他应用程序而言是不理想的)。
  2. 只针对python.exepythonw.exe,可以进入这些可执行文件的“属性”对话框,选择兼容性选项卡,并单击“在高 DPI设置下禁用显示缩放”。我可以仅为自己或所有用户执行此操作。我还可以通过以编程方式设置适当的注册表键或通过.exe.manifest文件来自动化此过程(后者似乎还需要全局设置更改,以优先使用外部清单,在其他应用程序可能会产生副作用的情况下)。

我的问题是:我能否在打开图形窗口之前,在每次启动时从内部我的程序中进行此操作?我或使用我的软件的任何人都不一定希望对所有 Python应用程序启用此设置-我们可能只想在运行特定的Python程序时启用它。我想象中可能有一个winapi调用(或在pygame中包装的SDL中的某些内容)可以实现此目的,但到目前为止,我的研究没有结果。


3
SetProcessDpiAwareness是一个Windows API函数,用于将进程设置为支持高 DPI(像素密度)显示。它允许应用程序在高分辨率显示器上呈现更清晰的图像,并且可以避免应用程序的界面因缩放而变得模糊或失真。该函数可用于Windows 8.1及更高版本。 - IInspectable
如果您需要支持Windows 7,请阅读有关向您的EXE添加“清单”资源的信息,如@IInspectable的链接中所述。 - Dave S
3
Windows 7有一个单独的API调用SetProcessDPIAware()。如果你无法完全控制清单文件(因为每个可执行文件只能有一个),则可能需要使用此调用。 - andlabs
FYI,150%并不总是默认值,Windows会根据屏幕大小和EDID数据选择默认值。 - Anders
看一下这个答案——在Windows 10上对我有用! - HelloGoodbye
1个回答

26

这是我一直在寻找的答案,基于 IInspectable 和 andlabs 的评论(非常感谢):

  import ctypes

  # Query DPI Awareness (Windows 10 and 8)
  awareness = ctypes.c_int()
  errorCode = ctypes.windll.shcore.GetProcessDpiAwareness(0, ctypes.byref(awareness))
  print(awareness.value)

  # Set DPI Awareness  (Windows 10 and 8)
  errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
  # the argument is the awareness level, which can be 0, 1 or 2:
  # for 1-to-1 pixel control I seem to need it to be non-zero (I'm using level 2)

  # Set DPI Awareness  (Windows 7 and Vista)
  success = ctypes.windll.user32.SetProcessDPIAware()
  # behaviour on later OSes is undefined, although when I run it on my Windows 10 machine, it seems to work with effects identical to SetProcessDpiAwareness(1)

意识水平的定义如下所述:

typedef enum _PROCESS_DPI_AWARENESS { 
    PROCESS_DPI_UNAWARE = 0,
    /*  DPI unaware. This app does not scale for DPI changes and is
        always assumed to have a scale factor of 100% (96 DPI). It
        will be automatically scaled by the system on any other DPI
        setting. */

    PROCESS_SYSTEM_DPI_AWARE = 1,
    /*  System DPI aware. This app does not scale for DPI changes.
        It will query for the DPI once and use that value for the
        lifetime of the app. If the DPI changes, the app will not
        adjust to the new DPI value. It will be automatically scaled
        up or down by the system when the DPI changes from the system
        value. */

    PROCESS_PER_MONITOR_DPI_AWARE = 2
    /*  Per monitor DPI aware. This app checks for the DPI when it is
        created and adjusts the scale factor whenever the DPI changes.
        These applications are not automatically scaled by the system. */
} PROCESS_DPI_AWARENESS;

我的目标最适合使用第二级别,尽管在系统分辨率/ DPI缩放没有改变的情况下,第一级别也可以使用。

SetProcessDpiAwareness会失败,如果它之前已经针对当前进程进行了调用(包括在进程启动时被系统调用,由于注册表键或.manifest文件引起)。错误代码为-2147024891=0x80070005=E_ACCESSDENIED


1
为了使这个答案完整且有说服力,我会解释为什么你选择了作为 SetProcessDpiAwareness() 参数使用的“魔数”,并可能在代码示例中定义命名常量以表示可能的值。 - zett42
@StefanRickli感谢您的建议修改。byref也适用于您吗?这是我在基于此回答的最新代码中发现的。 - jez

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