Find a widget in a window by coordinates - python

I make a window with tkinter and I want to check if a Label is at the coordinates relx = 0.3 and rely = 0.63. Is there any function which I could use?
I already tried to use the nametowidget function, but there I have to give every widget I have a name.
So, the widget I try to get is moving in the window and if it reaches the coordinates I want to move it in another way, but I dont know any function which I could use

You can use winfo_x/y to get the label coordinates and winfo_width/height to get the dimension of the window to compute relative coordinates. I made a little example where the goal is to resize the window to get the right relative coordinates:
import tkinter as tk
def check_label_relpos():
relx = l.winfo_x()/parent.winfo_width()
rely = l.winfo_y()/parent.winfo_height()
if (abs(relx - 0.3) < 0.02) and (abs(rely - 0.63) < 0.02):
print("Ok")
else:
print(relx, rely, "Try again")
parent = tk.Tk()
l = tk.Label(parent, text="Label text", bg="red")
l.place(x=50, y=160)
tk.Button(parent, text="Check label relpos", command=check_label_relpos).place(relx=0.5, rely=1, anchor="s")
parent.mainloop()

Related

What units does the scaling method work in tkinter canvas?

Trying to figure out how the scaling method in the Tkinter Canvas package works.
import tkinter
root = tkinter.Tk()
root.geometry("1200x800")
root.resizable(False,False)
root.title("Examples")
myCanvas = tkinter.Canvas(root, bg="white", height=700, width=1000)
myCanvas.create_rectangle(0,200,300,300, tags="myTag")
myCanvas.scale("myTag", 8200,1200,0.99,0.99)
myCanvas.create_rectangle(0,400,300,300, tags="myTag2")
myCanvas.move("myTag2",200,0)
input_elem = tkinter.Entry(root,width=50)
input_elem.grid(row=1, column=0)
btn = tkinter.Button(root,width=50, text="here", height=5)
btn.grid(row=1, column=2)
myCanvas.grid(row=0, column=0)
root.mainloop()
in the documentation I found this:
.scale(tagOrId, xOffset, yOffset, xScale, yScale) Scale all objects
according to their distance from a point P=(xOffset, yOffset). The
scale factors xScale and yScale are based on a value of 1.0, which
means no scaling. Every point in the objects selected by tagOrId is
moved so that its x distance from P is multiplied by xScale and its y
distance is multiplied by yScale.
This method will not change the size of a text item, but may move it.
Based on this I would have expected the .scale() to work in the same units as the canvas (by default pixels) but it seems like it doesn't. My xOffset value is fairly large and the rectangle moved only very little. So I created a second rectangle to compare and realized it's scaling based off of the width of the canvas, so this :
myCanvas.scale("myTag", (20*0.99)*1000,1200,0.99,0.99)
myCanvas.move("myTag2",(0.99*200),0)
equals to the same xOffset. Why is is the scaling a factor of 10 though? Shouldn't (200*0.99)*1000 in the scaling method equal to 0.99*200 in the move method? Or can someone point me to a more detailed documentation?
I think the best way to understand Canvas move and scale is to see them in action.
Here is a small interactive demo that may help.
It draws a small rectangle (square) at origin.
Control for moving (pick and place) the square is single Button-1 click
Control for scale is via keyboard (Up, Right) = grow (Down and Left) = shrink
Run the code, pick up the square and place it near canvas center.
Make it grow by pressing and holding Up or Right key.
Now make it shrink by pressing and holding Down or Left key.
Try moving mouse pointer inside and outside of square while changing scale.
Note how the square moves away from your mouse pointer when growing
and moves toward your mouse pointer when shrinking.
Exactly what one would expect from objects approaching or receding.
You can change scale and move square at the same time.
This works with objects line, polygon, rectangle and oval.
import tkinter as tk
class moveScale(tk.Tk):
def __init__(self):
super().__init__()
# Remove various paddings so the entire canvas is visible
self.canvas = tk.Canvas(
self, highlightthickness = 0, borderwidth = 0,
cursor = "crosshair")
self.canvas.grid(sticky = tk.NSEW)
self.xx = self.yy = 0 # Store for dynamic mouse pointer position
self.con = False # Flag controls move on|off
self.tag = "myTag"
self.sU = 1.005 # Scale up (growth)
self.sD = 1 / self.sU # Scale down (shrink)
self.canvas.create_rectangle(
0, 0, 40, 40, fill = "", tags = self.tag)
# Mouse and Keyboard bindings
self.canvas.bind("<Motion>", self.mover)
self.canvas.bind("<Button-1>", self.swap)
self.canvas.event_add("<<BIG>>", "<Up>", "<Right>")
self.canvas.event_add("<<GIB>>", "<Left>", "<Down>")
self.canvas.bind("<<BIG>>", self.big)
self.canvas.bind("<<GIB>>", self.small)
# Keyboard input only works when canvas has the focus
self.canvas.focus_force()
# Movement control
def swap(self, ev):
self.con = self.con == False
def mover(self, ev):
x, y = self.canvas.canvasx(ev.x), self.canvas.canvasy(ev.y)
if self.con:
self.canvas.move(self.tag, x - self.xx, y - self.yy)
self.xx, self.yy = x, y
def big(self, ev):
self.canvas.scale(self.tag, self.xx, self.yy, self.sU, self.sU)
def small(self, ev):
self.canvas.scale(self.tag, self.xx, self.yy, self.sD, self.sD)
main = moveScale()
main.mainloop()

