8

I'd like my application to be able to detect if it's running on a HiDPI screen, and if so, scale itself up so as to be usable. As said in this question, I know I need to set a scaling factor, and that this factor should be my DPI divided by 72; my trouble is in getting my DPI. Here's what I have:

def get_dpi(window):
    MM_TO_IN = 1/25.4
    pxw = window.master.winfo_screenwidth()
    inw = window.master.winfo_screenmmwidth() * MM_TO_IN
    return pxw/inw

root = Tk()
root.tk.call('tk', 'scaling', get_dpi(root)/72)

This doesn't work (testing on my 4k laptop screen). Upon further inspection, I realized get_dpi() was returning 96.0, and that winfo_screenmmwidth() was returning 1016! (Thankfully, my laptop is not over a meter wide).

I assume that TkInter is here calculating the width in mm from some internally-detected DPI, wrongly detected as 96, but I'm not sure where it's getting this; I'm currently on Linux, and xrdb -query returns a DPI of 196, so it's not getting the DPI from the X server.

Does anyone know a cross-platform way to get my screen DPI, or to make TkInter be able to get it properly? Or, more to the point: how can I make TkInter play nice with HiDPI screens and also work fine on normal ones? Thanks!

charliegreen
  • 494
  • 1
  • 5
  • 11

3 Answers3

9

This answer is from this link and left as a comment above, but it took hours of searching to find. I have not had any issues with it yet, but please let me know if it does not work on your system!

import tkinter
root = tkinter.Tk()
dpi = root.winfo_fpixels('1i')

The documentation for this says:

winfo_fpixels(number)
# Return the number of pixels for the given distance NUMBER (e.g. "3c") as float

A distance number is a digit followed by a unit, so 3c means 3 centimeters, and the function gives the number of pixels on 3 centimeters of the screen (as found here). So to get dpi, we ask the function for the number of pixels in 1 inch of screen ("1i").

Andrew Pye
  • 558
  • 3
  • 16
5

I know I'm answering this question late, but I'd like to expand upon @Andrew Pye 's idea. You are right, GUI's with tkinter look different across different monitors with different DPI's anytime you use a 'width' or 'height' or 'pady' or anything that is measured in pixels. I noticed this when I made a GUI on my desktop, but then later ran the same GUI on my 4K laptop (The window and the widgets appeared much smaller on the laptop). This is what I did to fix it, and it worked for me.

from tkinter import *

ORIGINAL_DPI = 240.23645320197045   # This is the DPI of the computer you're making/testing the script on.

def get_dpi():
    screen = Tk()
    current_dpi = screen.winfo_fpixels('1i')
    screen.destroy()
    return current_dpi

SCALE = get_dpi()/ORIGINAL_DPI    # Now this is the appropriate scale factor you were mentioning.

# Now every time you use a dimension in pixels, replace it with scaled(*pixel dimension*)
def scaled(original_width):
    return round(original_width * SCALE)

if __name__ == '__main__':
    root = Tk()
    root.geometry(f'{scaled(500)}x{scaled(500)}')    # This window now has the same size across all monitors. Notice that the scaled factor is one if the script is being run on a the same computer with ORIGINAL_DPI. 
    root.mainloop()
Gabe Morris
  • 804
  • 4
  • 21
1

I'm using TclTk, not TkInter, and the only way I know how to do this is to work it out from the font metrics...

% font metrics Tk_DefaultFont -ascent 30 -descent 8 -linespace 38 -fixed 0

The linespace is approximately 0.2x the DPI (currently set to 192 here)

  • (Oops, this answer got formatted with the output from my command "font metrics Tk_DefaultFont" in the same line as the command itself, sorry for confusion) – Jasper Taylor Aug 12 '19 at 10:05