How to make a speedrun timer stay to the front - python

I made an in game timer for Minecraft to use while speedrunning and it works great but the one problem is that when I make Minecraft fill my whole screen NOT FULLSCREEN just cover it the timer disappears.
I knew this would happen and I am wondering if this is possible to fix and make the pygame window go to the front even if it is blocked by an app that you are currently using.

You can do this with Tkinter:
This way, you can make a timer:
Always put to front
Transparent
Furthermore, tkinter is in the Standard library.
You can use this code:
from tkinter import *
from threading import Thread
from time import time
tk = Tk()
tk.title('Timer')
tk.wm_attributes('-topmost', 1) # put the window to front
pause = True
start = time()
time_when_paused = time() # time displayed when in pause
def restart(): # restart to 0
global pause, start
pause = False
start = time()
def toggle_pause():
global pause, start, time_when_paused
pause = not pause
if pause:
time_when_paused = time() # update the time displayed
else:
start += time() - time_when_paused # forget the time passed in pause
def timer():
while True:
if pause:
label['text'] = '%.2f' %(time_when_paused - start) # %.2f for 2 decimals
label['fg'] = 'orange'
else:
label['text'] = '%.2f' %(time() - start)
label['fg'] = 'green'
label = Label(tk, '', font=('Helvetica', 30), fg='orange')
label.grid(columnspan=2) # display the timer
# buttons
restart_button = Button(tk, text='Restart', width=20, command=restart)
restart_button.grid(padx=5, pady=5)
pause_button = Button(tk, text='Pause', width=20, command=toggle_pause)
pause_button.grid(padx=(0, 5), column=1, row=1)
timer_thread = Thread(target=timer)
timer_thread.start() # put in a thread because of...
tk.mainloop() # ...this which acts like "while window is not closed"

Related

Why is the execution of my code (the program) happening faster, every time I hit the start button?

