Tkinter get status message from called function displayed in real time - python

I try to show a status from a called function in real time. However, the messages appears in the GUI all at ones after function is done. What can I do?
Thanks for your help!
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
return
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()

You can use the Tk.update() function to update the window without having to wait for the function to finish:
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()
(also the return is unnecessary, you use it if you want to end a function partway through or return a value from the function).
Let me know if you have any problems :).

Related

tkinter - how to update button text before command function ends?

I have read numerous questions here but none actually answered my puzzle:
openFolderBtnGenerate = Button(top, text="Generate", command=lambda: compute(csv_file_name, lan, lon, alt))
openFolderBtnGenerate.grid(row=5, column=1)
def compute(csv_file_name, lat_0, lon_0, alt_0):
global openFolderBtnGenerate
openFolderBtnGenerate['text'] = "Please wait..."
# here mathematical computation code that takes 10 seconds to accomplish
...
# end
I intended the button text to change before the computation starts so users understand it might take some time to accomplish.
What really happens is that the button text is changed only after the computation ends.
How can I make the button text change right after the button is clicked, without having to wait these long 10 seconds?
What I think you want to do is just to change text as you press the button then run the function, so a small thing you can do is make another function CODE :
Def function2()
global openFolderBtnGenerate
openFolderBtnGenerate['text'] = "Please wait..."
compute(csv_file_name, lan, lon, alt)
openFolderBtnGenerate = Button(top, text="Generate", command=function2)
openFolderBtnGenerate.grid(row=5, column=1)
def compute(csv_file_name, lat_0, lon_0, alt_0):
# here mathematical computation code that takes 10 seconds to accomplish
...
# end
It works for me and will surely work for you too.
See line "root.update_idletasks()":
import time
import tkinter as tk
root = tk.Tk()
def start_task():
btn_text.set("Please wait for task to finish...")
btn.update_idletasks()
time.sleep(2)
btn_text.set("Press to start a task")
btn_text = tk.StringVar()
btn = tk.Button(root, textvariable=btn_text, command=start_task)
btn_text.set("Press to start a task")
btn.pack()
root.mainloop()
And another sample, without time.sleep() (for UI not to freeze):
import time
import tkinter as tk
root = tk.Tk()
def update_text():
btn_text.set("Press to start a task")
def start_task():
btn_text.set("Please wait for task to finish...")
btn.update_idletasks()
root.after(2000, update_text)
btn_text = tk.StringVar()
btn = tk.Button(root, textvariable=btn_text, command=start_task)
btn_text.set("Press to start a task")
btn.pack()
root.mainloop()

How to add time (ticks) in a GUI for a game

I want to add the ticking of time in seconds to a GUI, for a clicker game. So the idea is to have a function that gets called every n ticks, and this function increments X objects.
I have tried to use a while loop, both before and after calling the .mainloop() method. It didn't work in either occasion, I also tried the crazy idea of having the mainloop() method inside the while loop (aware of what that would do lol).
from tkinter import *
import time
result = 0
window = Tk()
window.title("Numbers Game")
window.geometry('360x240')
label = Label(window, text=result)
label.grid(column=0,row=0)
def clicked():
global result
result += 1
label.config(text=result)
button = Button(window, text="Push Me", command=clicked)
button.grid(column=1, row=2)
window.mainloop()
while True:
time.sleep(1)
clicked()
The current version of my code produces an error that mentions the function doing GUI related things outside of the window. But I don't have the slightest clue of how to achieve this.
You mean you want to have the result counter increment every second? You can't use infinite loops with a GUI, because they interfere with the GUI's mainloop. You have to integrate your code into the mainloop using the after method.
from tkinter import *
import time
result = 0
window = Tk()
window.title("Numbers Game")
window.geometry('360x240')
label = Label(window, text=result)
label.grid(column=0,row=0)
def clicked():
global result
result += 1
label.config(text=result)
def tick():
clicked()
window.after(1000, tick) # after 1,000 milliseconds, call tick() again
button = Button(window, text="Push Me", command=clicked)
button.grid(column=1, row=2)
tick() # start the "loop"
window.mainloop()

Python Tkinter Progressbar After [duplicate]

Imagine the following simple example:
def doNothing():
sleep(0.5)
barVar.set(10)
sleep(0.5)
barVar.set(20)
sleep(0.5)
barVar.set(30)
mainWindow = Tk()
barVar = DoubleVar()
barVar.set(0)
bar = Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= Button(mainWindow, text='Click', command=doNothing)
button.grid(row=0, column=0)
mainWindow.mainloop()
What I get when I run this, the progressbar is already at 30% when clicking the button, no progress in front of me. Like attached:
What I need: I can see the progress in front of me (not hanging then suddenly 30%)
Update:
I upadted the code according to #Bernhard answer, but still I can not see the progress in front of me. Just a sudden jump of 30% after waiting 1.5 sec
Seocnd Update:
I'm only using sleep here as a simulation for a process that takes time, like connecting over ssh and grabing some info.
Do not use sleep() in tkinter. The entire reason for you problem is sleep() will freeze tkinter until it is done with its count so what you are seeing is a frozen program and when the program is finally released its already set to 30 percent on the next mainloop update.
Instead we need to use Tkinter's built in method called after() as after is specifically for this purpose.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+10)
mainWindow.after(500, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
If you want the bar to appear to move smoothly you will need to speed up the function call and reduce the addition to the DoubbleVar.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+0.5)
mainWindow.after(50, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
Because you are calling the function when the buttion is initialized, you need to loose the '(barVar') in the command=(barVar)). This way you bind the function to the button and don't call it when initializing it.
button= Button(mainWindow, text='Click', command=doNothing)
If you need to pass an argument you need to bypass the calling by using lambda:
button= Button(mainWindow, text='Click', command= lambda: doNothing(barVar))
I think I find the solution.
simply add mainWindow.update() after each progress. So the final code would be:
def doNothing():
sleep(0.5)
barVar.set(10)
mainWindow.update()
sleep(0.5)
barVar.set(20)
mainWindow.update()
sleep(0.5)
barVar.set(30)
mainWindow.update()

