tkinter 无法正确识别屏幕分辨率(tkinter not recognizing screen resolution correctly)

问题

我使用的是 4k 显示器 (3840x2160)。

from tkinter import *

root = Tk()

width = root.winfo_screenwidth()
height = root.winfo_screenheight()

print (width, height)

mainloop()

当我运行此代码时,输​​出为 1536 x 864

有人可以解释为什么会发生这种情况,以及如何解决它,谢谢。

回答1

这应该是DPI感知的问题,在MSDN官方文档中阅读。

在 windows 10 中:需要使用 SetProcessDpiAwareness(或 SetThreadDpiAwarenessContext),尝试使用:

import tkinter as tk
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2) # your windows version should >= 8.1,it will raise exception.

root = tk.Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
print(width,height)
root.mainloop()

SetProcessDpiAwareness 的要求

在 Windows XP 或 7 中,您需要使用 SetProcessDPIAware()

所以所有的代码可能是:

import tkinter as tk
import ctypes
try:
    ctypes.windll.shcore.SetProcessDpiAwareness(2) # if your windows version >= 8.1
except:
    ctypes.windll.user32.SetProcessDPIAware() # win 8.0 or less 

root = tk.Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
print(width,height)
root.mainloop()

如果你使用tk.call('tk', 'scaling') ,也可以。但是当你使用ImageGrab.grab((xxxx)) (以及那些需要传递位置参数的函数)时,也许它会得到尺寸错误。

回答2

查看问题,这似乎是 ms-windows 中的一个缺陷, tk没有使用已知的解决方法。

查看tk的源代码,在文件win/tkWinX.c中,函数TkWinDisplayChanged使用 Windows API 调用GetDeviceCaps以参数HORZRESVERTRES获取屏幕宽度和高度。

不幸的是,这个页面状态(强调我的):

注意GetDeviceCaps报告显示驱动程序提供的信息。 如果显示驱动程序拒绝报告任何信息, GetDeviceCaps会根据固定计算来计算信息。 如果显示驱动程序报告无效信息,GetDeviceCaps 将返回无效信息。 此外,如果显示驱动程序拒绝报告信息,GetDeviceCaps 可能会计算不正确的信息,因为它假定固定 DPI (96 DPI) 或固定大小(取决于显示驱动程序提供和未提供的信息)。 不幸的是,实现到 Windows 显示驱动程序模型 (WDDM)(在 Windows Vista 中引入)的显示驱动程序会导致 GDI 无法获取信息,因此 GetDeviceCaps 必须始终计算信息。

我将改写如下; “在 windows Vista 之后,来自GetDeviceCaps的显示信息不可信”。

有这个关于高 dpi 显示器的页面。 此页面使用其他一些GetDeviceCaps值、 LOGPIXELSXLOGPIXELSY (x 和 y 中每“逻辑英寸”的像素数)来计算实际窗口大小。 GetDeviceCaps似乎使用的“默认”DPI 是 96。

查看来自win/tkWinX.ctk代码片段,您可以看到tk使用LOGPIXELSXLOGPIXELSY来计算以 mm 为单位的屏幕尺寸。

void
TkWinDisplayChanged(
    Display *display)
{
    HDC dc;
    Screen *screen;

    if (display == NULL || display->screens == NULL) {
      return;
    }
    screen = display->screens;

    dc = GetDC(NULL);
    screen->width = GetDeviceCaps(dc, HORZRES);
    screen->height = GetDeviceCaps(dc, VERTRES);
    screen->mwidth = MulDiv(screen->width, 254,
          GetDeviceCaps(dc, LOGPIXELSX) * 10);
    screen->mheight = MulDiv(screen->height, 254,
          GetDeviceCaps(dc, LOGPIXELSY) * 10);

这个值似乎是用来计算winfo_fpixels()所以我认为如果你使用root.winfo_fpixels('1i')你会得到一个可靠的 DPI 值。

所以,试试这个:

import tkinter as tk

root = tk.Tk()

width = root.winfo_screenwidth()
height = root.winfo_screenheight()
dpi = root.winfo_fpixels('1i')

real_width = int(width * dpi / 96)
real_height = int(height * dpi / 96)

print('real width should be', real_width)
print('real height should be', real_height)

编辑一种解决方法是设置tkinter的缩放因子:

factor = dpi / 72
root.tk.call('tk', 'scaling', factor)
回答3

我在我的 Raspberry pi 上运行了你的代码,并为我的显示器(不是 4K 显示器)获得了正确的值。

我没有解决方案,但我观察到您的预期/观察到的答案之间的比率是

3840 / 1536 = 2.5
2160 / 864 = 2.5

也许 4K 显示器的屏幕驱动程序会在真实的物理像素 (3840x2160) 和一些“逻辑像素”的概念之间产生差异。 目的是避免某些软件显示,例如,具有 8 个实际物理像素的 8 点文本,因为那将是不可读的。

我无法对此进行测试(我没有硬件),这只是一个假设。 我也可能没有确切的术语。

(顺便说一句,在 iOS 上有点与像素的概念——你可以搜索这些术语。即使它不能回答你的问题,也可能是类似的问题)。


更多相关内容:请点击查看