only last modification to Label being shown in Python - Tkinter . Why? - python

I am learning GUI development using Tkinter. I want to show multiple messages on the label which I have stored in a string. I used sleep to view the changes.However only the last message string is shown at execution.
from tkinter import *
import time
master = Tk()
def onClick():
for i in range(0,len(list_of_str)):
w.configure(text=list_of_str[i])
time.sleep(5)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
I am a noobie. So thanks for helping !

A simple solution to your problem is to use a combination of the try/except method and using after().
In tkinter sleep() will pause the application instead of providing a timer. For tkinter you want to use the after() method to scheduled an event after a set amount of time instead. The after() method is meant for this exact problem and is what you will always use in tkinter for a delayed event.
In my below example I modified your onClick function to take 1 argument and to use that in our after() method to select the next item in the list after 5 seconds. Note that for the after() method time is done in milliseconds so 5000 is 5 seconds.
from tkinter import *
master = Tk()
def onClick(ndex):
try:
w.configure(text=list_of_str[ndex])
master.after(5000, onClick, ndex+1)
except:
print("End of list")
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = lambda: onClick(0))
w.pack()
b.pack()
mainloop()

I think you want this:
from tkinter import *
import time
master = Tk()
global i
i = 0
def onClick():
master.after(1, change)
def change():
global i
if i == len(list_of_str):
pass
else:
w.configure(text=list_of_str[i])
i += 1
master.after(1000, onClick)
list_of_str = ['first','second','third','fourth','fifth']
w = Label(master, text="Hello, world!")
b = Button(master,text='Click me',command = onClick)
w.pack()
b.pack()
mainloop()
time.sleep is a no-no in tkinter. I advise you make your gui in a class and it wil be easier.
example with class:
import tkinter as tk
from tkinter import *
class GUI:
def __init__(self, master):
self.list_of_str = ['first','second','third','fourth','fifth']
self.count = 0
self.master = master
self.w = Label(master, text="Hello, world!")
self.w.pack()
self.b = Button(master,text='Click me',command = self.onClick)
self.b.pack()
def onClick(self, event=None):
if self.count == len(self.list_of_str):
pass
else:
self.w.configure(text=self.list_of_str[self.count])
self.count += 1
self.master.after(1000, self.onClick)
def main():
root = tk.Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()

Related

Button click counter in Python

I am trying to write a Python program to count how many times a button is clicked. I have written the following code:
import tkinter
from tkinter import ttk
def clicked(event):
event.x = event.x + 1
label1.configure(text=f'Button was clicked {event.x} times!!!')
windows = tkinter.Tk()
windows.title("My Application")
label = tkinter.Label(windows, text="Hello World")
label.grid(column=0, row=0)
label1 = tkinter.Label(windows)
label1.grid(column=0, row=1)
custom_button = tkinter.ttk.Button(windows, text="Click on me")
custom_button.bind("<Button-1>", clicked)
custom_button.grid(column=1, row=0)
windows.mainloop()
I know that event.x is used to capture the location of mouse. And hence the result of the program is not as expected. I want something else. Can you please help me solve the issue.
You don't need event for this. You need own variable to count it.
And it has to be global variable so it will keep value outside function.
count = 0
def clicked(event):
global count # inform funtion to use external variable `count`
count = count + 1
label1.configure(text=f'Button was clicked {count} times!!!')
EDIT: Because you don't need event so you can also use command= instead of bind
import tkinter as tk
from tkinter import ttk
count = 0
def clicked(): # without event because I use `command=` instead of `bind`
global count
count = count + 1
label1.configure(text=f'Button was clicked {count} times!!!')
windows = tk.Tk()
windows.title("My Application")
label = tk.Label(windows, text="Hello World")
label.grid(column=0, row=0)
label1 = tk.Label(windows)
label1.grid(column=0, row=1)
custom_button = ttk.Button(windows, text="Click on me", command=clicked)
custom_button.grid(column=1, row=0)
windows.mainloop()
You can add 'counter' as an attribute with a decorator like this:
def static_vars(**kwargs):
def decorate(func):
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
#static_vars(counter=0)
def clicked(event):
clicked.counter += 1
I don't know is this best way or not, but this code is worked
import tkinter as tk
class Main():
def __init__(self, root):
self.root = root
self.count = 0
frame = tk.Frame(self.root)
frame.pack()
btn = tk.Button(frame, text ='click me')
btn.pack()
btn.bind('<Button-1>', self.click)
self.lbl = tk.Label(frame, text = 'Count is 0')
self.lbl.pack()
def click(self, event):
self.count += 1
self.lbl.config(text=f'count {self.count}')
if __name__=="__main__":
root = tk.Tk()
Main(root)
root.mainloop()

How to temporarily pause a GUI in Tkinter? [duplicate]

