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
Related
Im just starting out and have made a timer and stuff succesfully but now wanted to make a GUI since its not that straight forward and I want to have a good project.
I want to know how to remove a button after I press it and how to start my timer code after i take an entry for how long I want my timer to last. Here is my timer code:
import time
from playsound import playsound
a = int(input("How long would you like your time to be(In Minutes): "))
with alive_bar() as bar:
b = a * 60
time.sleep(b)
bar()
playsound(r"C:\Users\shavi\Music\mixkit-arcade-retro-game-over-213.wav")
print("done"
and here is my gui code
from tkinter import *
#First creating my window
timer = Tk()
#My window name and dimension
timer.title("My Timer")
timer.geometry('400x200')
#My Command
def clicked():
lbl.configure(text = "Starting Timer")
#Label text
lbl = Label(timer, text ="Start a timer?")
lbl.grid()
#Button
button = Button(timer, text = "Yes",
command=clicked,)
button.grid(column=2, row=0)
#Running
timer.mainloop()
I tried using the .after but cant figure it out pleae help me figure this out
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()
I am using NI instrument to read data and display it on GUI. Have used tkinter. But could not find a way to update the data using while loop.
import nidaqmx
import time
from tkinter import *
master = Tk()
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value ='%.2f' % data #said sensor value
master.minsize(width=400, height=400)
w = Label(master, text=sensor_value) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
time.sleep(5)
mainloop()
When working with Tkinter we should avoid Threading, while loop with root.update() it is not like we can't use them but not advisable instead use after(delay_ms, callback=None, *args) method provided by Tkinter itself for a reason.
Now to your code, there are few issues in your code.
In while loop you are creating a Label every 5 secs instead create one label and update the value of that label inside the loop with w['text'] = '...' or w.configure(text='...').
Don't put mainloop inside the loop, instead call it in the last line with the instance of the main window in your case master (master.mainloop()).
Same with master.minsize(width=400, height=400), you don't have to tell the master window every 5 sec to set the minimum size to 400x400 it should be called once if not decide to change the minimum size to different geomentry.
Your code should look like this.
import nidaqmx
from tkinter import *
master = Tk()
master.minsize(width=400, height=400)
w = Label(master) #shows as text in the window
w.pack() #organizes widgets in blocks before placing them in the parent.
def run():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
w['text'] = '%.2f' % data #said sensor value
master.after(5000, run)
run() # run the function once.
master.mainloop()
This should be the right way of doing this and as i couldn't run your code if anything doesn't work, let me know otherwise.
try this code:
import time
from tkinter import *
import nidaqmx
master = Tk()
def test():
while True:
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
print('1 Channel 1 Sample Read: ')
data = task.read()
sensor_value = '%.2f' % data # said sensor value
w['text'] = sensor_value
master.update()
time.sleep(2)
w = Label(master)
w.pack()
btn = Button(text="Start read from sensor", command=test)
btn.pack()
mainloop()
You'll likely need a second thread to poll the sensor.
import nidaqmx
import time
import threading
from tkinter import *
stop_signal = threading.Event()
def read_loop():
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
while True:
data = task.read()
label["text"] = "%.2f" % data
# Wait for the signal, or 5 seconds
if stop_signal.wait(timeout=5):
break # If the signal was set, break
# Build window
master = Tk()
master.minsize(width=400, height=400)
label = Label(master, text="")
label.pack()
# Set up & start reading thread
threading.Thread(target=read_loop).start()
try:
# Enter Tk main loop
mainloop()
finally:
# Clean up afterwards
stop_signal.set()
Keep in mind that mainloop() is blocking, so time.sleep() is useless as you won't get back to the task.read() line.
Consider using the Thread module from the threading lib importing it like this:
from threading import Thread
or using a button to refresh the value as someone else suggested you.
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.
from Tkinter import *
import tkFileDialog
import tkMessageBox
import os
import ttk
import serial
import timeit
import time
######################################################################################
class MyApp:
def __init__(self, parent):
########################################################
#Setup Frames
self.MiddleFrame = Frame(parent) #Middle Frame
self.MiddleFrame.pack()
#GLOBAL VARIABLES
self.chip_number = 0 #number of chip testing
###########################################
#Middle Frame setup
Label(self.MiddleFrame, text='Done').grid(row=8, column=1, sticky = E)
self.Done = Canvas(self.MiddleFrame, bg="yellow", width=10, height=10)
self.Done.grid(row=8, column=2)
Label(self.MiddleFrame, text='Chip Number:').grid(row=9, column=1, sticky = E)
#start button
self.button1 = Button(self.MiddleFrame,state=NORMAL, command= self.start_pre)
self.button1["text"]= "START"
self.button1.grid(row=1, column=2, sticky = E)
###########################################
#Action of Start Button
def start_pre(self):
x = 0
while x<10000:
self.start_button()
x=x+1
#Talking to Board
def start_button(self):
#increase chip count number and update
self.chip_number += 1
Label(self.MiddleFrame, text=str(self.chip_number)).grid(row=9, column=2, sticky = E)
#reset-yellow
self.reset_color()
print "Still Working", self.chip_number
self.Done.configure(background="green")
self.Done.update_idletasks()
###############################################################
#Color Boxes
#Reset
def reset_color(self):
self.Done.configure(background="yellow")
self.Done.update_idletasks()
###############################################################################################################
#Start Programs
root = Tk() #makes window
root.title("Interface")
myapp = MyApp(root) #this really runs program
root.mainloop() #keep window open
With my program, i first push the start button.
I will print "still working" and the GUi will update chip number and blink done light over and over. The start button go to function that will execute 10000 times. However after 3000 iterations, the gui freeze, but the program is still print "still working". How do I keep the gui from crashing?
There are many problems with your code. For one, this is fundamentally flawed:
while self.stop == True:
self.start_button()
time.sleep(0.5)
You simply can't expect a GUI to behave properly with code like that. As a general rule of thumb you should never have the main thread of a GUI call sleep. Causing sleep prevents the event loop from processing any events, including low level events such as requests to refresh the screen.
The use of sleep has been asked and answered many times on stackoverflow. You might find some of those questions useful. For example,
windows thinks tkinter is not responding
Python Tkinter coords function not moving canvas objects inside loop
How do widgets update in Tkinter?
Tkinter multiple operations
Python Tkinter Stopwatch Error
You have another problem that falls into the category of a memory leak. From that while loop, you call self.start_button() indefinitely. This happens about once a second, due to sleep being called for half a second in the loop, and another half a second in start_button.
Each time you call start_button, you create another label widget that you stack on top of all previous widgets in row 9, column 2. Eventually this will cause your program to crash. I'm surprised that it causes your program to fail so quickly, but that's beside the point.
My recommendation is to start over with a simple example that does nothing but update a label every second. Get that working so that you understand the basic mechanism. Then, once it's working, you can add in your code that reads from the serial port.
May I suggest that you start over with the following code? You can port in back to Python 2 if needed, but your program has been rewritten to use Python 3 and has been designed to use tkinter's ability to schedule future events with the after methods. Hopefully, you will find the code easier to follow.
import collections
import timeit
import tkinter
def main():
root = Application()
root.setup()
root.mainloop()
class Application(tkinter.Tk):
def setup(self):
mf = self.__middle_frame = tkinter.Frame(self)
self.__middle_frame.grid()
bf = self.__bot_frame = tkinter.Frame(self)
self.__bot_frame.grid()
self.__port_set = False
self.__chip_number = 0
self.__chip_pass_num = 0
self.__chip_fail_num = 0
self.__chip_yield_num = 0
self.__stop = True
self.__widgets = collections.OrderedDict((
('COT', 'Continuity Test'), ('CHE', 'Chip Erase'),
('ERT', 'Erase Test'), ('WRT', 'Write Test'),
('WIRT', 'Wire Reading Test'), ('WIT', 'Wire Reading Test'),
('WRAT', 'Write All Test'), ('DO', 'Done')))
for row, (key, value) in enumerate(self.__widgets.items()):
label = tkinter.Label(mf, text=value+':')
label.grid(row=row, column=0, sticky=tkinter.E)
canvas = tkinter.Canvas(mf, bg='yellow', width=10, height=10)
canvas.grid(row=row, column=1)
self.__widgets[key] = label, canvas
self.__cn = tkinter.Label(mf, text='Chip Number:')
self.__cn.grid(row=8, column=0, sticky=tkinter.E)
self.__display = tkinter.Label(mf)
self.__display.grid(row=8, column=1, sticky=tkinter.E)
self.__button = tkinter.Button(bf, text='START',
command=self.__start_pre)
self.__button.grid(sticky=tkinter.E)
def __start_pre(self):
self.__button['state'] = tkinter.DISABLED
self.__start_button(0)
def __start_button(self, count):
if count < 100:
self.__chip_number += 1
self.__display['text'] = str(self.__chip_number)
self.__widgets['DO'][1]['bg'] = 'yellow'
start_time = timeit.default_timer()
print('Still Working:', self.__chip_number)
self.after(500, self.__end_button, count)
else:
self.__button['state'] = tkinter.NORMAL
def __end_button(self, count):
self.__widgets['DO'][1]['bg'] = 'green'
self.after(500, self.__start_button, count + 1)
if __name__ == '__main__':
main()