My problem with my code is that I want to update my list during the delay and make the label appear twice with different values. I want b to appear first and then d will appear later. But it seems that only d is displayed now.
Is there any ways that I can go around it?
Any help is much appreciated!
import Tkinter as tk
a_list=["a","b","c"]
root=tk.Tk()
variableone=tk.StringVar()
label1=tk.Label(root,text="appear later")
label1.pack()
label=tk.Label(root, textvariable=variableone)
label.after(2000, lambda:variableone.set(a_list[1]))
label.pack()
a_list[1]="d"
label.after(5000, lambda:variableone.set(a_list[1]))
root.mainloop()
Your problem is due to your lambdas using the current value of a_list[1] when they're called, not the value it had when thelambdas were created. You can get around that by giving them a default argument, since that's only evaluated at creation time.
This is a common problem when using lambdas as callback functions. See Python Tkinter, setting up button callback functions with a loop for a similar problem using lambdas to set up a series of Button widgets in a loop.
This code does what you want. It uses s as the default argument to hold the value of a_list[1].
import Tkinter as tk
a_list=["a","b","c"]
root=tk.Tk()
variableone=tk.StringVar()
label1=tk.Label(root,text="appear later")
label1.pack()
label=tk.Label(root, textvariable=variableone)
label.after(2000, lambda s=a_list[1]:variableone.set(s))
label.pack()
a_list[1]="d"
label.after(5000, lambda s=a_list[1]:variableone.set(s))
root.mainloop()
Related
This seems so simple but I can't figure out what I need to do to remedy. I have a tkinter project and on a button press, a function runs that takes several seconds. I want a "loading..." type message while the function is running so it's obvious it's actually working and not crashed. I figured a label would be easy enough and on the first line of the function, have label1.set('loading') but I suppose because of the way functions work, the label doesn't set until the function is done running--which is not helpful.
I made a second short function
def update_status(message):
label1.set(message)
and for the button in tkinter, used command=lambda:[update_status('loading'),search()] in hopes that the update_status() function would run first, alter the label, and then the second search() function that takes upwards of 30 seconds would run. But I get the same effect.
What's the simplest way finish running the update_status() function--thereby updating my label acting as the "status", and THEN run the time consuming search() function?
I'm not opposed to something more complicated like a loading window or something similar, but just wanted something simple (I have not even googled any type of loading window--I'm mostly hung up on how to get 2 functions to run on a button click in a sequential order).
Hey I do not think you need 2 functions to do what you want. You simply have to update your root so that the label is directly updated.
Here is an example:
import tkinter as tk
import time
def update_status(message1, message2):
var.set(message1)
root.update()
time.sleep(5)
var.set(message2)
if __name__ == '__main__':
root = tk.Tk()
root.title("Wait for function")
var = tk.StringVar()
var.set('Waiting for input')
label1 = tk.Label(root, textvariable=var)
label1.pack()
Button1 = tk.Button(root, text="Wait", command=lambda:update_status('loading', 'done'))
Button1.pack()
root.mainloop()
I have a scale and an input field which both control the same variable to give the user choice of which one they'd like to use. I've coded it a bit like this:
def scale_has_moved(value):
entry_field.delete(0, END)
entry_field.insert(0, str(float(value)))
# Other functions I want the code to do
def entry_field_has_been_written(*args):
value = float( entry_field.get() )
scale.set(value)
This works, when I move the scale the entry_field gets written in and vice versa, and the other functions I want the code to do all happen. The obvious problem is the functions call each other in a loop, so moving the scale calls scale_has_moved() which calls the additional functions within and writes in the entry field, then because the entry field has been written in entry_field_has_been_written() gets called which in turn calls scale_has_moved() again, it doesn't go in an endless loop but it does everything at least twice everytime which affects performance.
Any clue how I'd fix this? Thank you
If you use the same variable for both widgets, they will automatically stay in sync. You don't need your two functions at all. The following code illustrates the technique.
import tkinter as tk
root = tk.Tk()
var = tk.IntVar(value=0)
scale = tk.Scale(root, variable=var, orient="horizontal")
entry = tk.Entry(root, textvariable=var)
scale.pack(side="top", fill="x")
entry.pack(side="top", fill="x")
root.mainloop()
First of all this is my code:
button_1 = Button(
image=button_image_1,
borderwidth=0,
highlightthickness=0,
command=lambda:[get_to_main_when_clicked(),delete_red_border()],
relief="flat"
)
As you can see I binded 2 functions to this button. To the first one: If a specific condition is true, then the function lets an image appear. The second one then should wait 3 seconds and should delete the appeared image. The only really weird problem is, that it no matter what I do, first executes the delete_red_border() function. It waits 3 seconds, then its trying to delete an image that couldn't be defined and globalized, because the get_to_main_when_clicked() function wasn't executed. How can I solve this?
PS: The specific condition is true.
Don't do this. Create a function specifically for this button. A function is much easier to understand and debug than a lambda, especially a complex lambda. You can then put any logic you want inside the function.
def do_something():
get_to_main_when_clicked()
delete_red_border()
button_1 = Button(..., command=do_something)
I found the solution for it. The problem was, that it didn't refreshed/updated the window after finishing the first function. That was solved by one line of code:
window.update()
I am a beginner and I am making a login system (just for practicing).
I'm using tkinter to develop a simple UI. The thing is that when I call a second root (sign_in root) with a button from another root (main_screen), and I try to get some values typed in entry with StringVars assigned to them, they return just an empty string ""
def main_screen():
root=Tk()
user=StringVar()
pas=StringVar()
btn2=Button(root,text='Sign-In',command=sign_in_screen)
btn2.place(x=125,y=160)
root.mainloop()
def sign_in_screen():
root1=Tk()
newuser=StringVar()
newpas=StringVar()
ent3=Entry(root1,width=28,textvariable=newuser)
ent3.place(x=100,y=50)
ent4=Entry(root1,width=28,textvariable=newpas,show="*")
ent4.place(x=100,y=100)
btn3=Button(root1,text='Sign-In',command=lambda:register(newuser.get(), newpas.get()))
btn3.place(x=50,y=160)
root1.mainloop()
main_screen()
Having multiple instances of Tk become hideously complicated because each one creates a separate tcl interpreter. This causes weird effects like what you see here. It's you almost always want to use the Toplevel widget.
I wanted this code to create a popup error window that destroys itself after 4 seconds but can also be closed via a button.
def error(self):
top = Toplevel()
top.after(4000, lambda: top.destroy())
center_window(300,100, top)
top.title("Error")
Label(top, text="Please enter a valid code", height=3, width=200).pack()
ok = Button(top, text="OK", command=top.destroy)
ok.pack()
ok.bind("<Return>", lambda a: top.destroy())
ok.focus_set()
I have run the code and it works fine 90% of the time except sometimes it throws this error:
TypeError: <lambda>() takes exactly 1 argument (0 given)
I have done research that says it is Tkinters threading. I am not sure if this is my issue but when I take out this line of code:
top.after(4000, lambda: top.destroy())
It seems to work. If anyone could help me, I have taught myself what I know of python, so I am sure there are holes in my learning. I think I may need to somehow use the main thread of execution to destroy this window, or otherwise create my own custom widget. Any help is appreciated.
When using after or bind, you don't need to use lambda.
Instead, for example, use:
top.after(4000, top.destroy)
which references the function top.destroy directly.
You can directly bind the function to be called instead of using a lambda:
top.after(4000, top.destroy)
...
ok.bind("<Return>", top.destroy)
You would use a lambda, for instance, if you needed to pass arguments to the function; this is not the case here.