I just recently made a pomodoro clock implementation on python and I was facing some trouble with its execution but I came up with a solution for it but I observed a discrepancy in its execution.
So my whole code and implementation looks something like this (my code is not clean or pretty; sorry about that, I am one of those people who uses a mixture of tabs and spaces):
from tkinter import *
from time import *
from threading import Thread
# ---------------------------- CONSTANTS ------------------------------- #
PINK = "#e2979c"
RED = "#e7305b"
GREEN = "#9bdeac"
YELLOW = "#f7f5dd"
FONT_NAME = "Courier"
WORK_MIN = 25
SHORT_BREAK_MIN = 5*60000
LONG_BREAK_MIN = 20*60000
class Reset:
def __init__(self):
self.click = False
self.activation = False
def button_command(self):
self.click = True
def timer_command(self):
return self.click
# ---------------------------- TIMER RESET ------------------------------- #
timer_reset = Reset()
reset = timer_reset.click
# ---------------------------- TIMER MECHANISM ------------------------------- #
def timer_mechanism():
minute=0
if counter[0]<60000:
if counter[0] % 1000 == 0 and counter[0]/1000<10:
canvas.itemconfig(clock_counter, text=(f"00:0{int(counter[0]/1000)}"))
canvas.update_idletasks()
elif counter[0]% 1000==0 and counter[0]/1000>=10:
canvas.itemconfig(clock_counter, text=(f"00:{int(counter[0] / 1000)}"))
canvas.update_idletasks()
elif counter[0]>=60000:
if counter[0]%1000==0:
transition=int(counter[0]/1000)
while transition>=60:
minute+=1
transition-=60
second=transition
if second<10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:0{second}"))
canvas.update_idletasks()
elif second>=10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:{second}"))
canvas.update_idletasks()
elif minute>=10 and second<10:
canvas.itemconfig(clock_counter, text=(f"{minute}:0{second}"))
canvas.update_idletasks()
else:
canvas.itemconfig(clock_counter,text=(f"{minute}:{second}"))
canvas.update_idletasks()
# -----------------------------New countdown mechanism----------------------------#
i = [4]
def start_countdown():
if not timer_reset.click and i[0] > 0:
# keep listening to the function that receives from the button while doing an after for the 25 min and 10 min respectively.3
timer.config(text="Work", fg=GREEN)
window.update_idletasks()
# the solution to this problem is to break up the after process instead of doing it all at the same time make use of small increments so that this after could finish withing a second or two and then go to another function that contains all the timer_reset.click values update and come everytime until we reach a limit somewhere probably an array value that will be updated until we get to the 25 minute mark so this insures as the signal from the button will be accepted.
execute("Work")
# start=time()
# end=time()
# while end-start<5:
# end=time()
# timer_reset.click=timer_reset.timer_command()
else:
window.destroy()
timer_reset.click = False
i[0] = 4
beginning()
def rest():
global start_time
start_time=0
timer_reset.activation = True
if not timer_reset.click:
global restloop
restloop = True
global workloop
workloop = False
timer.config(text="Break", fg=PINK)
if i[0] == 4:
global frame
frame = Frame(window, width=20, height=20)
frame.grid(row=3, column=1)
tick = Label(frame, text="✔", font=(FONT_NAME, 12, "bold"), bg=YELLOW, fg=GREEN)
tick.pack(side=LEFT)
window.update_idletasks()
if i[0] > 0:
execute("Break")
if timer_reset.click:
timer_reset.click = False
window.destroy()
beginning()
else:
window.destroy()
beginning()
else:
window.destroy()
i[0] = 4
timer_reset.click = False
beginning()
i[0] -= 1
counter=[0]
def execute(identifier):
if identifier=="Work":
window.after(1,mirror,LONG_BREAK_MIN)
if identifier=="Break":
window.after(1,mirror,SHORT_BREAK_MIN)
def mirror(value):
if timer_reset.click:
window.destroy()
i[0]=4
timer_reset.click=False
counter[0]=0
beginning()
elif counter[0]<value:
counter[0]+=1
timer_mechanism()
if value==LONG_BREAK_MIN:
execute("Work")
if value==SHORT_BREAK_MIN:
execute("Break")
elif value==LONG_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
rest()
elif value==SHORT_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
start_countdown()
# ---------------------------- UI SETUP ------------------------------- #
def beginning():
global window
window = Tk(className="Pomodoro")
window.title("Pomodoro")
window.config(pady=50, padx=100, bg=YELLOW)
window.wm_state("normal")
global timer
timer = Label(text="Timer", font=(FONT_NAME, 50, "bold"), bg=YELLOW, foreground=GREEN)
timer.grid(row=0, column=1)
global canvas
canvas = Canvas(width=200, height=223, bg=YELLOW, highlightthickness=0)
tomato = PhotoImage(file="tomato.png")
canvas.create_image(100, 100, image=tomato)
global clock_counter
clock_counter=canvas.create_text(100, 120, text="00:00", fill="white", font=(FONT_NAME, 35, "bold"))
canvas.grid(row=1, column=1)
start = Button(text="Start", highlightthickness=0, borderwidth=0, command=start_countdown)
start.grid(row=2, column=0)
end = Button(text="Reset", highlightthickness=0, borderwidth=0, command=timer_reset.button_command)
end.grid(row=2, column=2)
window.mainloop()
beginning()
And the UI part of my code looks something like this:
So the discrepnancy I was talking about was that after starting the program and the countdown is happening if by mistake or whatever reason the user presses the Start button again the countdown starts to happen faster and if you keep pressing the start button without hitting the reset button it starts counting down even faster and faster.
Now I think I understand why this might be but this is like a sliver of what the solution might be, I might be completely wrong for all I know but is it because of the after method since every time the user hits the button he is starting another thread of the after method process and this kind of creates a sense/illusion of multiprocessing(parallelism)? I really don't know, to be honest. I might be stretching too far, but please any type of explanation would really be helpful even if it is a jab at my answer. I am very genuinely curious as to why this is happening.
Any help/suggestion will be really helpful.

How to Update Tkinter Label while looking for keyboard input

