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!
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").
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()
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)
Related
I´m trying to figure out how to set the size of my tkinter canvas to bigger then actually my screen is. My screen is 1920x1280, if I set in the following code any higher numbers, the size never gets above this (and I want to do it due to huge drawing there).
Code:
from tkinter import *
class Draw:
def __init__(self, min, max):
self.min = min
self.max = max
def draw(self):
master = Tk()
w = Canvas(master, width=2500, height=2500)
#...
I also tried master.geometry("2500x2500") but that didn´t work either.
You can't make windows larger than the physical screen. However, if your goal is to create a large drawing, you can do that without making the canvas physically large. The canvas widget is just a viewport into a much larger virtual drawing area.
For example, you can create a canvas that is only 400x400 pixels, yet draw an image that is 4000x4000. You can define the size of the virtual window by setting the scrollregion attribute to whatever size you want (up to a limit which I think is maybe around 64000x64000)
This happens because of your screen constraint, you cannot run 2500px * 2500px window on your 1920px * 1280px screen, if you try to run a window 1920px * 1280px on your screen, it would work.
This happens because your limits (2500px * 2500px) is too big for your monitor. The window tries to make 7250000 pixel on you 2457600 pixel screen!
So you would have to get a better screen possibly a 3k or 4k screen to run this.
None of the tkinter Scales I've made show tickmarks.
I tried pasting other people's code into my editor and same result, no ticks. The example is utterly simple so I couldn't think of anything to try except different values for the tickinterval option. I'm using tkinter 8.6 with python 3.5. Thanks for any assistance.
import tkinter as tk
root = tk.Tk()
flt = tk.Scale(
root,
label="No Ticks",
from_=50.0,
to=200.0,
length=900,
orient="horizontal",
resolution=25,
tickinterval=50.0)
flt.grid()
root.mainloop()
There are no error messages and everything else seems to work.
The option tickinterval does not refer to tick marks but to the numbers displayed along the Scale.
Once this was pointed out by Bryan Oakley in the comments, I was able to glean the following information from running simple examples, which I had not found in the docs. Basically, the resolution option and the tickinterval option values need to be considered together for both to work as desired.
tickinterval
Using the default value of zero, the only number shown will be the one which names the position of the slider. Otherwise the tickinterval will be the distance apart of numbers marking positions on the Scale. To be sure tickintervals will be displayed as desired, this should be set to an even multiple of resolution e.g. if resolution = 100, tickinterval should be 200
or 300 etc. If resolution is set to 0 or -1, tickinterval will display at any increment desired.
I am working on a project where I have to return number of millimeters or centimeters or whatever traversed by a mouse.
Now, whoops, this is a bit tricky as every screen is different.
I found a nice function: wx.DC.GetSizeMM() that should have solved this problem for me.
My hopes went up, but it gives me some extremely dubious results.
On my PC under Windows:
>>> app = wx.App()
>>> dc = wx.ScreenDC()
>>> dc.GetSize() # Get resolution in pixels:
wx.Size(1680, 1050)
>>> dc.GetSizeMM() # Now this is completely off as these should be mm we're looking at:
wx.Size(432, 270)
>>>
My screen is at least 1 cm wider than dc.GetSizeMM() thinks it is.
For a: wx.MemoryDC(wx.EmptyBitmap(100, 100)).GetSizeMM() I get wx.Size(25, 25).
I mean, come on, completely ridiculous!
Edit: OK, maybe not so completely. :D
It is obvious that aspect ratio is fine, but what factor is used to convert?
What am I missing? Why doesn't it work?
With tkinter canvas, to calculate the size of the graphics I display, I normally use the function winfo_screenwidth(), and size my objects accordingly.
But when used on a system with two monitors, winfo_screenwidth() returns the combined width of both monitors -- which messes up my graphics.
How can I find out the screen width in pixels of each monitor, separately?
I have had this problem with several versions of Python 3.x and several versions of tkinter (all 8.5 or above) on a variety of Linux machines (Ubuntu and Mint).
For example, the first monitor is 1440 pixels wide. The second is 1980 pixels wide. winfo_screenwidth() returns 3360.
I need to find a way to determine the screenwidth for each monitor independently.
Thanks!
It is an old question, but still: for a cross-platform solution, you could try the screeninfo module, and get information about every monitor with:
import screeninfo
screeninfo.get_monitors()
If you need to know on which monitor one of your windows is located, you could use:
def get_monitor_from_coord(x, y):
monitors = screeninfo.get_monitors()
for m in reversed(monitors):
if m.x <= x <= m.width + m.x and m.y <= y <= m.height + m.y:
return m
return monitors[0]
# Get the screen which contains top
current_screen = get_monitor_from_coord(top.winfo_x(), top.winfo_y())
# Get the monitor's size
print current_screen.width, current_screen.height
(where top is your Tk root)
Based on this slightly different question, I would suggest the following:
t.state('zoomed')
m_1_height= t.winfo_height()
m_1_width= t.winfo_width() #this is the width you need for monitor 1
That way the window will zoom to fill one screen. The other monitor's width is just wininfo_screenwidth()-m_1_width
I also would point you to the excellent ctypes method of finding monitor sizes for windows found here. NOTE: unlike the post says, ctypes is in stdlib! No need to install anything.
I am currently remaking flappy bird in Tkinter. (I understand this is bad, I explain why at the bottom.) My issue is with the pipes, and the speeds they scroll at and the distance they are from each other. Unless something is wrong with my logic, if a start the two pipes separated from each other then move them when they get to a certain point, and place them at the same point, they should retain the gap between them. This may be better explained in code.
from tkinter import *
import random
root = Tk()
root.geometry('430x640')
root.configure(background='turquoise')
canvas = Canvas(root,width=int(435),height=int(645))
canvas.configure(background='turquoise')
canvas.pack()
x, x2 = 400, 700
y = random.randint(0,300)
y2 = random.randint(0,300)
def drawPipe():
global x,x2,y,y2
canvas.coords(pipeTop,(x,0,(x+50),y))
canvas.coords(pipeBottom,(x,640,(x+50),(y+150)))
canvas.coords(pipeTop2,(x2,0,(x2+50),y2))
canvas.coords(pipeBottom2,(x2,640,(x2+50),(y2+150)))
x -= 3
x2 -= 3
if x < -46:
x = 435
y = random.randint(5,540)
if x2 <-46:
x2 = 435
y2 = random.randint(5,540)
root.after(1,drawPipe)
pipeTop = canvas.create_rectangle(x,0,(x+50),y,fill='green')
pipeBottom = canvas.create_rectangle(x,640,x+50,y+150,fill='green')
pipeTop2 = canvas.create_rectangle(x2,0,(x2+50),y,fill='green')
pipeBottom2 = canvas.create_rectangle(x2,640,(x2+50),(y2+150),fill='green')
drawPipe()
root.mainloop()
This is not my full code, but it is the bit concerned with drawing and updating the pipes. When run, this code will show you how the pipes scrolling speed up and down. I do not understand how this is possible. All the values for the pipes are the same apart from the starting positions. Is this due to the inefficient way Tkinter uses the after method? I attempted to use threading but this produced problems when using root.bind (see my previous question). Or is it due to a logic error? Thank you in advance to anyone who can help me.
Side note: I realise I should not be making a game in tkinter, especially one that requires multiple things to be happening at once. However, I am doing this at school and the modules I would like to use (Pygame or Pyglet) cannot be downloaded just for me to make a game that has no real purpose. If I could use something other than tkinter I probably would. Thank you for your help.
Using after(1,..) you get 1000FPS (Frames Per Second) but you don't need it - use after(20, ...) to get 50 FPS.
Beside using after(1,..) your program have no time to do other things - it have no time to execute all after() so you can get different speed.
With after(1,..) I couldn't even move window.
And my CPU became hotter so fan started working faster and louder.