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()
Related
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()
It works on my monitor (1920x1080) perfectly but when I use my laptop (Also 1920x1080) none of the buttons or the timer shows..... I've tried using 2 different geometry managers to manage the placement of elements. At first I used .place() but that wasn't working/ideal on my laptop so I switched to .grid(). .grid() was working perfectly on my laptop until today. How can I fix this?
Code:
# Required libraries.
# This is for the gui.
import tkinter as tk
# This is for the timer.
from datetime import datetime
# Sets the Timer up.
counter = 66600
running = False
# Defines the timer.
def counter_label(label):
def count():
if running:
global counter
# To manage the initial delay.
if counter==66600:
display="Starting..."
else:
tt = datetime.fromtimestamp(counter)
string = tt.strftime("%H:%M:%S")
display=string
label['text']=display
label.after(1000, count)
counter += 1
# This starts the timer.
count()
# Start function of timer.
def Start(label):
global running
running=True
counter_label(label)
start['state']='disabled'
stop['state']='normal'
reset['state']='normal'
# Stop function of the timer.
def Stop():
global running
start['state']='normal'
stop['state'] ='disabled'
reset['state']='normal'
running = False
# Reset function of the timer.
def Reset(label):
global counter
# Sets the timer to 0.
counter=10800
# If reset is pressed after pressing stop.
if running==False:
reset['state']='disabled'
# This displays CBR when the timer is not in use.
label['text']='CBR'
# If reset is pressed while stopwatch is running.
else:
label['text']='Starting...'
# Declares the gui.
gui = tk.Tk()
# Changes what the gui is called.
gui.title('Car Go Brrrr v1.9')
# Sets Windows size.
gui.geometry("1920x1080")
# Columns
gui.columnconfigure(0, weight=1)
gui.rowconfigure(0, weight=1)
# This gets grid() to work.
for i in range(20):
tk.Frame(gui, width=20, height=20).grid(row=0, column=i)
for j in range(20):
tk.Frame(gui, width=50, height=50).grid(column=0, row=j)
# Pitstop Button
pitButton = tk.Button(gui, text="Call Pitstop")
pitButton.grid(column=18, row=17)
pitButton.config(width=11, height=5)
# Defines the Exit telemetry button.
def clickExitButton():
exit()
# Exit button code.
exButton = tk.Button(gui, text="Exit Telemetry", command=clickExitButton)
exButton.grid(column=18, row=18)
exButton.config(width=11, height=1)
# Timer label text.
label = tk.Label(text="CBR", fg="black", font="Verdana 30 bold")
label.grid(column=18, row=0)
# Start button.
start = tk.Button(text="Start Timer", command=lambda:Start(label))
start.config(width=11, height=1)
start.grid(column=18, row=16)
# Stop button.
stop = tk.Button(text="Stop Timer", state='disabled', command=Stop)
stop.config(width=11, height=1)
stop.grid(column=18, row=15)
# Reset button.
reset = tk.Button(text="Reset Timer", state='disabled', command=lambda:Reset(label))
reset.config(width=11, height=1)
reset.grid(column=18, row=14)
# Never ever ever remove this, this is essential for the gui to work.
gui.mainloop()
Thanks!
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 :).
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()
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.