I am Trying to make a tkinter based Cube Timer Program for which I have done a console based program before I wanted to improve on it by making a GUI for it... I have made some tkinter projects before and I have a decent knowledge of how stuff works the thing is I want the Label in the tkinter window to update in every second while listening for a spacebar key on press I couldn't figure out a way to do it can someone help me?
def on_press(key):
global chk,start,end,timer_label
print('{0} pressed'.format(key))
if key == Key.space:
if chk == True:
end = time.time()
chk = False
messagebox.showinfo('Your Time',"Your Time =>"+str(round(end-start,3)))
def on_release(key):
global chk,start,end,timer_label
if key == Key.space:
if chk == False:
start = time.time()
chk = True
with Listener(on_press=on_press,on_release=on_release) as listener:
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
global timer_label
timer_label = tk.StringVar()
timer_label.set("0.0")
tk.Label(timer_frame,text = timer_label.get()+"s",font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
main.mainloop()
This is what I have tried but didn't work
You need main.after to periodically run a function like updating the label. To listen to keyboard event, you need main.bind. See example below.
Enhancement: use tk.Label(..., textvariable=timer_label) means the label will automatically update when you set timer_label
import tkinter as tk
def key_press(event):
if event.keysym == 'space':
print('pressed')
def key_release(event):
if event.keysym == 'space':
print('released')
def update_label():
# get the time from the string
time = float(timer_label.get()[:-1])
# increment the time and put it back in timer_label
timer_label.set(str(time+1) + 's')
# calling this function again 1000ms later, which will call this again 1000ms later, ...
main.after(1000, update_label)
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
#textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
timer_label = tk.StringVar()
timer_label.set("0.0s")
# using textvariable, you can simply update timer_label and it will be reflected
tk.Label(timer_frame,textvariable = timer_label ,font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
# bind keypress and keyrelease to the functions
main.bind("<KeyPress>", key_press)
main.bind("<KeyRelease>", key_release)
# call update label function for the first time
update_label()
main.mainloop()

countdown timer overlay in python with keypress Tkinter

Hello I am trying to make a simple script which on key press x starts a timer of 45 sec after that when it reaches 10 sec color text changes to red and when countdown comes 0 I want to destroy the timer gui but not the program, and when i press x again program starts doing stuff again and repeats the process
So far I managed this I tried all day I also tried adding on keypress but it become so complicated and didn't worked so I am asking help here
from tkinter import *
root = Tk()
root.geometry("112x55")
root.overrideredirect(True)
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
root.resizable(0, 0)
seconds = 45
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:
seconds.delete("1.0","end")
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=0, y=0)
timer() # start the timer
root.mainloop()
As you used wm_attributes('-transparentcolor', 'white') (but you never set the background color of root and the timer_display to white, so it don't have effect) and overrideredirect(True), that means you want the window totally transparent and borderless. However this has side effect that you may never get the window focus back after the window loses focus.
Also you used wm_attributes('-disabled', True), then you can't have any key press event triggered. It should not be used.
Suggest to use wm_attributes('-alpha', 0.01) to simulate the transparent effect without the above issues.
Below is an example:
from tkinter import *
root = Tk()
root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)
seconds = 45
def countdown(seconds):
if seconds > 0:
mins, secs = divmod(seconds, 60)
timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
fg='red' if seconds <= 10 else 'white')
root.after(1000, countdown, seconds-1)
else:
root.wm_attributes('-alpha', 0.01) # make the window "disappear"
def start_countdown(event):
root.wm_attributes('-alpha', 0.7) # make the window "appear"
countdown(seconds)
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()
root.bind('x', start_countdown)
root.bind('q', lambda e: root.destroy()) # provide a way to close the window
root.mainloop()
NOTE #1: If the window loses focus, you can still click somewhere near the top-left corner of the screen to resume window focus, although you cannot see the window.
NOTE #2: If you want system-wise key handler, tkinter does not support it. You need to use other module, like pynput.
If you want to destroy the timer GUI, You'll need to make a class like this:
class Timer(Frame):
def __init__(self, master):
super().__init__(master)
# Paste the code you want to run here, make sure you put "self." before it.
# For example:
def clicked():
print('Clicked!')
self.myButton = Button(self, text="Click me!", border=0, width=25, height=1, command=self.clicked)
self.logbtn.grid(columnspan=2)
self.pack()
if seconds == 0:
self.destroy() # by using self.destroy(), you tell it to delete the class.
else:
# You can put whatever you want it to do if it's not 0 here.
tm = Timer(root)
You can bind functions to keystroke events with root.bind(event, callback).
If you are using Linux or Mac, root.overrideredirect(True) will
prevent your application from receiving keystroke events. You can read
more here: Tkinter's overrideredirect prevents certain events in Mac and Linux
Example:
def keydown(e):
print(f"Key pressed: ")
print("Key code:", e.keycode)
print("Key symbol:", e.keysym)
print("Char:", e.char)
def keyup(e):
print(f"Key '{e}' released")
root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)
root.focus_set()
Alternatively, you can also bind to specific keys with <Key-KEYSYM>, e.g. <Key-space> for the spacebar. A list with all keysyms can be found here
Some more events are listed here
Implementation example
Here is an example with a custom CountdownLabel class that is derived from tkinter.Label and automatically binds to the spacebar key event.
app.py
from countdown import CountdownLabel
from tkinter import Frame, StringVar, Tk, Button
root = Tk()
root.geometry("120x60")
root.lift()
root.wm_attributes("-topmost", True)
root.resizable(0, 0)
# Not supported on Linux and MacOS
# root.overrideredirect(True)
# root.wm_attributes("-disabled", True)
# root.wm_attributes("-transparentcolor", "white")
timer_display = CountdownLabel(root, 10, 5)
timer_display.pack(fill="both", expand=True)
timer_display.configure(background="white")
timer_display.configure(font=('Trebuchet MS', 26, 'bold'))
timer_display.focus_set()
root.mainloop()
countdown.py
from tkinter import Label
class CountdownLabel(Label):
# context : A reference to the Label in order to change the text and
# to close it later on
# duration: Total time in seconds
# critical: Length of the last timespan before the countdown finishes
# in seconds
def __init__(self, context, duration, critical):
super().__init__(context)
self.duration = duration
self.critical = critical if duration >= critical else duration
self.update_ui()
self.bound_sequence = "<Key-space>"
self.bound_funcid = self.bind(self.bound_sequence, self.get_handler())
# Returns a function for the event binding that still has access to
# the instance variables
def get_handler(self):
# Gets executed once when the counter starts through handler() and calls
# itself every second from then on to update the GUI
def tick():
self.after(1000, tick)
self.update_ui()
self.duration -= 1
# Gets executed when time left is less than <critical> (default = 10s)
# Sets the font color to red
def change_font_color():
self.configure(foreground="red")
# Destroys itself after the countdown finishes
self.after((self.critical + 1) * 1000, lambda : self.destroy())
def handler(event):
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_funcid = -1
self.bound_sequence = None
self.after((self.duration - self.critical) * 1000, change_font_color)
tick()
return handler
# Updates the displayed time in the label
def update_ui(self):
mm = self.duration // 60
ss = self.duration % 60
self.config(text="%02d:%02d" % (mm, ss))
def change_binding(self, sequence):
if self.bound_funcid > 0:
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_sequence = sequence
self.funcid = self.bind(self.bound_sequence, self.get_handler())