I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()

Label Is Not Shown When Called From Other Tkinter Program

I was writing a program with a start page, and two programs that are called from that start page. Both of the subprograms work by themselves. However, when I put them into my start page, the stopwatch timing label doesn't show up. If you are wondering, I put them into my program by doing:
import program
program.function()
Here is my start page program:
from Tkinter import *
class start_page:
def __init__(self,master):
self.master = master
self.frame = Frame(self.master)
self.countdown = Button(master, text = "Timer", command = self.c).pack()
self.stopwatch_butt = Button(master,text="Stopwatch",command=self.g).pack()
def g(self):
import stopwatch
stopwatch.f()
def c(self):
import timer_prog
timer_prog.timer()
self.master.after_cancel(timer_prog)
def main():
root = Tk()
s = start_page(root)
root.title("Timer Suite: Brian Ton")
root.mainloop()
main()
If I run this program, the timer program works fine, but the stopwatch doesn't show its label, only its buttons. I tried to clear all Tk after functions, and that didn't work, and I also tried to run the stopwatch program first, to no avail.
Here is my stopwatch program:
from Tkinter import *
import datetime
def s():
start.config(state='disabled')
stop.config(state="normal")
reset.config(state='disabled')
Start()
def Start():
if reset['state'] == 'disabled' and stop['state'] == 'normal':
hidden.set(str(int(hidden.get())+1))
root.update()
root.after(1000,Start)
curr = hidden.get()
g.set(str(datetime.timedelta(seconds=int(curr))))
print g.get()
else:
return None
def Stop():
start.config(state='disabled')
stop.config(state='disabled')
reset.config(state="normal")
def Reset():
start.config(state="normal")
stop.config(state="disabled")
reset.config(state='disabled')
hidden.set('0')
g.set(str(datetime.timedelta(seconds=0)))
def f():
global root,frame,master,hidden,g,timelabel,start,stop,reset
root = Tk()
frame = Frame(root)
master = root
hidden = StringVar()
g = StringVar()
hidden.set('0')
timelabel = Label(master,textvariable=g)
g.set(str(datetime.timedelta(seconds=int(0))))
timelabel.grid(row=1,column=2)
start = Button(master,text="Start",command = s,state="normal")
stop = Button(master,text="Stop",command = Stop,state = "disabled")
reset = Button(master,text="Reset",command = Reset,state = "disabled")
start.grid(row=2,column=1)
stop.grid(row=2,column=2)
reset.grid(row=2,column=3)
root.update()
root.mainloop()
And here is my timer program:
from Tkinter import *
import datetime
def get_seconds(h,m,s):
hr_sec = h * 3600
m_sec = m * 60
return hr_sec+m_sec+s
def timerstartstop():
hours = hour_entry.get()
minutes = minute_entry.get()
sec = second_entry.get()
if hours == "":
hours = 0
hour_entry.insert(0,"0")
if minutes == "":
minutes = 0
minute_entry.insert(0,"0")
if sec == "":
sec = 0
second_entry.insert(0,"0")
c = get_seconds(int(hours), int(minutes), int(sec))
global s
s = StringVar(master)
s.set(c)
if startstop['text'] == 'Stop':
global curr
curr = shown
s.set(-1)
if startstop['text'] == 'Reset':
startstop.config(text="Start")
s.set(c)
root.update()
shown.set(str(datetime.timedelta(seconds=int(s.get()))))
return None
countdown()
import winsound
def countdown():
startstop.config(text="Stop")
global shown
good = True
shown = StringVar(master)
shown.set(str(datetime.timedelta(seconds=int(s.get()))))
L = Label(master,textvariable=shown).grid(row=1,column=2)
if int(s.get()) == 0:
startstop.config(text="Reset")
while startstop['text'] != "Start":
root.update()
winsound.Beep(500,500)
elif int(s.get()) < 0:
good = False
shown.set(curr.get())
startstop.config(text="Reset")
else:
if good:
s.set(str(int(s.get())-1))
root.after(1000,countdown)
def ex():
root.after_cancel(countdown)
root.destroy()
def timer():
global root
global master
global frame
root = Tk()
master = root
frame = Frame(master)
global hour_entry
hour_entry = Entry(master,width=3)
hour_entry.grid(row=0,column=0)
colon_l = Label(master,text=':').grid(row=0,column=1)
global minute_entry
minute_entry = Entry(master,width=2)
minute_entry.grid(row=0,column=2)
colon_l2 = Label(master,text=':').grid(row=0,column=3)
global second_entry
second_entry = Entry(master,width=2)
second_entry.grid(row=0,column=4)
global startstop
startstop = Button(master,text="Start",command=timerstartstop)
e = Button(master,text="Exit",command=ex).grid(row=1,column=3)
startstop.grid(row=0,column=5)
root.mainloop()
In addition, I tried to run these two programs from a different starting menu that used the console, which worked.
The console program is:
import timer_prog
timer_prog.timer()
raw_input('next')
import stopwatch
stopwatch.f()
Attached are some screenshots of what the stopwatch program should look like vs what it does look like when called from the starting program.
Note: I can tell the program is running from the starting page, as it prints the current time each second. Also, I attached some screenshots
Stopwatch Program Run Directly
Stopwatch Program Run From The Start Page
Tkinter program should use only one Tk() - to create main window - and one mainloop() - to control all windows and widgets. If you use two Tk() and two mainloop() then it has problem - for example get()/set() may not work.
Subwindows should use Toplevel() instead of Tk().
Function which starts program (ie. run()) could run with parameter window (def run(window)) and then you can execute it as standalone program with
root = Tk()
run(root)
root.mainloop()
or after importing
run(Toplevel())
(without maniloop())
You can use if __name__ == "__main__" to recognize if program starts as standalone.
Example
main.py
from Tkinter import *
class StartPage:
def __init__(self, master):
self.master = master
master.title("Timer Suite: Brian Ton")
Button(master, text="Timer", command=self.run_timer).pack()
Button(master, text="Stopwatch", command=self.run_stopwatch).pack()
def run_stopwatch(self):
import stopwatch
window = Toplevel()
stopwatch.run(window)
def run_timer(self):
import timer_prog
window = Toplevel()
timer_prog.timer(window)
self.master.after_cancel(timer_prog)
def main():
root = Tk()
StartPage(root)
root.mainloop()
main()
stopwatch.py
from Tkinter import *
import datetime
def pre_start():
start_button.config(state='disabled')
stop_button.config(state='normal')
reset_button.config(state='disabled')
start()
def start():
global current_time
# stop_button['state'] can be 'normal' or 'active' so better use ` != 'disabled'`
if reset_button['state'] == 'disabled' and stop_button['state'] != 'disabled':
current_time += 1
time_var.set(str(datetime.timedelta(seconds=current_time)))
print(time_var.get())
master.after(1000, start)
def stop():
start_button.config(state='disabled')
stop_button.config(state='disabled')
reset_button.config(state='normal')
def reset():
global current_time
start_button.config(state='normal')
stop_button.config(state='disabled')
reset_button.config(state='disabled')
current_time = 0
time_var.set(str(datetime.timedelta(seconds=0)))
def run(window):
global master
global current_time, time_var
global start_button, stop_button, reset_button
master = window
current_time = 0
time_var = StringVar()
time_var.set(str(datetime.timedelta(seconds=0)))
time_label = Label(window, textvariable=time_var)
time_label.grid(row=1, column=2)
start_button = Button(master, text='Start', command=pre_start, state='normal')
stop_button = Button(master, text='Stop', command=stop, state='disabled')
reset_button = Button(master, text='Reset', command=reset, state='disabled')
start_button.grid(row=2, column=1)
stop_button.grid(row=2, column=2)
reset_button.grid(row=2, column=3)
if __name__ == '__main__':
# it runs only in standalone program
root = Tk()
run(root)
root.mainloop()

