I am wondering how I can change the coordinates and size of a button. I know you can do button1.pack(side=RIGHT) but what if I want to say button1.pack(x=100, y=40). I have tried button1.geometry but that hasn't worked.
Answer:
I did button1.place(x=0, y=0) and the button went to the top corner.
Here is the code I used if anybody is curious:
from tkinter import *
t = Tk()
t.title("Testing")
t.geometry("250x250")
MyButton = Button(t, text="Click Me")
MyButton.pack()
def Clicked(event):
MyButton.place(x=0, y=0)
MyButton.bind("<Button-1>" ,Clicked)
MyButton.pack()
t.mainloop()
Tkinter has three layout managers
Grid
Pack
Place
You can use x,y with place() but then rather you shouldn't use other managers which calculate position automatically and they may have problem when you put something manually using pack().
But you may put Frame (using place()) and use other manager inside Frame
pack() and grid() are more popular because they calculate positions automatically when you change size or you use on different system.
Example created few days ago for other question.
Button moves Frame in random position.
EDIT: now Button moves itself to random position and changes height.
import tkinter as tk
import random
# --- function ---
def move():
#b.place_forget() # it seems I don't have to use it
# to hide/remove before I put in new place
new_x = random.randint(0, 100)
new_y = random.randint(0, 150)
b.place(x=new_x, y=new_y)
b['height'] = random.randint(1, 10)
# --- main ---
root = tk.Tk()
b = tk.Button(root, text='Move it', command=move)
b.place(x=0, y=0)
root.mainloop()
Related
I'm trying to set a Tkinter canvas to red/green for one second, then back to white afterward. However, despite the fact that the code setting the canvas to red/green precedes the code reverting back to white, the window doesn't reflect the initial color change. I understand that by calling .after, the program freezes until the specified duration is over, but I don't understand why it doesn't change to red or green before freezing.
if is_correct:
self.canvas.config(bg="green")
else:
self.canvas.config(bg="red")
self.window.after(1000, self.canvas.config(bg="white"))
Refer to this simple program.
from tkinter import *
root=Tk()
def change_bg():
canvas.config(bg="red")
root.after(1000,lambda: canvas.config(bg="white"))
canvas=Canvas(root,bg="white")
canvas.pack()
root.after(1000,change_bg)
root.mainloop()
from tkinter import *
import time
def change_color():
can.config(bg="red")
can.update()
change_color2()
def change_color2():
time.sleep(1)
can.config(bg="white")
root = Tk()
root.geometry("500x500")
can = Canvas(root, bg="white", height=450, width=500)
can.pack()
Button(root, text="Change color for 1 sec", command=change_color).pack()
root.mainloop()
You can refer to this code
I am new to python and I have been learning tkinter recently. So I thought with myself that using the grid_forget() function I can remove a widget and redefine it. I thought of this animation that changes the padding of a label so it would create space (kind of like moving the label but not exactly). However, the animation does not work at all. The program freezes until the label reaches the last value of the padding. How can I fix this? Or is there a better way to animate a label moving in the screen?
Here is my code:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
You need to update the screen for changes to be shown.
Here is a working version using the .update() method:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
root.update()
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
Here is a way I also use to animate stuff on the screen, I am not able to understand what you were trying to achieve with your code snippet above, I tried making some changes to it but I feel this way is much better and let's you get more control of your window.
This uses the widely used Canvas widget in the tkinter library.
The Canvas is a general purpose widget, You can use it for a lot of things. Visit the hyper link for more clarity
Here is a short example of how you would create text on the screen.
from tkinter import *
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = '-----')
c.pack()
# This is all you need to create this text on your screen!
root.mainloop()
The idea is that you put your canvas up on your window , and then place whatever you want on it.
There are a lot more attributes that you can add to make your text look even better. Here is an in-depth tutorial on it.
Now that we have made your text widget, It is now time to move it around. Let us move it to 90,20 From our initial position which is 20,20
Here is how we will do it. If we simply move to text object to 90,90, We won't see any animations, it will just directly have it there. So what we will do is first create it at 21,20. Then 22,20. And so on...
We do this really fast till we reach 90,20
This looks like we are moving the text
from tkinter import *
import time
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = 'weee')
c.pack()
def animation():
y = 0.1
x = 0
for _ in range(1000):
c.move(my_text,x,y)
root.update()
anlabel = Button(root,text = 'Animate!',command = animation).pack()
root.mainloop()
This is not only applicable to text, but everything (like other images)that is there on the canvas. The canvas also has Events which will let you use mouse-clicks and other keys on the computer too.
I have made some changes from the previous code, But it is executable and you can try it for yourself to see how it works. increasing the value in time.sleep() makes the animation slower, the lesser the value, the faster.
Are you sure you aren't trying to do something more like the below example? Animating the padding on one of your widgets is going to screw up the rest of your display.
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='')
lbl.grid(row=0, column=0)
def animation(step=12):
step = 12 if step < 0 else step
lbl['text'] = ' ------ '[step:step+6]
root.after(200, lambda: animation(step-1))
Button(root, text='Animate', command=animation).grid(row=1, column=0, sticky='w')
root.mainloop()
I want to center a tkinter window on the screen, which can be done with:
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}+"
f"{(root.winfo_screenheight()-root.winfo_height())//2}")
This is using the screen width and the width of the window to calculate the upper left corner. However, in order to find out the window width, I have to run root.update() as shown in the following example, which leads to the window showing up at a wrong position for a tiny moment.
import tkinter as tk
root = tk.Tk()
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
# without the following line, the window dimensions are not being calculated
root.update()
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
root.mainloop()
To avoid this, I can think of two solutions:
defining the window size in pixels, which means that the window size does not adjust automatically anymore, and
doing something like root.update() without the window being visible.
I don't know how to avoid the call to update(), but you could initially make the window completely transparent, which would prevent it from even momentarily showing up in the wrong position — thereby granting you the opportunity to position it properly and manually making it visible.
import tkinter as tk
root = tk.Tk()
# This changes the alpha value (how transparent the window should be).
# It ranges from 0.0 (completely transparent) to 1.0 (completely opaque).
root.attributes("-alpha", 0.0)
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
tk.Button(root, text='Quit', command=root.quit).pack()
root.update() # Allow the window's size to be calculated,
# Move it so it's centered on the screen.
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
# Now make it visible.
root.attributes("-alpha", 1.0)
root.mainloop()
The simplest way to achieve a clean window display is shown in the following code.
withdraw the master immediately, create widgets, update master before centering window and finally deiconify.
Works every time.
import tkinter as tk
def screencenter(o):
w, h = o.winfo_width(), o.winfo_height()
x = int((o.winfo_screenwidth() - w) / 2)
y = int((o.winfo_screenheight() - h) / 2)
o.geometry(f'{w}x{h}+{x}+{y}')
master = tk.Tk()
master.withdraw()
# create whatever widgets you need
master.update()
screencenter(master)
master.deiconify()
master.mainloop()
What you're looking for is root.withdraw() and root.deiconify().
The former will hide your window from view and the latter will show it.
I've included a full example below.
from tkinter import Tk
def show_it():
height = root.winfo_height()
width = root.winfo_width()
root.geometry(f"+{(s_width - width)//2}+"
f"{(s_height - height)//2}")
# show it again
root.deiconify()
root = Tk()
s_height = root.winfo_screenheight()
s_width = root.winfo_screenwidth()
root.withdraw()
# hide the window
root.after(200, show_it)
root.mainloop()
In my code, I am trying to make a loading screen for a frogger game but for some reason I am encountering a problem where I display a picture and then do the .sleep function before displaying a label over the top of it however it displays both of them at the same time it just runs the code 1 second after it should, can anyone help?
Here is my code below:
from tkinter import *
import tkinter as tk
import time
window = Tk()
window.geometry("1300x899")
LoadingScreen = PhotoImage(file = "FroggerLoad.gif")
Loading = Label(master = window, image = LoadingScreen)
Loading.pack()
Loading.place(x = 65, y = 0)
time.sleep(1)
FroggerDisplay = Label(master = window, font ("ComicSans",100,"bold"),text = "Frogger")
FroggerDisplay.pack()
FroggerDisplay.place(x = 500, y = 300)
window.mainloop()
When you use time.sleep(1) before starting the window.mainloop(), the window is created only after 1 second, and the FroggerDisplay label will be created at the same time with it. So, you can't use time.sleep(seconds) now.However, you can use window.after(ms, func) method, and place into the function all the code between time.sleep(1) and window.mainloop(). Note, that unlike the time.sleep(seconds) you must give the time to window.after (the first argument) as milliseconds.Here is the edited code:
from tkinter import *
def create_fd_label():
frogger_display = Label(root, font=("ComicSans", 100, "bold"), text="Frogger") # create a label to display
frogger_display.place(x=500, y=300) # place the label for frogger display
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
loading_screen = PhotoImage(file="FroggerLoad.gif") # create the "Loading" image
loading = Label(root, image=loading_screen) # create the label with the "Loading" image
loading.place(x=65, y=0) # place the label for loading screen
root.after(1000, create_fd_label) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
PS: 1) Why do you use .pack(...) and then .place(...) methods at the same time - the first one (.pack(...) here) will be ignored by Tkinter.
2) It's better to use a Canvas widget for creating a game - unlike labels it supports transparency and simpler to use. For example:
from tkinter import *
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
canv = Canvas(root) # create the Canvas widget
canv.pack(fill=BOTH, expand=YES) # and pack it on the screen
loading_screen = PhotoImage(file="FroggerLoad.gif") # open the "Loading" image
canv.create_image((65, 0), image=loading_screen) # create it on the Canvas
root.after(1000, lambda: canv.create_text((500, 300),
font=("ComicSans", 100, "bold"),
text="Frogger")) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
Note: you might need to change coords with Canvas.
I'm trying to add a nice handle to Tkinter.PanedWindow sash. To do that I place a Label with a custom grip image next to a pane. Example:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=1)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg)
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
root.mainloop()
It looks ok. But now the Label overlaps the sash, steals mouse events and I can't resize PanedWindow by dragging the Label. How can I make griplabel ignore mouse events and redirect them all to the PanedWindow sash?
I tried bindtags, but:
griplabel.bindtags(pw.bindtags())
does not seem to do anything, i.e. I still can't drag the Label to resize PanedWindow.
Or is there a better way to create a custom handle for PanedWindow?
With a kind help of #tcl freenode channel I came up with this:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg, cursor="sb_h_double_arrow")
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
griplabel.bind("<Button-1>", lambda e:pw.event_generate("<Button-1>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
griplabel.bind("<B1-Motion>", lambda e:pw.event_generate("<B1-Motion>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
root.mainloop()
Two griplabel.bind(...) calls forward mousedown+mousemove events from Label to PanedWindow, adjusting x and y coords. Those two events are enough to make the sash move.
And griplabel mouse "cursor" is set to sb_h_double_arrow as that's the cursor PanedWindow uses for sash by default, according to the Tk documentation:
Command-Line Name: -sashcursor
Mouse cursor to use when over a sash. If null, sb_h_double_arrow will be used for horizontal panedwindows, and sb_v_double_arrow will be used for vertical panedwindows.
And it's also one of cursor names recognized by Tk on all platforms.
TCL wiki mentions another way to set a custom sash handle bar, using ttk.PanedWindow with custom ttk.Style layout:
from Tkinter import *
import ttk
root = Tk()
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
style = ttk.Style()
style.element_create("Sash.xsash", "image", gripimg, sticky=W+E)
style.layout("MySash.TPanedWindow", [('Sash.xsash', {})])
pw = ttk.PanedWindow(root, orient=HORIZONTAL, style="MySash.TPanedWindow")
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
root.mainloop()
But it looks and works differently. Essentially it replaces background of ttk.PanedWindow with a tiled image, which remains static, and the sash becomes a viewport sliding over it. That looks unusual, still someone may like it.