Tkinter code isnt executed after destroying a window

from tkinter import *
from random import *
root = Tk()
#A function to create the turn for the current player. The current player isnt in this code as it is not important
def turn():
window = Toplevel()
dice = Button(window, text="Roll the dice!", bg= "white", command=lambda:diceAction(window))
dice.pack()
window.mainloop()
#a function to simulate a dice. It kills the function turn.
def diceAction(window):
result = Toplevel()
y = randint(1, 6)
# i do something with this number
quitButton = Button(result, text="Ok!", bg="white", command=lambda: [result.destroy(), window.destroy()])
quitButton.pack()
window.destroy()
result.mainloop()
#A function to create the playing field and to start the game
def main():
label1 = Button(root, text="hi", bg="black")
label1.pack()
while 1:
turn()
print("Hi")
turn()
main()
root.mainloop()
With this code i basically create a roll the dice simulator. In my actual code i give the function turn() player1/player2(which are class objects) so i can track whose turn it is. Thats why i call turn() 2 times in the while.
The problem is that the code after the first turn() isnt executed(until i manually close the root window which is weird) anymore. At my knowledge this should work.
I open the turn function which opens the diceAction function upon button press. diceAction() gives me the number and kills both windows. Then the second turn() should be called and the process continues until someone wins(which i havent implemented in this code).
The print("Hi") isnt executed either. Am i missing something? You can copy this code and execute it yourself.
The short answer is "Infinite loops and tkinter don't play well together". The long answer is that you never escape window.mainloop(). I don't see a good enough reason that you need to have window.mainloop() and result.mainloop() running to justify the headache of multiple loops in tkinter.
A subjectively better way of doing this is to have the end of the first turn() trigger the start of the next one:
from tkinter import *
from random import *
root = Tk()
global turnCount
turnCount = 0
def turn():
window = Toplevel()
dice = Button(window, text="Roll the dice!", bg="white", command=lambda:diceAction())
dice.pack()
def diceAction():
result = Toplevel()
y = randint(1, 6)
quitButton = Button(result, text="Ok!", bg="white", command=lambda: nextTurn())
quitButton.pack()
def nextTurn():
global turnCount
turnCount = turnCount + 1
for i in root.winfo_children():
if str(type(i)) == "<class 'tkinter.Toplevel'>":
i.destroy()
turn()
def main():
label1 = Button(root, text="hi", bg="black")
label1.pack()
turn()
main()
root.mainloop()
I would recommend attempting to use OOP on a project like this instead of the global that I declared above.

how to prevent tkinter from freezing when I click somewhere else?

My tkinter gui starts to freeze when I click on somewhere else. Is there a way to prevent that?
Here's my code:
#=========================
from tkinter import *
from time import sleep
import random
#=====================
root=Tk()
root.title("Wise Words")
root.geometry("500x180+360+30")
root.resizable(0,0)
root.call("wm", "attributes", ".", "-topmost", "1")
#===================
def display(random):
if random == 1:
return "Be wise today so you don't cry tomorrow"
elif random == 2:
return "Frustration is the result of failed expectations"
elif random == 3:
return "Wishes are possibilities. Dare to make a wish"
if True:
sleep(4)
r=random.randint(1,3)
sentence=display(r)
label.configure(text=str(sentence))
label.update_idletasks()
root.after(5000, display(random))
#==================
def Click(event):
display(random)
#======================
label=Button(root, fg="white", bg="blue", text="Click to start!",
font=("Tahoma", 20, "bold"), width=40, height=4,
wraplength=400)
label.bind("<Button-1>", Click)
label.pack()
#================
root.mainloop()
Note: The label for display is the Button itself, so I name it 'label'.
You're doing several strange things in your code:
Using time.sleep in a Tkinter application
Calling the button a label (there is a Label Tkinter widget)
Binding the left mouse button to a button instead of just giving the button a command
Passing the random module around and expecting it to evaluate to an integer
Returning strings to a button
Using an unconditional branching statement (if True:)
Masking a module name with a parameter name
Expecting the name random to refer to both the random module and a passed argument, at the same time
Making a recursive call in a function that already calls after
Leaving the button bound to a function that already schedules calls to itself with after, allowing you to schedule many calls
Using an if structure to choose a random string instead of using random.choice
Scheduling an after call with the result of a function call (display(random)) instead of the function itself
That's not necessarily a complete list.
The following fixes the above issues.
from tkinter import *
import random
def display():
strings = ("Be wise today so you don't cry tomorrow",
"Frustration is the result of failed expectations",
"Wishes are possibilities. Dare to make a wish")
button.config(text=random.choice(strings))
root.after(5000, display)
def click(event=None):
button.config(command='')
display()
root=Tk()
root.title("Wise Words")
root.geometry("500x180+360+30")
root.resizable(0,0)
root.call("wm", "attributes", ".", "-topmost", "1")
button = Button(root, fg="white", bg="blue", text="Click to start!",
font=("Tahoma", 20, "bold"), width=40, height=4,
wraplength=400, command=click)
button.pack()
root.mainloop()

Categories

Resources