33

After hours of tweaking I have settled on this code which allows me to get round the familiar problem of blurry / fuzzy text in Windows 10 on high DPI displays when using Tkinter interfaces in Python 3.

I didn't want to have to set the compatibility flag or expect others to do it and I discovered that by flagging DPI awareness 'on' through a DLL call and then retrieving the DPI setting I could then scale up the GUI window and frames within.

Before passing this to others, however, I wanted to check if my approach of passing 'GUI' (a tkinter.Tk() instance) to the MakeTkDPIAware function in the main body and getting that function to add custom properties to it is a healthy choice or risks causing problems to the tkinter instance. The properties added are then available for use in the main body, but is it safe to assume that will always happen?

I have been able to find out if this practice is a known one - and if it is frowned upon or a poor design choice. (So often in Python, I can be so excited to get something working that I forget to check this type of question at the time), so I hope someone can advise. It seemed the tidiest way to 'remember' the scaling data, rather than creating a new global variable.

I would be very interested to hear if another solution would be more Pythonic.

import re


def Get_HWND_DPI(window_handle):
    #To detect high DPI displays and avoid need to set Windows compatibility flags
    import os
    if os.name == "nt":
        from ctypes import windll, pointer, wintypes
        try:
            windll.shcore.SetProcessDpiAwareness(1)
        except Exception:
            pass  # this will fail on Windows Server and maybe early Windows
        DPI100pc = 96  # DPI 96 is 100% scaling
        DPI_type = 0  # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
        winH = wintypes.HWND(window_handle)
        monitorhandle = windll.user32.MonitorFromWindow(winH, wintypes.DWORD(2))  # MONITOR_DEFAULTTONEAREST = 2
        X = wintypes.UINT()
        Y = wintypes.UINT()
        try:
            windll.shcore.GetDpiForMonitor(monitorhandle, DPI_type, pointer(X), pointer(Y))
            return X.value, Y.value, (X.value + Y.value) / (2 * DPI100pc)
        except Exception:
            return 96, 96, 1  # Assume standard Windows DPI & scaling
    else:
        return None, None, 1  # What to do for other OSs?


def TkGeometryScale(s, cvtfunc):
    patt = r"(?P<W>\d+)x(?P<H>\d+)\+(?P<X>\d+)\+(?P<Y>\d+)"  # format "WxH+X+Y"
    R = re.compile(patt).search(s)
    G = str(cvtfunc(R.group("W"))) + "x"
    G += str(cvtfunc(R.group("H"))) + "+"
    G += str(cvtfunc(R.group("X"))) + "+"
    G += str(cvtfunc(R.group("Y")))
    return G


def MakeTkDPIAware(TKGUI):
    TKGUI.DPI_X, TKGUI.DPI_Y, TKGUI.DPI_scaling = Get_HWND_DPI(TKGUI.winfo_id())
    TKGUI.TkScale = lambda v: int(float(v) * TKGUI.DPI_scaling)
    TKGUI.TkGeometryScale = lambda s: TkGeometryScale(s, TKGUI.TkScale)


#Example use:
import tkinter


GUI = tkinter.Tk()
MakeTkDPIAware(GUI)  # Sets the windows flag + gets adds .DPI_scaling property
GUI.geometry(GUI.TkGeometryScale("600x200+200+100"))
gray = "#cccccc"
DemoFrame = tkinter.Frame(GUI, width=GUI.TkScale(580), height=GUI.TkScale(180), background=gray)
DemoFrame.place(x=GUI.TkScale(10), y=GUI.TkScale(10))
DemoFrame.pack_propagate(False)
LabelText = "Scale = " + str(GUI.DPI_scaling)
DemoLabel = tkinter.Label(DemoFrame, text=LabelText, width=10, height=1)
DemoLabel.pack(pady=GUI.TkScale(70))

2 Answers 2

73

Simply using

from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)

and allowing font size adjustment solved my problems on Windows 10.

Not sure why no one commented or assisted though. The blurry fonts are a pain, I would expect this thread had a debate and a best solution for this Tkinter nuisance..

5
  • 3
    This was perfect, thank you! This information needs to be more widely known - been looking for this solution for months.
    – FoxDot
    Oct 11, 2017 at 12:17
  • 11
    Worked like magic ! For the completion of this answer I just wanted to mention this required additional line "from ctypes import windll"
    – Nir
    Jun 17, 2020 at 6:01
  • This solution was perfect, but is there any solution to fix it by default instead of writing it in every code. Mar 30, 2021 at 10:37
  • Awesome! I'd think that this sort of functionality would come by default but this is quick enough.
    – ArduinoBen
    Apr 11, 2022 at 9:32
  • Just as a sidenote, you need to call windll.shcore.SetProcessDpiAwareness(1) before creating the root windows root = tkinter.Tk()
    – oeter
    Mar 17, 2023 at 15:41
16

Not exactly answering your question but if you want to fix blurriness of tkinter windows on high dpi displays in Windows 10 (Fall Creators update) without having to insert lines of python code:

  1. Find your python.exe and pythonw.exe executables

  2. Right click on python.exe and click "Properties"

  3. Click on the "Compatibility" tab

  4. Click the "Change high DPI settings" button

  5. Tick "Override high DPI scaling behaviour" and select "Application" in the drop-down menu

  6. Repeat for pythonw.exe if necessary

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.