Tkinter无法正确识别屏幕分辨率

10

我正在使用一台4K显示器(3840x2160)。

from tkinter import *

root = Tk()

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

print (width, height)

mainloop()
当我运行这段代码时,输出的大小为1536乘以864。请问为什么会这样,并且我该如何解决呢?谢谢。

似乎在这里也发生了(http://stackoverflow.com/questions/17129144/tk-winfo-returns-wrong-screensize-python-2-7-5-on-windows-8)。我不明白为什么(在我的机器上运行良好)。 - TigerhawkT3
这是在Windows上吗?可能是一些高DPI感知标志,意味着Tk正在被缩放。 - patthoyts
是的,它在Windows 10 64位上。我相信Tkinter的默认DPI为72,我的屏幕尺寸为15.6英寸,DPI为282。 - user3196284
很可能您在Windows中配置了一些缩放。由于tkinter不知道dpi缩放(遗留应用程序),因此Windows让它相信实际显示分辨率是真实分辨率除以缩放因子。 - Jean-Marc Volle
3个回答

8

这应该是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))(以及需要传递位置参数的其他函数)时,可能会获得错误的大小。

1
如果由于某种原因,您不想通过编码来解决问题,您可以右键单击python.exe,选择兼容性->更改高DPI设置->勾选“覆盖高DPI缩放行为”,并将其更改为应用程序。 - Arkyo

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。
查看tk代码中的这个片段,你可以看到tk使用LOGPIXELSXLOGPIXELSY来计算屏幕大小(以毫米为单位)。
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)

我很感激你的努力。使用你的解决方案只能让我得到一个与全屏大小相同但分辨率仍然模糊的tkinter窗口。我真的在寻找一种方法,使我的应用程序具有与我的计算机屏幕相同的分辨率。 - Jadon Erwin
@JadonErwin 请参考此答案 - Roland Smith
太好了!谢谢你,我的朋友!我一旦被允许就会给予奖励。 - Jadon Erwin

1
我在我的树莓派上运行了您的代码,并得到了正确的显示值(我的显示器不是4K显示器)。
我没有解决方案,但我观察到你期望/观察到的答案之间的比率是。
3840 / 1536 = 2.5
2160 / 864 = 2.5

也许4K显示屏的屏幕驱动程序会区分真实物理像素(3840x2160)和某种“逻辑像素”的概念。其目的是避免一些软件使用8个真实物理像素来显示8点文字,因为这样会导致不可读。我无法对此进行测试(我没有硬件),这只是一个假设。我可能没有精确的术语。 (顺便说一句,在iOS上有点与像素的概念-您可以搜索这些术语。即使它不能解决您的问题,它也可能是类似的问题)。

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