I want to update the position of a label, so I use .update() method, after I replace it with .place() method. The problem is that all my widgets, that are on my window, get updated and I do not want this, because the program is working harder, and I see lag while "moving" my label. What can I do?
...
def update_label:
l.place(relx = 0.2, rely = 0.1+0.2)
l.update()#here the program is updating every widget
l=tk.Label(root)
l.place(relx = 0.2, rely = 0.1)
b=Button(root,command(update_label()))
b.pack()
...
In fact, I want to replace more than one label in update_label function, but I wanted to make the example easier to understand.
You can use the .update() method, but there are a few things wrong with your code.
First off, you use the tk attribute with the label, but not with the button. Try to be consistent.
I reworked your code and made it cleaner. It now works:
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
x = 0.2
y = 0.1
l = tk.Label(root, text = "label")
l.place(relx = x, rely = y)
def update_label():
global x, y
y += 0.2
l.place(relx = x, rely = y)
l.update()#here the program is updating every widget
b = tk.Button(root,text = "update", command = update_label)
b.pack()
Hope this helps!
Edit:
Writing l.update() won't update or move any other widgets. If you wish to move/update all widgets, then you must put them in the update_label() function.
Hope this helps!
To update the position of a single widget, you can use the place_forget() method to temporarily remove it and then call its place() method (again) with new values to reposition it. Since it appears you want to update the position based on the where the widget currently is, information about the widget's current position is retrieved from it first using the place_info() widget method.
Here's a runnable example based on the code in your question that illustrates what I'm suggesting:
import tkinter as tk
root = tk.Tk()
root.geometry("800x600")
def update_label(lbl):
info = lbl.place_info() # Get dictionary of widget's current place options.
cur_relx = float(info['relx']) # Get current value of relative x.
cur_rely = float(info['rely']) # Get current value of relative y.
lbl_1.place_forget() # Remove widget from current manager.
lbl_1.place(relx=cur_relx, rely=cur_rely+0.2) # Add it back with updated y position.
lbl_1 = tk.Label(root, text='Label 1')
lbl_1.place(relx=0.2, rely=0.1)
lbl_2 = tk.Label(root, text='Label 2')
lbl_2.place(relx=0.2, rely=0.2)
btn_1 = tk.Button(root, text='Update', command=lambda lbl=lbl_1: update_label(lbl))
btn_1.pack()
root.mainloop()
Related
I am placing labels on a Tab in Tkinter with a for loop. How can I identify in the event handler which label was clicked (or its loop index)? I guess it is functionally similar to a ListBox but without the formatting restrictions. I might want to put the labels in a circle or place them diagonally. I tried finding the coordinates of the label but these are available only if the tab is the first one visible or the tab is redrawn when made active. Also the x, y passed in the event handler is the x, y within the label which does not help to identify the label.
I could copy the label code about 10 times and and have about 10 event handlers. This would work but this is no longer 1970!
Perhaps I could bind a handler to the tab canvas and identify the label from its coordinates. The label would need to be on the first tab or the tab drawn when active.
Perhaps I could create a different event handler for each label by holding the event handlers in an array. I would need an event handler for each label. The code would need to change if the number of labels changed.
I am currently trying a label with ''. Would using buttons with command be easier?
What simple part of Python am I missing? I cannot be the first person to need this! Any help or advice would be appreciated.
You can save a reference to the label text for each label widget in a dict.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('+800+50')
notebook = ttk.Notebook(root, width=300, height=200, padding=[10,10,10,10])
notebook.pack()
tab_one = tk.Frame(notebook, bg='wheat')
notebook.add(tab_one, text='Cheese', padding=[10,10,10,10])
tab_two = tk.Frame(notebook, bg='mint cream')
notebook.add(tab_two, text='Misc.', padding=[10,10,10,10])
def clicked(event):
print('Clicked:', name_dict[event.widget])
# Populating Cheese tab with clickable Labels
name_list = ['Cheddar', 'Ilchester', 'Limburger']
name_dict = {}
for index, name in enumerate(name_list):
a = tk.Label(tab_one, width=10, text=name, bg='peru')
a.grid(row=index, column=0, padx=5, pady=5)
name_dict[a] = name # Save references in a dict
a.bind('<Button-1>', clicked)
tk.Label(tab_two, text='Just some text...', bg='powder blue').pack(anchor='nw')
root.mainloop()
Is this what you had in mind?
When you bind events, the function receives an object that includes a reference to the widget that received the event. In the following example, notice how it uses event.widget to refer to the widget that was clicked on.
import tkinter as tk
def update_label(event):
event.widget.configure(text="You clicked me")
root = tk.Tk()
for i in range(10):
l = tk.Label(root, text="Click me", width=20)
l.pack()
l.bind("<1>", update_label)
root.mainloop()
I overlapped 5 Tk.Canvas objects and each will have different images. I want to bring each canvas to front of every other canvases to draw pictures in the most-front canvas.
class window_tk():
def __init__(self,main):
self.main=main
self.canvas_org = tk.Canvas(self.main, bg='white')
self.canvas_layer1 = tk.Canvas(self.main, bg='red')
self.canvas_layer2 = tk.Canvas(self.main, bg='green')
self.canvas_layer3 = tk.Canvas(self.main, bg='blue')
self.canvas_layer4 = tk.Canvas(self.main, bg='black')
self.btn_load = tk.Button(self.main,text = "Load Image",command = self.load_ct)
self.btn_layer1 = tk.Button(self.main,text = "Draw in L1",command = self.bring_1)
self.btn_layer2 = tk.Button(self.main,text = "Draw in L2",command = self.bring_2)
self.btn_layer3 = tk.Button(self.main,text = "Draw in L3",command = self.bring_3)
self.btn_layer4 = tk.Button(self.main,text = "Draw in L4",command = self.bring_4)
def bring_1(self):
self.canvas_layer1.place(x=50,y=00)
def bring_2(self):
self.canvas_layer2.place(x=50, y=00)
def bring_3(self):
self.canvas_layer3.place(x=50, y=00)
def bring_4(self):
self.canvas_layer4.place(x=50, y=00)
I thought the canvas.place() function will bring the canvas front but it was not. Which function can I use ? Or should I unpack all other canvases ?
Since Canvas has override the .tkraise() function, you need to call TCL command directly:
self.canvas.tk.call('raise', self.canvas._w)
Please see the answer given by acw1668. The lift function doesn't work for Canvas objects. His answer is correct.
All tkinter objects, Canvas included, support the following method:
w.lift(aboveThis=None)
If the argument is None, the window containing w is moved to the top of the window stacking order. To move the window just above some Toplevel window w, pass w as an argument.
This gives you full control over which widget sits on top.
https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/universal.html
Now that I re-read that, I see that its language is slightly incorrect. "w" is any tkinter widget, "above_this" is another tkinter widget. The function places "w" above "above_this" in the stacking order.
You can use the following functions -
canvas.tag_raise(canvas_layer4) -> For bringing to front
canvas.tag_lower(canvas_layer4) -> For pushing back
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 wrote a small python function, which takes several numerical input parameters and prints many lines with statements, which going to be used in an experiment, like this toy example:
def myADD(x,y,z):
res = x + y + z
print("the result is: {0}+{1}+{2}={3}").format(x,y,z,res)
I would like to create a minimalistic GUI, simply an overlay which calls my myADD.py script, where I can fill those parameters x,y,z and after clicking a "compute" button a text field occurs with the print statement.
Does anyone has a template, I was looking into the TKinter, but my attempts by manipulating other templates didn't succeed.
Would appreciate help, thanks.
Tkinter is a fantastic choice since it is built-in. It is ideally suited for this type of quick, minimalistic GUI.
Here's a basic framework for a Tkinter app to show you how simple it can be. All you need to do is add your function, either by importing it or including it in the same file:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.entry = {}
# the basic layout is a form on the top, and
# a submit button on the bottom
form = tk.Frame(self)
submit = tk.Button(self, text="Compute", command=self.submit)
form.pack(side="top", fill="both", expand=True)
submit.pack(side="bottom")
# this fills in the form with input widgets for each parameter
for row, item in enumerate(("x", "y", "z")):
label = tk.Label(form, text="%s:"%item, anchor="w")
entry = tk.Entry(form)
label.grid(row=row, column=0, sticky="ew")
entry.grid(row=row, column=1, sticky="ew")
self.entry[item] = entry
# this makes sure the column with the entry widgets
# gets all the extra space when the window is resized
form.grid_columnconfigure(1, weight=1)
def submit(self):
'''Get the values out of the widgets and call the function'''
x = self.entry["x"].get()
y = self.entry["y"].get()
z = self.entry["z"].get()
print "x:", x, "y:", y, "z:", z
if __name__ == "__main__":
# create a root window
root = tk.Tk()
# add our example to the root window
example = Example(root)
example.pack(fill="both", expand=True)
# start the event loop
root.mainloop()
If you want the result to appear in the window, you can create another instance of a Label widget, and change it's value when you perform the computation by doing something like self.results_label.configure(text="the result")
Tkinter is usually a good start because it is bundled with Python (tutorial).
That said, Tk is pretty old and therefore "odd" at times. If you want a more modern UI, have a look at PyQt. It's based on Qt but it doesn't come with Python by default, so you have to install it manually.
I have a list of tkinter widgets that I want to change dynamically.
How to delete the widgets from the window?
You can call pack_forget to remove a widget (if you use pack to add it to the window).
Example:
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=lambda: b.pack_forget())
b.pack()
root.mainloop()
If you use pack_forget, you can later show the widget again calling pack again. If you want to permanently delete it, call destroy on the widget (then you won't be able to re-add it).
If you use the grid method, you can use grid_forget or grid_remove to hide the widget.
One way you can do it, is to get the slaves list from the frame that needs to be cleared and destroy or "hide" them according to your needs. To get a clear frame you can do it like this:
from tkinter import *
root = Tk()
def clear():
list = root.grid_slaves()
for l in list:
l.destroy()
Label(root,text='Hello World!').grid(row=0)
Button(root,text='Clear',command=clear).grid(row=1)
root.mainloop()
You should call grid_slaves(), pack_slaves() or slaves() depending on the method you used to add the widget to the frame.
You simply use the destroy() method to delete the specified widgets like this:
lbl = tk.Label(....)
btn = tk.Button(....., command=lambda: lbl.destroy())
Using this you can completely destroy the specific widgets.
You say that you have a list of widgets to change dynamically. Do you want to reuse and reconfigure existing widgets, or create all new widgets and delete the old ones? It affects the answer.
If you want to reuse the existing widgets, just reconfigure them. Or, if you just want to hide some of them temporarily, use the corresponding "forget" method to hide them. If you mapped them with pack() calls, you would hide with pack_forget() (or just forget()) calls. Accordingly, grid_forget() to hide gridded widgets, and place_forget() for placed widgets.
If you do not intend to reuse the widgets, you can destroy them with a straight destroy() call, like widget.destroy(), to free up resources.
clear_btm=Button(master,text="Clear") #this button will delete the widgets
clear_btm["command"] = lambda one = button1, two = text1, three = entry1: clear(one,two,three) #pass the widgets
clear_btm.pack()
def clear(*widgets):
for widget in widgets:
widget.destroy() #finally we are deleting the widgets.
Today I learn some simple and good click event handling using tkinter gui library in python3, which I would like to share inside this thread.
from tkinter import *
cnt = 0
def MsgClick(event):
children = root.winfo_children()
for child in children:
# print("type of widget is : " + str(type(child)))
if str(type(child)) == "<class 'tkinter.Message'>":
# print("Here Message widget will destroy")
child.destroy()
return
def MsgMotion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
return
def ButtonClick(event):
global cnt, msg
cnt += 1
msg = Message(root, text="you just clicked the button..." + str(cnt) + "...time...")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind("<Button-1>", MsgClick)
msg.bind("<Motion>", MsgMotion)
msg.pack()
#print(type(msg)) tkinter.Message
def ButtonDoubleClick(event):
import sys; sys.exit()
root = Tk()
root.title("My First GUI App in Python")
root.minsize(width=300, height=300)
root.maxsize(width=400, height=350)
button = Button(
root, text="Click Me!", width=40, height=3
)
button.pack()
button.bind("<Button-1>", ButtonClick)
button.bind("<Double-1>", ButtonDoubleClick)
root.mainloop()
Hope it will help someone...
You can use forget method on the widget
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=b.forget)
b.pack()
b['command'] = b.forget
root.mainloop()
I found that when the widget is part of a function and the grid_remove is part of another function it does not remove the label. In this example...
def somefunction(self):
Label(self, text=" ").grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
somefunction.text_ent.grid_remove()
...there is no valid way of removing the Label.
The only solution I could find is to give the label a name and make it global:
def somefunction(self):
global label
label = Label(self, text=" ")
label.grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
global label
somefunction.text_ent.grid_remove()
label.grid_remove()
When I ran into this problem there was a class involved, one function being in the class and one not, so I'm not sure the global label lines are really needed in the above.