Python with tkinter aspect ratio issue

I am attempting to create a simple window that maintains a square shape when resized, using python 3.6.4 and tkinter 8.6. Here is my code that produces a window, but does not maintain its aspect ratio when resized.
import tkinter as tk
w = tk.Tk()
w.aspect(1,1,1,1)
w.mainloop()
maybe you can use a canvas to make that, he detect event (image size changed) and .place relative x and relative y. I tryed make a script to help you, but you need make somes changes
from tkinter import *
# create a canvas with no internal border
canvas = Canvas(bd=0, highlightthickness=0)
canvas.pack(fill=BOTH, expand=1)
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
# track changes to the canvas size and draw
# a rectangle which fills the visible part of
# the canvas
def configure(event):
global lastw, lasth
canvas.delete("all")
w, h = event.width, event.height
try:
label.config(font = ('Arial ', int(12 * ((w - lastw) / (h - lasth))))) # -- this formula need change :3
except ZeroDivisionError: pass
lastw, lasth = canvas.winfo_width(), canvas.winfo_height()
canvas.bind("<Configure>", configure)
label = Label(canvas, text = "YOLO")
label.place(relx = 0.5, rely = 0.5) # - this make the widget automatic change her pos
mainloop()
you can see how this work here http://effbot.org/zone/tkinter-window-size.htm

How to fit Tkinter listbox to contents

