10

I'm using a 4k display (3840x2160).

from tkinter import *

root = Tk()

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

print (width, height)

mainloop()

When I run this code the output is 1536 by 864

Could someone explain why this is happening, and how I can fix it, Thanks.

  • Seems to happen [here](http://stackoverflow.com/questions/17129144/tk-winfo-returns-wrong-screensize-python-2-7-5-on-windows-8) too. I don't understand why (works fine on my machine). – TigerhawkT3 Apr 03 '16 at 03:14
  • Is this on Windows? Might be some high-DPI aware flag that means Tk is being scaled. – patthoyts Apr 03 '16 at 09:22
  • Yes it is on Windows 10 64-bit. I believe the default DPI for Tkinter is 72, my screen being 15.6 inch would have 282 DPI. –  Apr 03 '16 at 20:14
  • Most probably you have some scaling configured in windows. As tkinter is not aware of dpi scaling (legacy apps) Windows make it believe the actual display resolution is the real one divided by the scaling factor. – Jean-Marc Volle Apr 21 '20 at 11:41

3 Answers3

7

This should be the problem of DPI aware,Read this in MSDN official document.

In windows 10: You need to use SetProcessDpiAwareness(Or SetThreadDpiAwarenessContext),Try to use:

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()

Requirements of SetProcessDpiAwareness

In Windows XP or 7,you need to use SetProcessDPIAware()

So all the code might be:

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()

If you use tk.call('tk', 'scaling'),it is also okay.but when you use ImageGrab.grab((xxxx))(And those functions which need to pass the position argument),maybe it will get the wrong size.

jizhihaoSAMA
  • 12,336
  • 9
  • 27
  • 49
  • 1
    If for some reason you don't want to solve it via code, you can right click the python.exe, Compatibility -> Change high DPI settings -> check the box 'Override high DPI scaling behaviour.' and change it to Application. – Arkyo May 24 '22 at 22:14
2

Looking at the problem, it seems to be a defect in ms-windows, and tk not using the known workaround for it.

Looking at the source code for tk, in the file win/tkWinX.c the function TkWinDisplayChanged uses the windows API call GetDeviceCaps to get the screen width and height with the parameters HORZRES and VERTRES.

Unfortunately, this page states (emphasis mine):

Note GetDeviceCaps reports info that the display driver provides. If the display driver declines to report any info, GetDeviceCaps calculates the info based on fixed calculations. If the display driver reports invalid info, GetDeviceCaps returns the invalid info. Also, if the display driver declines to report info, GetDeviceCaps might calculate incorrect info because it assumes either fixed DPI (96 DPI) or a fixed size (depending on the info that the display driver did and didn’t provide). Unfortunately, a display driver that is implemented to the Windows Display Driver Model (WDDM) (introduced in Windows Vista) causes GDI to not get the info, so GetDeviceCaps must always calculate the info.

I would rephrase that as follows; "after windows Vista, display information from GetDeviceCaps is not to be trusted".

There there is this page about high-dpi displays. This page uses some other GetDeviceCaps values, LOGPIXELSX and LOGPIXELSY (the numbers of pixels per "logical inch" in x and y) to calculate the real window size. The "default" DPI that GetDeviceCaps seems to use is 96.

Look at this fragment of tk code from win/tkWinX.c, you can see that tk uses LOGPIXELSX and LOGPIXELSY to calculate the screen size in 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);

This value seems to be used to calculate winfo_fpixels() So I think that if you use root.winfo_fpixels('1i') you will get a reliable DPI value.

So, try this:

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)

Edit A workaround is to set tkinter's scaling factor:

factor = dpi / 72
root.tk.call('tk', 'scaling', factor)
Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • I appreciate the effort. Using your solution only gets me a tkinter window the same size as full screen, but still the same grainy resolution. I am really looking for a way to get my application to have the same resolution as my computer screen. – Jadon Erwin Apr 20 '20 at 22:22
  • @JadonErwin See also [this answer](https://stackoverflow.com/a/34133102/1219295). – Roland Smith Apr 20 '20 at 22:27
  • That works! Thanks my friend! I'll give that bounty over as soon as I'm allowed. – Jadon Erwin Apr 20 '20 at 22:36
1

I ran you code on my Raspberry pi, and got the correct value for my display (which is not a 4K display).

I do not have the solution, but I observe that the ratio between your expected/observed answers are

3840 / 1536 = 2.5
2160 / 864 = 2.5

Maybe the screen driver for a 4K display makes a difference between real physical pixels (3840x2160) and some concept of "logical pixels". The purpose would be to avoid some software to display, for example, a 8-point text with 8 real physical pixels, since that would be unreadable.

I cannot test this (I do not have the hardware), it is only a hypothesis. I also may not have the exact terminology.

(BTW, on iOS there are the concepts of points vs pixels--you can search for these terms. Even if it doesn't answer your problem, it may be a similar problem).

Sci Prog
  • 2,651
  • 1
  • 10
  • 18