Open Tkinter multiple Windows with delay (Python) - python

I've been trying to open several Tkinter windows with a delay of couple seconds between each Tkinter window.
My Script so far :
import Tkinter as tk
import os
from PIL import Image, ImageTk
from win32api import GetSystemMetrics
from time import sleep
from random import uniform
root = tk.Tk()
tk.Label(root, text="this is the root window").pack()
root.geometry("10x10")
l = []
def showPic(i):
if(i<5):
loc = os.getcwd() + '\pictures\pic%s.jpg' % i
img = Image.open(loc)
img.load()
photoImg = ImageTk.PhotoImage(img)
l.append(photoImg)
window = tk.Toplevel()
window.geometry("750x750+%d+%d" % (uniform(0, GetSystemMetrics(0) - 750), uniform(0, GetSystemMetrics(1) - 750)))
tk.Label(window, text="this is window %s" % i).pack()
tk.Label(window, image=photoImg).pack()
root.after(1000, showPic(i+1))
else:
return
root.after(0, showPic(1))
root.mainloop()
I also tried using the sleep() and after() functions with no success.
It waits the time before preparing the window, but shows them all together, simultaniously, after the time I set

after expects function name without () and arguments
Using
root.after(1000, showPic(i+1))
is like
result = showPic(i+1)
root.after(1000, result)
You can use lambda
root.after(1000, lambda:showPic(i+1))
because it is almost like
def new_function():
showPic(i+1)
root.after(1000, new_function)
or
root.after(1000, showPic, i+1)

Related

How to update Tkinter Progressbar?

So I'm writing some code to check VAT-IDs using a web API. Since some files have quite a large amount of API-calls it sometimes takes quite a while to complete and I want to show a progressbar so other users know that the program hasn't crashed yet. I found an example and modified it slightly so that it fits my needs. This code shows a window with a progressbar and once the for loop is finished the window closes.
import tkinter as tk
from tkinter import ttk
import time
def wrap():
MAX = 30000
root = tk.Tk()
root.geometry('{}x{}'.format(400, 100))
progress_var = tk.IntVar() #here you have ints but when calc. %'s usually floats
theLabel = tk.Label(root, text="Sample text to show")
theLabel.pack()
progressbar = ttk.Progressbar(root, variable=progress_var, maximum=MAX)
progressbar.pack(fill=tk.X, expand=1)
def loop_function():
k = 0
for k in range(MAX):
### some work to be done
progress_var.set(k)
time.sleep(0.002)
root.update()
root.destroy()
root.after(100, loop_function)
root.mainloop()
wrap()
Now I wanted to implement this into my tool:
import pandas as pd
import re
import pyvat
from tkinter import ttk
import tkinter as tk
def vatchecker(dataframe):
#initialise progressbar
root = tk.Tk()
root.geometry('{}x{}'.format(400, 100))
progress_var = tk.DoubleVar() #here you have ints but when calc. %'s usually floats
theLabel = tk.Label(root, text="Calling VAT Api")
theLabel.pack()
maxval = len(dataframe['Vat-ID'])
progressbar = ttk.Progressbar(root, variable=progress_var, maximum=maxval)
progressbar.pack(fill=tk.X, expand=1)
checked =[]
def loop_function():
for row in range(len(dataframe['Vat-ID'])):
print("Vatcheck: " + str(round(row/maxval * 100, 2)) + " %")
if pd.isna(dataframe['Vat-ID'][row]):
checked.append('No Vat Number')
else:
#check if vat id contains country code
groups = re.match(r'[A-Z][A-Z]', dataframe['Vat-ID'][row])
if groups != None:
querystring = dataframe['Vat-ID'][row][:-2]
country = dataframe['Vat-ID'][row][-2:]
#else get VAT-ID from Country ISO
else:
querystring = dataframe['Vat-ID'][row]
country = dataframe['Land-ISO2'][row]
try:
result = pyvat.check_vat_number(str(querystring), str(country))
checked.append(result.is_valid)
except:
checked.append('Query Error')
progress_var.set(row)
root.update()
root.destroy()
root.quit()
root.after(100, loop_function)
root.mainloop()
dataframe['Vat-ID-check'] = checked
return dataframe
This function gets called by the main script. Here the progressbar window is shown yet the bar doesn't fill up.
With print("Vatcheck: " + str(round(row/maxval * 100, 2)) + " %") I can still track the progress but it's slightly ugly.
Earlier in the main script the user already interacts with a Tkinter GUI but afterwards I close those windows and loops with root.destroy()' and 'root.quit() so I think it should be fine to run another Tkinter instance like this?
Any help or hints would be greatly appreciated.
as #jasonharper mentioned above changing progress_var = tk.DoubleVar() to progress_var = tk.DoubleVar(root) works

How to create a timer with tkinter? I am learning Python [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()

Stop function doesn't work using progressbar

I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()

Rapidly Display Images With Tkinter

I am looking for an efficient way to rapidly display images with tkinter, and I mean like really fast. Currently I have this code:
from tkinter import*
import threading
import time
root = Tk()
root.geometry("200x200")
root.title("testing")
def img1():
threading.Timer(0.2, img1).start()
whitei = PhotoImage(file="white.gif")
white = Label(root, image=whitei)
white.image = whitei
white.place(x=0, y=0)
def img2():
threading.Timer(0.2, img2).start()
blacki = PhotoImage(file="black.gif")
black = Label(root, image=blacki)
black.image = blacki
black.place(x=0, y=0)
img1()
time.sleep(0.1)
img2()
root.mainloop()
Essentially the code just displays a black and white image but it puts my CPU at 100% usage and is pretty slow no matter how small I make the amount of time each picture is displayed for. Is there a faster, more efficient way to do this?
As mentioned, I would suggest using after. You aren't really supposed to alter any tkinter objects outside your main thread. Also, creating a new object each time isn't the most efficient. Here's something I would try:
import tkinter as tk
root = tk.Tk()
root.geometry("200x200")
root.title("testing")
whitei = tk.PhotoImage(file="white_.gif")
blacki = tk.PhotoImage(file="black_.gif")
label = tk.Label(root, image=whitei)
label.image1 = whitei
label.image2 = blacki
label.place(x=0, y=0)
time_interval = 50
def img1():
root.after(time_interval, img2)
label.configure(image=whitei)
def img2():
root.after(time_interval, img1)
label.configure(image=blacki)
root.after(time_interval, img1)
root.mainloop()
You do not need to use threading. 2nd unless you are using sleep() inside of a separate thread you should never use sleep in a tkinter application. sleep() interrupts the mainloop and will cause tkinter to freeze up until sleep is finished. This is 99.9% of the time not what you want to do so here you should use after() for any timed events.
You can simple create each label for each image and then with a tracking variable raise the correct label to the top.
Here is a simple example.
from tkinter import *
root = Tk()
root.geometry("200x200")
root.title("testing")
current_image = ""
black_image = PhotoImage(file="black.gif")
white_image = PhotoImage(file="white.gif")
black_label = Label(root, image=black_image)
white_label = Label(root, image=white_image)
black_label.image = black_image
white_label.image = white_image
black_label.grid(row=0, column=0)
white_label.grid(row=0, column=0)
def loop_images():
global current_image, black_image, white_image
if current_image == "white":
black_label.tkraise(white_label)
current_image = "black"
else:
white_label.tkraise(black_label)
current_image = "white"
root.after(100, loop_images)
loop_images()
root.mainloop()

How do I properly update the countdown timer in one second intervals in the main window? [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()

Categories

Resources