I'm adding strings to a listbox using the code below. When I run the code and the window opens, the longer strings get clipped as the window is not large enough (see screenshot). I have tried making the window resizeable and adding scroll bars but I was wondering if there was a way to automatically size it to fit the content.
master = tk.Tk()
listbox = tk.Listbox(master, selectmode=tk.SINGLE)
games = ["Garry's Mod", "Mount and Blade: Warband", "Tekkit"]
for game in sorted(games):
listbox.insert(tk.END, game)
button = tk.Button(master, text="Execute", command=execute)
listbox.pack()
button.pack()
tk.mainloop()
Resetting the listbox width worked for me. I used the Oblivion's answer and noticed that the width is always zero.
listbox = tk.Listbox(master, selectmode=tk.SINGLE)
listbox.config(width=0)
I also recommend to reset the root window geometry after reloading a content of the list. Otherwise if user manually extends a window the window would stop accommodate size of its content.
root.winfo_toplevel().wm_geometry("")
just give width and height 0 as below
listbox.config(width=0,height=0)
tkListAutoWidth.py shows one way to do it.
Edit:
So you might have something along the lines of,
import tkinter as tk
from tkinter import font
class NewListbox(tk.Listbox):
def autowidth(self, maxwidth=100)
autowidth(self, maxwidth)
def autowidth(list, maxwidth=100):
f = font.Font(font=list.cget("font"))
pixels = 0
for item in list.get(0, "end"):
pixels = max(pixels, f.measure(item))
# bump listbox size until all entries fit
pixels = pixels + 10
width = int(list.cget("width"))
for w in range(0, maxwidth+1, 5):
if list.winfo_reqwidth() >= pixels:
break
list.config(width=width+w)
if __name__ == "__main__":
master = tk.Tk()
listbox = NewListbox(master, selectmode=tk.SINGLE)
# ...
# ...
keys = serverDict.keys()
for key in sorted(keys):
listbox.insert("end", key)
listbox.pack()
button = tk.Button(master, text="Execute", command=execute)
button.pack()
listbox.autowidth()
master.mainloop()

How do I align PanedWindows in Tkinter?

I'm new to Tkinter, and finding it a bit rough to get the hang of it. The point of this piece of code is to calculate time using the equation (Time = (Velocity - Initial Velocity) / Acceleration) But I need to take user input for the variables.
Here's what I have so far. It would be great, except for the fact that the labels don't line up with the text widgets. Is there any easy way to do what I need?
def timF():
timPanel = Toplevel()
timPanel.wm_title("Time")
timCont = PanedWindow(timPanel, orient=VERTICAL)
timCont.pack(fill=BOTH, expand=1)
# Top Paned Window and contents #
timTopCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timTopCont)
# Velocity label
timFVelL = Label(timTopCont, text="Velocity")
timTopCont.add(timFVelL)
# Initial Velocity label
timFiveL = Label(timTopCont, text="Initial Velocity")
timTopCont.add(timFiveL)
# Acceleration label
timFaccL = Label(timTopCont, text="Acceleration")
timTopCont.add(timFaccL)
# Bottom Paned Window and contents #
timBotCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timBotCont)
# Velocity entry
timFVelE = Entry(timBotCont)
timBotCont.add(timFVelE)
# Initial Velocity entry
timFiveE = Entry(timBotCont)
timBotCont.add(timFiveE)
# Acceleration entry
timFAccE = Entry(timBotCont)
timBotCont.add(timFAccE)
Just use grid() to place the widgets, instead of pack(). It is the easiest way to do so if you know the concrete row and column of the layout you want to place each widget:
timFVelL.grid(row=0, column=0)
timFVelE.grid(row=0, column=1)
timFiveL.grid(row=1, column=0)
timFiveE.grid(row=1, column=1)
# ...

How do I create a Tiling layout / Flow layout in TkInter?