make a window flash like wrning signal light specific number of times and carry out some function after it

i want to make a notifying application in python using tkinter.
i want to check the system time if it matches the time from database time field of any row a window show flash on and off some specific number of times like danger warning lights.after the flashes finished those number of times a window should come showing about the events/scheduled that are to notified to the user.
i have tried to write the code but it makes the window appear only once.
i have tried using the root.after method of tkinter but dont know how to implement the requirements i want in the application.
#repeatedly checking if system time matched any teachers scedule time--------------------------------
def repeatedquery():
def alertmsg():
for j in range (4):
newteacheradd.withdraw()
messagewindow = Toplevel()
#to come out press alt+escape
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification")
msgflash = Label(messagewindow, text='Notice', bg="#1f618d", fg='white', font=("Verdana", 28, 'bold'),
height=5,width=40, relief=RAISED)
msgflash.place(x=150, y=300)
print ("hjgj")
time.sleep(10)
messagewindow.after(15000, messagewindow.withdraw)
messagewindow.mainloop()
def showMesagewin():
newteacheradd.withdraw()
global messagewindow
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification Screen")
tt = '{:3}'.format(str(i[0])) + ' {:15}'.format(str(i[1])) + ' {:15}'.format(
str(i[2])) + ' {:15}'.format(str(i[3])) + '\n'
msg = 'YOUR LECTURE/PRAC DETAILS \n\n'+" "+tt
# to place message at centre
msgflash = Label(messagewindow, text=msg, bg="#1f618d",fg='white', font=("Verdana", 28,'bold'),height=5,relief = RAISED)
msgflash.place(x=250, y=300)
#Bell rings ___________________________________________________________________________________________
pygame.init()
pygame.mixer.init()
sounda = pygame.mixer.Sound("bell.wav")
sounda.play()
time.sleep(5)
#Belll rings____________________________________________________________________________________________
messagewindow.after(5000, messagewindow.withdraw)
# schedule closing of showMesagewin event in 5 seconds
messagewindow.mainloop()
currentDT = datetime.datetime.now()
t=currentDT.strftime("%H")
conn = sq.connect("Teacher.db")
curr = conn.cursor()
tid = __userip.get()
day2 = datetime.datetime.now()
day3 = day2.strftime("%A")
curr.execute("select Time,Subject,Lecture_prac,Venue from "+day3+" where Teacher_ID=?", (tid,))
sc = curr.fetchall()
t1=0
for i in sc:
if (t == i[0]):
alertmsg()
print ('gbhj')
showMesagewin()
newteacheradd.after(1000,repeatedquery)
#-------------------------------------------------------------------------------------------------
repeatedquery()
Edited Code
#repeatedly checking if system time matched any teachers scedule time--------------------------------
from tkinter import *
import datetime
import time
def mainwin():
newteacherappend=Tk()
i=[]
i.append(2)
i.append('Phy')
i.append('L')
i.append('301')
def repeatedquery():
def alertmsg():
for j in range (4):
newteacherappend.withdraw()
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification")
msgflash = Label(messagewindow, text='Notice', bg="#1f618d", fg='white', font=("Verdana", 28, 'bold'),
height=5,width=40, relief=RAISED)
msgflash.place(x=150, y=300)
print ("hjgj")
time.sleep(5)
messagewindow.after(5000, messagewindow.withdraw)
messagewindow.mainloop()
def showMesagewin():
newteacherappend.withdraw()
global messagewindow
messagewindow = Toplevel()
# Covers whole screen to come out press Alt + Esc
messagewindow.wm_attributes("-fullscreen", 1)
messagewindow.title("Notification Screen")
tt = '{:3}'.format(str(i[0])) + ' {:15}'.format(str(i[1])) + ' {:15}'.format(
str(i[2])) + ' {:15}'.format(str(i[3])) + '\n'
msg = 'YOUR LECTURE/PRAC DETAILS \n\n'+" "+tt
# to place message at centre
msgflash = Label(messagewindow, text=msg, bg="#1f618d",fg='white', font=("Verdana", 28,'bold'),height=5,relief = RAISED)
msgflash.place(x=250, y=300)
messagewindow.after(5000, messagewindow.withdraw)
# schedule closing of showMesagewin event in 5 seconds
messagewindow.mainloop()
currentDT = datetime.datetime.now()
t=currentDT.strftime("%H")
t=2
if (t == i[0]):
alertmsg()
print ('gbhj')
showMesagewin()
newteacherappend.after(1000,repeatedquery)
repeatedquery()
newteacherappend.mainloop()
mainwin()
I made code which use after without sleep to display flashing messages - first after after 5 seconds, second after 15 second. And it has only one mainloop().
More information in code
import tkinter as tk
import datetime
# --- functions ----
# function which creates window with message
def message_start(text):
global repeates
global message_window
global message_label
repeates = 3 # how many times change background color
# create window with messages
message_window = tk.Toplevel()
message_label = tk.Label(message_window, text=text, bg='red')
message_label.pack()
# update window after 500ms
root.after(500, message_update)
# function which changes background in displayed window
def message_update():
global message_window
global repeates
if repeates > 0:
repeates -= 1
if message_label['bg'] == 'red':
message_label['bg'] = 'green'
else:
message_label['bg'] = 'red'
# update window after 500ms
root.after(500, message_update)
else:
# close window
message_window.destroy()
# inform `check_time` that window is not busy
message_window = None
# loop which updates current time and checks which message it has to display
def check_time():
# display current time
current_time = datetime.datetime.now()
root_label_time['text'] = current_time.strftime('%Y.%m.%d %H:%M:%S')
# check if there is message to display
for message in messages:
if current_time >= message['start']: # it is time to display message
if message['state'] == 'waiting': # message is not displayed at this moment
if message_window is None: # window is not busy
message['state'] = 'displayed' # don't display it again
message_start(message['text']) # display message
# update time after 1000ms (1s)
root.after(1000, check_time)
# --- main ---
messages = [
{
'text': 'Time for coffee',
'start': datetime.datetime.now() + datetime.timedelta(seconds=5),
'state': 'waiting',
},
{
'text': 'Back to work',
'start': datetime.datetime.now() + datetime.timedelta(seconds=15),
'state': 'waiting',
},
]
message_window = None
repeates = 3
# ---
root = tk.Tk()
# label with current time
root_label_time = tk.Label(root, text='- wait -')
root_label_time.pack()
# label with sheduler
root_label_sheduler = tk.Label(root)
root_label_sheduler.pack()
# displa messages in sheduler
for message in messages:
root_label_sheduler['text'] += "\n" + message['start'].strftime('%Y.%m.%d %H:%M:%S ') + message['text']
# start displaying time
check_time()
root.mainloop()

Python tkinter GUI freezing/crashing

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()

Categories

Resources