I'm trying to place widgets using the .place() method. I understand that x and y are measured from the top left side of the page, but I'm trying to change it, so it can measure it from the top right.
My reason being, I would like the label to always be a constant distance from the right side of the page. I've tried relx and rely but unfortunately it didn't work for me, and for some reason neither is the anchor.
Can anyone help with this?
Here's my code:
from tkinter import *
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('500x500')
root.configure(bg='blue')
label = Label(root,text='Hello',anchor='nw')
label.place(x=50,y=50)
root.mainloop()
Try giving x and y co-ordinates to widgets, something like this,
import tkinter as tk
root=tk.Tk()
root.geometry('300x300')
b1=tk.Button(root,text="b1",width=12)
b1.place(relx = 1, x =-2, y = 2, anchor = 'ne')
b2=tk.Button(root,text="b2",width=12)
b2.place(relx = 1, x =-2, y = 30, anchor = 'ne')
root.mainloop()
hope this helps you!
Related
Met strange problem : canvas.bbox('ALL') gives None.
SO topics say that canvas only have coords to give when there some is .create_xxx() methods in code. The thing is that class FigureCanvasTkAgg in its __init__ method use create_image method and I think in my little snippet, when I create instance of thic Class it should give me some coordinate box when call instance.bbox command. But it's not...
self.Frame = Frame(root, bg = 'white')
self.Frame.place (relx = 0.37 , rely = 0.05 , relheight = 0.85 , relwidth = 0.51)
self.canvas = FigureCanvasTkAgg ( fig , master = self.Frame) # A tk.DrawingArea.
self.canvas.get_tk_widget ().place ( relx = 0 , rely = 0)
self.canvas.get_tk_widget ().config ( yscrollcommand = self.vbar.set ,
scrollregion = (0,0,w,639*h/10 ))
self.canvas.get_tk_widget ().update_idletasks ()
print(self.canvas.get_tk_widget ().winfo_width(),self.canvas.get_tk_widget ().winfo_height() )
self.Frame.update_idletasks ()
print(self.canvas.get_tk_widget ().bbox('ALL'))
self.toolbar = NavigationToolbar2Tk ( self.canvas , self.Frame )
self.vbar.pack ( side = RIGHT , fill = Y)
Met strange problem : canvas.bbox('ALL') gives None.
When you use 'ALL', it is looking for all canvas objects with the tag 'ALL' and not finding any.
If you want the bounding box of all objects, the correct argument to bbox is 'all', not 'ALL'. The literal string "all" is treated as a special case by the canvas to represent all objects on the canvas.
#BryanOakley 's answer is really an important clarification.
The excellent documentation by the late John Shipman, unfortunately, does have an error for present-day tkinter. It stated that:
.bbox(tagOrId=None)
Returns a tuple (x1, y1, x2, y2) describing a rectangle that encloses all the objects
specified by tagOrId. If the argument is omitted, returns a rectangle enclosing all objects
on the canvas. The top left corner of the rectangle is (x1, y1) and the bottom right corner
is (x2, y2).
Presently, if canvas.bbox() is used, tkinter returns _tkinter.TclError: wrong # args: should be ".!xxxxxxx bbox tagOrId ?tagOrId ...?"
There are online tkinter documents that advocate using ALL. An example is this one. However, most readers would have been oblivious that the from tkinter import * statement had been used to import all tkinter objects, which is also not in line with PEP8 guidelines on import statements. A good practice for importing tkinter is to use import tkinter as tk. Following this, your statement should either be:
print(self.canvas.get_tk_widget().bbox(tk.ALL))
or
print(self.canvas.get_tk_widget().bbox('all'))
Summarising: Either use .bbox(tk.ALL) or .bbox('all') on a tk.Canvas object/instance.
I'm using tkinter to animate a gif using guidance from the following pages:
Tkinter animation will not work
However I cannot get the suggestions to work. Right now I have something like this
import tkinter as tk
root = tk.Tk()
frames = [tk.PhotoImage(file = '/home/quantik/Pictures/dice.gif',format="gif -index %i" %(i)) for i in range(5)]
def animate(n):
if (n < len(frames)):
label.configure(image=frames[n])
n+=1
root.after(300, animate(n))
label = tk.Label(root)
label.pack()
root.after(0, animate(0))
root.mainloop()
However it just displays the last image in frames I've tried several methods but I keep getting the same result. Does anyone have any suggestions as to why this may be happening?
This code:
root.after(300, animate(n))
is exactly the same as this code:
result = animate(n)
root.after(300, result)
Notice what is happening? You don't want to call the function, you want to tell after to call it later. You do that like this:
root.after(300, animate, n)
That tells root.after to call the animate function after 300 milliseconds, and to pass the value of n as an argument to the function.
I ran into a problem, since pulling options["arrowshape"] from the tkinter canvas resulted in a string "x y z", while setting the arrowshape in create_line will use a tuple [x,y,z] and not a string...
Is this correct in Python 2.7.10+
This is how you create an arrow:
from tkinter import *
root = Tk()
can = Canvas(root, bg='white')
ar = can.create_line(5, 5, 100, 70, arrow='last', arrowshape='20 40 10')
can.pack()
root.mainloop()
You need to pass a string (or a list or a tuple) representing the shape of the arrow head. The first is the length, the last is the width, and the middle one relates to the amount of arc the arrow base has. You can play with it to get the taste.
You can designate the arrow shape also by
arrowshape=[20, 40, 10]
or
arrowshape=(20, 40, 10)
I have an issue in that I can compact this code (I've been told I can) but did not receive any help in doing so and don't have a clue how to do it.
Tried putting this into a for loop, but I want a 3x3 grid of buttons with the middle one being a listbox instead, dead centre of the buttons.
I have looked around, and after an hour, I got no answer.
Here I tried appending each button to a list and packing them in a for loop, but is it possible to do them each in a for loop and the Listbox done and packed separately after?
class MatchGame(Toplevel):
def __init__(self,master):
self.fr=Toplevel(master)
self.GameFrame=Frame(self.fr)
self.AllButtons=[]
self.AllButtons.append(Button(self.GameFrame,bg="red",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="green",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="dark blue",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="turquoise",height=5,width=15,text=""))
self.AllButtons.append(Listbox(self.GameFrame,bg="grey",height=5,width=15))
self.AllButtons.append(Button(self.GameFrame,bg="yellow",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="pink",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="orange",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="purple",height=5,width=15,text=""))
for x in range(0,len(self.AllButtons)):
AllButtons[x].grid(row=int(round(((x+1)/3)+0.5)),column=x%3)
self.GameFrame.grid(row=0,column=0)
Quit=Button(self.fr, text="Destroy This Game", bg="orange",command=self.fr.destroy)
Quit.grid(row=1,column=0)
It needs to have individual colours, same size and all that, but I don't know what to do. I'm fairly new to classes, and I can't work out for the life of me how to make this window with compact code (not 9 lines for each object, then packing them all.)
If you want to dynamically create a 3x3 grid of buttons. Then a nested for-loop seems to be your best option.
Example:
import tkinter as tk
root = tk.Tk()
# List of your colours
COLOURS = [['red', 'green', 'dark blue'],
['turquoise', 'grey', 'yellow'],
['pink', 'orange', 'purple']]
# Nested for-loop for a 3x3 grid
for x in range(3):
for y in range(3):
if x == 1 and y == 1: # If statement for the Listbox
tk.Listbox(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
else:
tk.Button(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
root.mainloop()
Is there a way to get the position of the mouse and set it as a var?
You could set up a callback to react to <Motion> events:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
I'm not sure what kind of variable you want. Above, I set local variables x and y to the mouse coordinates.
If you make motion a class method, then you could set instance attributes self.x and self.y to the mouse coordinates, which could then be accessible from other class methods.
At any point in time you can use the method winfo_pointerx and winfo_pointery to get the x,y coordinates relative to the root window. To convert that to absolute screen coordinates you can get the winfo_pointerx or winfo_pointery, and from that subtract the respective winfo_rootx or winfo_rooty
For example:
root = tk.Tk()
...
x = root.winfo_pointerx()
y = root.winfo_pointery()
abs_coord_x = root.winfo_pointerx() - root.winfo_rootx()
abs_coord_y = root.winfo_pointery() - root.winfo_rooty()
Personally, I prefer to use pyautogui, even in combination with Tkinter. It is not limited to Tkinter app, but works on the whole screen, even on dual screen configuration.
import pyautogui
x, y = pyautogui.position()
In case you want to save various positions, add an on-click event.
I know original question is about Tkinter.
I would like to improve Bryan's answer, as that only works if you have 1 monitor, but if you have multiple monitors, it will always use your coordinates relative to your main monitor. in order to find it relative to both monitors, and get the accurate position, then use vroot, instead of root, like this
root = tk.Tk()
...
x = root.winfo_pointerx()
y = root.winfo_pointery()
abs_coord_x = root.winfo_pointerx() - root.winfo_vrootx()
abs_coord_y = root.winfo_pointery() - root.winfo_vrooty()