I want to to fill my window with, say, labels and I want them to wrap once the column would be bigger than the current window (or rather parent frame) size.
I've tried using the grid layout, but then I have to calculate the size of the content of each row myself, to know when to put the next element in the next row.
The reason I ask, is because I want to create some sort of tiled file icons.
Or asked differently, is there something like Swing's FlowLayout for TkInter?
What I do when I want something like this is use the text widget for a container. The text widget can have embedded widgets, and they wrap just like text. As long as your widgets are all the same height the effect is pretty nice.
For example (cut and pasted from the question at the author's request):
textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
Here is a way to make flow behavior inside a frame.
I wrote a function that will do this. Basically you pass a frame to the function (not root or top level) and the function will look at all the children of the frame, go through them measure their sizes and place them in the frame.
Here is the placement procedure
Place the first widget, and move x over an amount equal to its width.
Measure the next widget.
If placing the next widget would cause it to goes past the frame width, bump its x value to 0 and bump it down a y value equal to the largest widget in the current row (start a new row).
Reset the value of the largest widget since you are starting a new row.
Keep repeating until all widgets are placed.
Bind that procedure to the resizing of the frame event.
I used 3 functions to make this work:
The function that runs the procedure.
The function that binds the resizing of the frame to the function.
The function that unbinds the resizing of the frame.
Here are the functions:
from tkinter import *
def _reorganizeWidgetsWithPlace(frame):
widgetsFrame = frame
widgetDictionary = widgetsFrame.children
widgetKeys = [] # keys in key value pairs of the childwidgets
for key in widgetDictionary:
widgetKeys.append(key)
# initialization/priming loop
width = 0
i = 0
x = 0
y = 0
height = 0
maxheight = 0
# loop/algorithm for sorting
while i < len(widgetDictionary):
height = widgetDictionary[widgetKeys[i]].winfo_height()
if height > maxheight:
maxheight = height
width = width + widgetDictionary[widgetKeys[i]].winfo_width()
# always place first widget at 0,0
if i == 0:
x = 0
y = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
# if after adding width, this exceeds the frame width, bump
# widget down. Use maximimum height so far to bump down
# set x at 0 and start over with new row, reset maxheight
elif width > widgetsFrame.winfo_width():
y = y + maxheight
x = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
maxheight = height
# if after adding width, the widget row length does not exceed
# frame with, add the widget at the start of last widget's
# x value
else:
x = width-widgetDictionary[widgetKeys[i]].winfo_width()
# place the widget at the determined x value
widgetDictionary[widgetKeys[i]].place(x=x, y=y)
i += 1
widgetsFrame.update()
def organizeWidgetsWithPlace(frame):
_reorganizeWidgetsWithPlace(frame)
frame.bind("<Configure>", lambda event: _reorganizeWidgetsWithPlace(frame))
_reorganizeWidgetsWithPlace(frame)
def stopOrganizingWidgetsWithPlace(frame):
frame.unbind("<Configure>")
And here is an example of them in use:
def main():
root = Tk()
root.geometry("250x250")
myframe = Frame(root)
# make sure frame expands to fill parent window
myframe.pack(fill="both", expand=1)
buttonOrganize = Button(myframe, text='start organizing',
command=lambda: organizeWidgetsWithPlace(myframe))
buttonOrganize.pack()
buttonStopOrganize = Button(myframe, text='stop organizing',
command=lambda: stopOrganizingWidgetsWithPlace(myframe))
buttonStopOrganize.pack()
##### a bunch of widgets #####
button = Button(myframe, text="---a random Button---")
canvas = Canvas(myframe, width=80, height=20, bg="orange")
checkbutton = Checkbutton(myframe, text="---checkbutton----")
entry = Entry(myframe, text="entry")
label = Label(myframe, text="Label", height=4, width=20)
listbox = Listbox(myframe, height=3, width=20)
message = Message(myframe, text="hello from Message")
radioButton = Radiobutton(myframe, text="radio button")
scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
scrollbar = Scrollbar(myframe)
textbox = Text(myframe, width=3, height=2)
textbox.insert(END, "Text Widget")
spinbox = Spinbox(myframe, from_=0, to=10)
root.mainloop()
main()
Notice:
That you do not need to grid, pack or place them. As long as you specify the frame, that will all be done at once when the function is called. So that is very convenient. And it can be annoying if you grid a widget, then try to pack another, then try to place another and you get that error that you can only use one geometry manager. I believe this will simply overwrite the previous choices and place them. I believe you can just drop this function in and it will take over management. So far that has always worked for me, but I think you should really not try to mix and match geometry managers.
Notice that initially the buttons are packed, but after pressing the button, they are placed.
I have added the "WithPlace" naming to the functions because I have a similar set of functions that do something very similar with the grid manager.

Categories

Resources