Hey I am new to python and am using tkinter for my gui. I am having trouble using the "after" method.
The goal is to make a random letter appear every 5 seconds.
Here is my code:
import random
import time
from tkinter import *
root = Tk()
w = Label(root, text="GAME")
w.pack()
frame = Frame(root, width=300, height=300)
frame.pack()
L1 = Label(root, text="User Name")
L1.pack(side=LEFT)
E1 = Entry(root, bd =5)
E1.pack(side=LEFT)
tiles_letter = ['a', 'b', 'c', 'd', 'e']
while len(tiles_letter) > 0:
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
frame.after(500)
tiles_letter.remove(rand) # remove that tile from list of tiles
root.mainloop()
can someone please help me --- the problem is definitely frame.after(500):
i'm not sure if it is correct to use "frame" and I don't know what which argument follows the 500.
Thanks
You need to give a function to be called after the time delay as the second argument to after:
after(delay_ms, callback=None, *args)
Registers an alarm callback that is called after a given time.
So what you really want to do is this:
tiles_letter = ['a', 'b', 'c', 'd', 'e']
def add_letter():
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
root.after(500, add_letter)
tiles_letter.remove(rand) # remove that tile from list of tiles
root.after(0, add_letter) # add_letter will run as soon as the mainloop starts.
root.mainloop()
You also need to schedule the function to be called again by repeating the call to after inside the callback function, since after only executes the given function once. This is also noted in the documentation:
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself
Note that your example will throw an exception as soon as you've exhausted all the entries in tiles_letter, so you need to change your logic to handle that case whichever way you want. The simplest thing would be to add a check at the beginning of add_letter to make sure the list isn't empty, and just return if it is:
def add_letter():
if not tiles_letter:
return
rand = random.choice(tiles_letter)
tile_frame = Label(frame, text=rand)
tile_frame.pack()
root.after(500, add_letter)
tiles_letter.remove(rand) # remove that tile from list of tiles
Live-Demo: repl.it
I believe, the 500ms run in the background, while the rest of the code continues to execute and empties the list.
Then after 500ms nothing happens, as no function-call is implemented in the after-callup (same as frame.after(500, function=None))
after is used to delay execution of the program or to execute a command in background sometime in the future. But you can build a loop inside the mainloop by calling itself.
import tkinter as tk #import tkinter
import datetime #import datetime for our clock
def tick(): #function to update the clock
showed_time = clock['text'] #current showed time
current_time = datetime.datetime.now().strftime("%H:%M:%S") #real time
if showed_time != current_time: #if the showed time is not the real time
clock.configure(text=current_time) #update the label with the current time
clock.after(1000, tick) #call yourself in 1000ms (1sec.) again to update the clock
return None
root=tk.Tk()
clock = tk.Label(root)
clock.pack()
tick()
root.mainloop()
In the above script we had built a digital clock and get in touch with the after method. The after method is nothing but an interval and on the end of that interval we want that something happen.
To learn more about this basic widget method [click]
after(delay_ms, callback=None, args)
This method registers a callback function that will be called after a
given number of milliseconds. Tkinter only guarantees that the
callback will not be called earlier than that; if the system is busy,
the actual delay may be much longer.
import tkinter as tk
import datetime
def tick():
showed_time = clock['text']
current_time = datetime.datetime.now().strftime("%H:%M:%S")
if showed_time != current_time:
clock.configure(text=current_time)
global alarm #make sure the alarm is reachable
alarm = clock.after(1000, tick)#assign the alarm to a variable
return None
def stop():
stop.after_cancel(alarm) #cancel alarm
root=tk.Tk()
clock = tk.Label(root)
clock.pack()
stop = tk.Button(root, text='Stop it!', command=stop)
stop.pack()
tick()
root.mainloop()
Here we have the same code but with the ability to cancel our loop with the after_cancel method of tkinter. You dont need to global the alarm inside a class. self.alarm = self.clock.after(...) works fine.
after_cancel(id)
Cancels an alarm callback.
id
Alarm identifier.
Related
I was trying to make a stopwatch in python but every time it stops working beacause of overflow, can someone please fix this??
Code:
import time
from tkinter import *
cur=time.time()
root = Tk()
def functio():
while True:
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
time.sleep(0.5)
Button(root,text='Start',command=functio).pack()
root.mainloop()
The while loop will block tkinter mainloop from handling pending events, use after() instead.
Also it is better to create the label once outside the function and update it inside the function:
import time
# avoid using wildcard import
import tkinter as tk
cur = time.time()
root = tk.Tk()
def functio():
# update label text
l1['text'] = round(time.time()-cur, 4)
# use after() instead of while loop and time.sleep()
l1.after(500, functio)
tk.Button(root, text='Start', command=functio).pack()
# create the label first
l1 = tk.Label(root)
l1.pack()
root.mainloop()
Note that wildcard import is not recommended.
Flow of execution can never exit your endless while-loop. It will endlessly block the UI, since flow of execution can never return to tkinter's main loop.
You'll want to change your "functio" function to:
def functio():
s = time.time()-cur
l1 = Label(root,text=s)
l1.pack()
l1.destroy()
root.after(500, functio)
That being said, I'm not sure this function makes much sense: You create a widget, and then immediately destroy it?
You'll want to do something like this instead:
import time
from tkinter import *
root = Tk()
def functio():
global timerStarted
global cur
# check if we started the timer already and
# start it if we didn't
if not timerStarted:
cur = time.time()
timerStarted = True
s = round(time.time()-cur, 1)
# config text of the label
l1.config(text=s)
# use after() instead of sleep() like Paul already noted
root.after(500, functio)
timerStarted = False
Button(root, text='Start', command=functio).pack()
l1 = Label(root, text='0')
l1.pack()
root.mainloop()
when calling rest function from button, then start function is called and prints values continues every second But when I call again rest function the start function call again but this time start function print values in 2x speed and so on.
But I don't want to print value in 2x speed. I am making a small project where I face this type of problem so that is why I write this small code. Please solve my problem
import tkinter as tk
window = tk.Tk()
window.geometry('400x400')
i = 0
def start():
global i
text_label .config(text=i)
i += 1
text_label .after(1000, start)
def rest():
global i
i=0
start()
text_label = tk.Label(window, text="start")
text_label .pack()
tk.Button(window, text="rest", command=rest).pack()
window.mainloop()
What is happening is that every time you call reset, a new callback is launched that will call start indefinitely every 100ms. Every callback being independent, and having no knowledge of the others, this results in a series of callbacks each calling start on their own time, every 100 ms.
To avoid this "snowballing", you need to cancel the previous callbacks in order to reset properly. You do this by keeping a reference on the callback, and calling tk.after_cancel(callback_id) in reset.
Like this:
import tkinter as tk
def start():
global i, callback_id
text_label.config(text=i)
i += 1
callback_id = text_label.after(1000, start)
def reset():
global i, callback_id
i = 0
if callback_id is not None:
text_label.after_cancel(callback_id)
callback_id = None
start()
window = tk.Tk()
window.geometry('400x400')
text_label = tk.Label(window, text="start")
text_label.pack()
callback_id, i = None, 0
tk.Button(window, text="reset", command=reset).pack()
window.mainloop()
The purpose is that input fn takes string input and pass it into GUI fn which runs the condition and ammend tkinter window accordingly.
#*********************************** IMPORTING MODULES*****************
import tkinter
from tkinter import*
import tkinter.messagebox
import sqlite3
import os
import threading
from time import sleep
from input import*
conn = sqlite3.connect('portal.db')
c = conn.cursor()
global a
#*************** TKINTER GUI CODE******************
def gui(a):
window = tkinter.Tk()
window.title("Smart Notice Board")
#********************** FRAMES OF MAIN WINDOW(HOME)******************
top = Canvas(window,width=1024,height=184)
top.pack(fill=X)
middle = Canvas(window, width=1024, height=450, bg='steelblue')
middle.pack(fill=X)
main_left = Canvas(middle, width=275, height=450, bg='lightgreen')
main_left.pack(side=LEFT)
main_right = Canvas(middle, width=800, height=450, bg='steelblue')
main_right.pack(side=RIGHT)
bottom = Canvas(window, width=1024, height=70, bg='black')
bottom.pack(fill=X)
#************************** IMAGES********************
i_top = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
top.create_image(0,10, anchor=tkinter.NW,image = i_top)
i_right = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\aus1.png')
main_right.create_image(0,0, anchor=tkinter.NW,image = i_right)
#i_left = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\widget1.png')
#main_left.create_image(0,0, anchor=tkinter.NW,image = i_left)
t1 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\first.png')
t2 = tkinter.PhotoImage(file='F:\\C_backup\\fyp\\5 jan 2k19\\BG.png')
#***************** TIMETABLE IMAGE VIEWING FN***********************
def home():
main_right.create_image(0,0, anchor=tkinter.NW,image = t2)
#*********************** TIMETABLE BUTTON PRESS FN*************************
def timetable():
main_right.create_image(0,0, anchor=tkinter.NW,image = t1)
#******************************* CONDITIONS**********************
if a == "NULL":
timetable()
if a == "HOME":
home()
#*********************** MAIN MENU BUTTONS****************
button_1 = Button(text = ' HOME', anchor = 'w', height = 2, width = 8,activebackground = '#33B5e5',bg = 'brown',fg = 'white',command = home)
top.create_window(2,150,anchor = 'nw', window = button_1)
button_2 = Button(text='TIMETABLE', height = 2, width=12, activebackground = '#33B5e5', bg = 'brown', fg = 'white',command = timetable)
top.create_window(75,150, anchor='nw', window = button_2)
window.mainloop()
#************************** MAIN LOOP************************
if __name__ == "__main__":
#print(valuea())
a=valuea()
gui(a)
Now what I want is continuously run that thing and update Tkinter window; but the 2btn fn only takes it one time and pass it into GUI fn which runs tkinter and it stucks on window.mainloop as tkinter is infinity loop.
Please suggest me a solution also u can run this code by only setting pictures from your computer
The standard method to run code regularly in the mainloop is to register a timeout function using the after method of the root window.
But, whatever you do in such a timeout function (and indeed in all other callbacks), it should not block, because that would lock up the mainloop! So you cannot use input. But you could read from sys.stdin, which is a io.TextIOWrapper instance.
You could use input in a second thread. But since Tkinter isn't thread-safe, that second thread should not use Tkinter functions or methods. So you should not simply update a label from the second thread. You could save/append the input to a global variable, but you'd have to protect that with a lock or mutex. And you'd need to use a timeout function in the main Tkinter thread to test if the lock or mutex is released by the second thread so the Tkinter thread can claim it and access the data. As you can see this is really complicated. So mixing Tkinter and threads is generally not recommended.
In order to ammend tkinter window and show the text and open picture.I did slight change in the input.py file;and coded as below
def vala():
a=speech()
if a == "HOME":
home()
if a == "NULL":
timetable()
window.after(1000,vala)
what I have done is I convert it into recursive fn that call itself after 1000ms. In this way, it can be done.
Basically, I have done it with Pocketsphinx as Input (i.e. my project takes speech input and open file/image in tkinter screen and it runs continuously)
Everything works but the time does not update and I think it is because the mainloop overrides the while loop. Please help, I have searched for a long time and found nothing.
While loop is below then main code:
def loop1():
Time = time.strftime("%H:%M:%S")
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
alarm = Tk()
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.mainloop()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
Main code:
#Import libraries
from tkinter import *
import time
Time = time.strftime("%H:%M:%S")
def loop1():
Time = time.strftime("%H:%M:%S")
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
alarm = Tk()
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.mainloop()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
initalarm = Tk()
label1 = Label(initalarm,text="What time do you want to wake up?")
label2 = Label(initalarm,text="Use this form.\nExample: 06:30:00")
Alarm = Entry()
start = Button(initalarm, text="Set Alarm", command=loop1)
label1.pack()
label2.pack()
Alarm.pack()
start.pack()
mainloop doesn't override anything. It simply will not return until the root window is destroyed. Or more correctly, it won't return until root.quit() is called, which happens automatically when the root window is destroyed.
Don't fight against the framework, use it. GUI programming with Tk is event driven, i.e. you don't control the control flow of your program directly in long running loops. Instead you set up handlers for different events which get called from Tk's main loop when the events occur. Examples of events are button presses or when a certain given time has passed. This can be used to regularly get some handler called by the main loop for updating the display and eventually activating the alarm.
import tkinter as tk
from tkinter.font import nametofont
from tkinter.simpledialog import askstring
from datetime import datetime as DateTime
def update_display(time_label, alarm_time):
now = DateTime.now().strftime('%H:%M:%S')
if now >= alarm_time:
time_label['foreground'] = 'yellow'
time_label['text'] = now
time_label.after(500, update_display, time_label, alarm_time)
def main():
root = tk.Tk()
root.title('Alarm Clock')
font = nametofont('TkTextFont').copy()
font['size'] *= 5
time_label = tk.Label(root, text='--:--:--', font=font)
time_label.pack()
alarm_time = askstring(
'When?', 'What time do you want to wake up?\nExample: 06:30:00'
)
if alarm_time is not None:
update_display(time_label, alarm_time)
root.mainloop()
if __name__ == '__main__':
main()
The code does approximately what was tried with the code in the question if I got it right, but an actual useful alarm clock program needs object oriented programming in Python, if it should not become a hard to understand and follow mess.
Also I would not operate on the times as strings but as objects from the datetime module, or at least as number of seconds since ”epoch” with the time module. The string form is better left to input and output for the user. And the user input should be validated. In the current form it is too easy to enter invalid alarm times by accident.
As per my understanding you are looking for the label to be updated till it reaches alarm time. If that's true than there are two thing wrong with your code
alarm = Tk() should be moved out of the while loop
You should use alarm.update() instead of alarm.mainloop()
Updated loop1 definition:
def loop1():
Time = time.strftime("%H:%M:%S")
alarm = Tk()
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.update()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
Main code:
#Import libraries
from tkinter import *
import time
Time = time.strftime("%H:%M:%S")
def loop1():
Time = time.strftime("%H:%M:%S")
alarm = Tk()
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.update()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
initalarm = Tk()
label1 = Label(initalarm,text="What time do you want to wake up?")
label2 = Label(initalarm,text="Use this form.\nExample: 06:30:00")
Alarm = Entry()
start = Button(initalarm, text="Set Alarm", command=loop1)
label1.pack()
label2.pack()
Alarm.pack()
start.pack()
The way that root.mainloop() or in your case alarm.mainloop() works is that it will make it's own loop that will update the root (or alarm) window. Whenever a command is run (like a root.bind, or a button command) it will pause the mainloopBecause mainloop is looping, doing it's own thing, it will never reach lines past the root.mainloop() or alarm.mainloop() (unless some weird errors occur).A really easy way to fix that is to make your own "mainloop" by having a loop that does your things and then uses root.update() or alarm.update() (which is what is being run over and over by the mainloop command but you can "customize" the loop)
Using this will still allow the binds and button commands to work. Instead of calling alarm.mainloop() before your loop, call alarm.update() inside of your loop
I'm writing a basic war-driving program. I have gotten it to loop the command to pull all the wireless access points near by. The problem is my stop button doesn't work and I am unable to update the label(I'm not even sure if I can update the label).
import sys, os, subprocess, re
from Tkinter import *
missionGO = 0
count = 0
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.start = Button(frame, text="Start", fg="green",
command=self.startButtonClick)
self.start.grid(row=3)
self.stop = Button(frame, text="Stop", fg="red",
command=self.stopButtonClick)
self.stop.grid(row=3, column=1)
self.totalSSIDLabel = Label(frame, text="Current Access Points: ")
self.totalSSIDLabel.grid(row=0)
self.totalSSID = Label(frame, text=count)
self.totalSSID.grid(row=0, column=1)
def startButtonClick(self):
missionGO = 1
while (missionGO == 1):
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
return
def stopButtonClick(self):
missionGO = 0
return
def stop(event):
missionGO = 0
# Finds all wireless AP
def getAccessPoints():
X = subprocess.check_output("netsh wlan show network mode=Bssid",
shell=True)
return X
def numberOfAccessPoints(file):
count = 0
words = file.split()
for line in words:
if re.match('SSID', line):
count = count + 1
return count
#Main
root = Tk()
app = App(root)
root.mainloop()
Tkinter is single threaded. That means that while you are in the while loop inside startButtonClick, no other events are processed. The stop button won't call its command until the startButtonClick function finishes
You need to remember that your program is already running a global infinite loop: the event loop. There's no reason to put another infinite loop inside it. When you want something to run forever, the trick is to put one iteration on the event loop, then when it runs it puts another iteration on the event loop.
The other key to this is to make sure that one iteration of the loop is fast -- it needs to be well under a second (more like under 100ms) or the UI will become laggy.
The logic looks something like this:
def startButtonClick(self):
self.missionGO = 1
self._do_one_iteration()
def _do_one_iteration(self):
if self.missionGO == 1:
wlan = getAccessPoints()
x = numberOfAccessPoints(wlan)
print x
# this adds another iteration to the event loop
self.after(10, self._do_one_iteration)
def stopButtonClick(self):
self.missionGO = 0
I think the main thread is hanging in the while loop of the start button click. Since it's busy it won't even notice the stop button has been pressed.
I can't tell you exactly why your stop button doesn't work, but I think I got the idea of your programm. My suggestion is to establish two threads. The first thread for the UI, and the second for constantly checking wireless networks with given interval (your current code checks ASAP - bad practice, you should pause within the loop.
Since I have not dealt with multithreading in Tkinter, I can only provide you with entry points:
threading Module
time.sleep for updating the nearby networks every second or similar
Is there a way to request a function to be called on Tkinter mainloop from a thread which is not the mainloop?
Tkinter: invoke event in main loop
Good luck!