Python Tkinter Label Delay

This maybe a silly question to ask. I have a label in Tkinter GUI and i want it to be updated through time.
Example:
Msglabel=Tkinter.Label(... text="")
Msglabel.Cofigure(text=" EXAMPLE!")
Wait(5sec)
Msglabel.Configure(text=" NEW EXAMPLE!")
I've read about the after() method but im looking for something like Wait.
You'll need to hand over control to Tkinter during your wait period since Tkinter updates the UI in a single threaded loop.
Sleeping between the configure calls will hang the UI.
As you mentioned, after is the method you want. Try something like this:
try:
import Tkinter as tkinter # Python 2
except ImportError:
import tkinter # Python 3
import itertools
class MyApplication(object):
def __init__(self):
# Create and pack widgets
self.root = tkinter.Tk()
self.label = tkinter.Label(self.root)
self.button = tkinter.Button(self.root)
self.label.pack(expand=True)
self.button.pack()
self.label['text'] = 'Initial'
self.button['text'] = 'Update Label'
self.button['command'] = self.wait_update_label
# Configure label values
self.label_values = itertools.cycle(['Hello', 'World'])
def launch(self):
self.root.mainloop()
def wait_update_label(self):
def update_label():
value = next(self.label_values)
self.label['text'] = value
update_period_in_ms = 1500
self.root.after(update_period_in_ms, update_label)
self.label['text'] = 'Waiting...'
if __name__ == '__main__':
app = MyApplication()
app.launch()

How can I schedule updates (f/e, to update a clock) in tkinter?

I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()

Categories