Python tkinter GUI freezing/crashing - python

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

Related

How do I implement a stop button with Tkinter for a stepper motor system?

I have a question regarding the use of a stop button in Tkinter.
For an experiment, I have to set up and X/Y stage that works by using two stepper motors. The arduino program works perfectly. The only problem is that when I activate the start function, which drives the stage to various coordinates, it freezes. Now the problem is that it has to run for weeks on end and it needs a stop button for emergencies and stopping the stepper motor in general. The stop button has to do two things: it has to stop the stepper driver motors, and it has to break the tkinter.after loop. However, due to the freezing, it is impossible to click on the button.
Here is my code:
import tkinter as tk
import serial
ser = serial.Serial('COM5', 115200)
running = False
def quit():
"""Function that closes the serial port and destroys the root of the GUI"""
global root
ser.close()
root.destroy()
def route():
"""Writes coordinates to the arduino, which in return drives the stepper motors"""
if running == True:
# The g line stands for go to!
ser.write(b'g115000\r\n')
root.after(50)
ser.write(b'g225000\r\n')
root.after(30000)
ser.write(b'g1400\r\n')
root.after(50)
ser.write(b'g2500\r\n')
root.after(12000,route())
def zeroing():
"""Zeros the program, this is necessary for the stage to
calibrate it's boundary conditions"""
#zeros the stage so that it is ready to use!
varLabel.set("zeroing, please move away from the stage")
#the z command zeros the motors for boundary business
ser.write(b'z\r\n')
def run_program():
"""Runs the function Route and sets running to True (not a good start/stop system)"""
#starts the program, but only after you zero the stage
global running
running = True
varLabel.set("Program running")
route()
def stop_program():
"""Sets the running flag to False and sends a stop command to the arduino"""
#stops the program immediately
global running
running = False
varLabel.set("Program stopped,please zero before continuing")
#the s byte is a command that stops the stepper motors
ser.write(b's\r\n')
if __name__== "__main__":
root = tk.Tk()
canvas1 = tk.Canvas(root, width=800, height=400)
canvas1.pack()
root.title('XY-stage controller')
#instructions
instructions = tk.Label(root,text='Enter the amount of hours you want your measurements to last in the text box.'
'\n Click on run program to start a measurement session.'
'\n Click on stop incase of an emergency or if it is wanted to stop the program.',
font = "Raleway")
instructions.pack(side='bottom')
# initialize active labels
varLabel = tk.IntVar()
tkLabel = tk.Label(textvariable=varLabel,)
tkLabel.pack(side='top')
# Buttons for initializing a bunch of good functions
zerobutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Zero',
command = zeroing,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
runprogbutton = tk.IntVar()
tkrunprogram= tk.Button(
root,
text='Run Program',
command = run_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'green'
)
tkrunprogram.pack(side='top')
stopbutton = tk.IntVar()
tkstopprog= tk.Button(
root,
text='Stop Program',
command = stop_program,
height = 4,
fg = "black",
width = 10,
bg = 'gray',
bd = 5,
activebackground = 'red'
)
tkstopprog.pack(side='top')
Buttonquit = tk.IntVar()
tkButtonQuit = tk.Button(
root,
text='Quit',
command = quit,
height = 4,
fg = "black",
width = 10,
bg = 'yellow',
bd = 5
)
# initialize an entry box
entry1 = tk.Entry(root)
durbox = canvas1.create_window(400, 200, window=entry1)
tkButtonQuit.pack(side='top')
root.mainloop()
The after commands in the end will introduce pauses of 60 minutes, which would make the program freeze for 60 minutes. Hopefully there is an easy solution to interrupting the function!
Thank you in advance!
You can make use of multithreading. Make all the communication in a separate thread and also make sure you don't update the GUI components in the child thread.
Here is a minimal example:
import serial
import tkinter as tk
from threading import Thread
import time
def start():
global running
stop()
btn.config(text="Stop", command=stop)
running = True
info_label["text"] = "Starting..."
thread = Thread(target=run, daemon=True)
thread.start()
def run():
ser = serial.Serial("COM5", 115200, timeout=2)
while running:
ser.write(b'g115000\r\n')
time.sleep(50)
ser.write(b'g225000\r\n')
time.sleep(30000)
ser.write(b'g1400\r\n')
time.sleep(50)
ser.write(b'g2500\r\n')
ser.write(b's\r\n')
ser.close()
def stop():
global running
running = False
info_label["text"] = "Stopped"
btn.config(text="Start", command=start)
root = tk.Tk()
running = False
info_label = tk.Label(root, text="INFO:")
info_label.pack()
btn = tk.Button(root, text="Start", command=start)
btn.pack()
root.mainloop()
after(x000) is effectively the same as time.sleep(x) - it puts the whole app to sleep. As a general rule of thumb, you should never do this in the same thread as the GUI. That doesn't mean you need to use threads, however.
tkinter's after method lets you schedule commands to run in the future. If the commands you are running are fast such as sending a few bytes down a serial connection, this is really all you need. It is less complex and has less overhead than using threads.
For example, your route function can probably be written something like this:
def route():
if running == True:
# immediately write this:
ser.write(b'g115000\r\n')
# after 50ms, write this:
root.after(50, ser.write, b'g225000')
# after 30 more seconds, write this
root.after(50+30000, ser.write, b'g1400\r\n')
# and then after 50ms more, write this
root.after(50+30000+50, ser.write, b'g2500\r\n')
# and finally, after 12 seconds, do it all again
root.after(50+30000+50+12000,route)
Once you call this once, you don't need to call it again, and you don't need to call it in a thread. It simply places some work on a queue that gets picked up some time in the future.
Since each call to root.after returns an id, you can save these ids so that in the case of wanting to stop everything, you can call after_cancel on each saved id.
Another way is to define a job as a series of delays and then bytes to write. For example:
job = (
(0, b'g115000\r\n'),
(50, b'g225000'),
(30000, b'g1400\r\n'),
(50, b'g2500\r\n'),
)
Then, your route function can look something like this (untested, but this is pretty close)
def route(job):
global after_id
delay = 0
for (delta, data) in job:
delay += delta
root.after(delay, ser.write, data)
delay += 12000
root.after(delay, route, job)
There are many variations of that theme. For example, you could create a Job class that implements this logic, or the job could contain the commands rather than the data. The point being, you can define a data structure that defines work to be done, and then use after to schedule that work.

How to show every change to an class object in Tkinter?

I have this skeleton of a game in two files.
First file mygui.py:
from tkinter import Tk, Label, Button
from mygame import p1, play_game
def rungame():
play_game()
gui_widgets()
root = Tk()
def gui_widgets():
health_label = Label(root, text=f"health: {p1.health}")
health_label.grid(column=0, row=0)
mana_label = Label(root, text=f"mana: {p1.mana}")
mana_label.grid(column=0, row=1)
mybtn = Button(root, text="RUN GAME", command=rungame)
mybtn.grid(column=0, row=2)
gui_widgets()
root.mainloop()
Seccond file mygame:
import time
class Player:
def __init__(self, health, mana):
self.health = health
self.mana = mana
def play_game():
p1.health -= 1
time.sleep(0.5)
p1.mana -= 3
time.sleep(0.5)
p1.mana += 1
p1 = Player(10, 15)
The game shows the begining statistic on gui and after clicking button it shows the end result. I want gui to show every p1 atribute change made in play_game() as they are happening. How would I do this?
Edit: I am keen on keeping two seperate files so that program would be more scalable.
I am not sure if this is possible, but I would figure that if I could run gui_widgets() continuosly (for example every 0,1 secconds) It would probalby work as intended. Still I understand that this may be impossible since tkinter seems to wait everytime when play_game()is running, witch takes at least one seccond to finish. So in that case they should probalby run alongside each other (if that even is a thing).
There maybe lot of ways for it.
Here's one way
Define __setattr__ method to send signal to buffer to update GUI.
sleep in main thread may block your GUI to update, so another thread used here.
A fixed and short timer to fresh/update GUI from the content of buffer.
# mygui.py
import threading
from tkinter import Tk, Label, Button
from mygame import p1, play_game, buffer
def rungame():
threading.Thread(target=play_game, args=(), daemon=True).start()
def update():
if buffer:
attribute, value = buffer.pop(0)
if attribute == 'health':
health_label.configure(text=f"health: {value}")
health_label.update()
elif attribute == 'mana':
mana_label.configure(text=f'mana: {value}')
mana_label.update()
root.after(100, update)
def gui_widgets():
health_label = Label(root, text=f"health:", width=20)
health_label.grid(column=0, row=0)
mana_label = Label(root, text=f"mana:", width=20)
mana_label.grid(column=0, row=1)
mybtn = Button(root, text="RUN GAME", command=rungame)
mybtn.grid(column=0, row=2)
return health_label, mana_label
root = Tk()
health_label, mana_label = gui_widgets()
root.after(100, update)
root.mainloop()
#my game.py
import time
class Player(object):
def __init__(self, health, mana):
self.health = health
self.mana = mana
def __setattr__(self, attribute, value):
super().__setattr__(attribute, value)
buffer.append((attribute, value))
def play_game():
for i in range(10):
p1.health = i
time.sleep(0.2)
p1.mana = 9 - i
time.sleep(0.2)
buffer = []
p1 = Player(10, 15)

mainloop overides while loop in tkinter

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

Python Set Button Text While busy

I'm new to python and I am trying to create a program but I can't even get the basics right. I have a button app that looks like this:
#simple GUI
from tkinter import *
import time
#create the window
root = Tk()
#modify root window
root.title("Button Example")
root.geometry("200x50")
button1state = 0
def start():
count = 0
button1["text"] ="Busy!"
while (count < 5):
root.after(1000)
count = count + 1
def button1clicked():
global button1state
if button1state == 0:
start()
button1["text"] ="On!"
button1state = 1
else:
button1["text"] ="Off!"
button1state = 0
app = Frame(root)
app.pack()
button1 = Button(app, text ="Off!", command = button1clicked)
button1.pack()
#kick off the event loop
root.mainloop()
Now everything works except it doesn't change the button text to busy while
**start()** is called. How can I fix this? Once I've got it working I want to use images to show the user that its OFF ON and BUSY. Please help me
You need to force the GUI to update before starting the task:
def start():
count = 0
button1.configure(text="Busy!")
root.update() # <-- update window
while (count < 5):
root.after(1000)
count = count + 1
But if you don't want your GUI to be frozen while the task is executed, you will need to use a thread as Dedi suggested.
You have to make a thread in order to make you function as a "background event" while your interface is working. Consider using that :
from threading import Thread
and then :
my_thread=Thread(target=start())
my_thread.start()
Where the first "start()" is the name of your function and the second one a call for the thread to begin.

When you move Tkinter windows stops program

Hi
I made a music downloading program that works great it tells you the percent its done and then if i move the window at all it stops downloading .
I made a diffrent little script that downloads a specified mp3 on the web and i can move it as much as i want and it doesent stop.
the only reason im not putting up the code is that it is really long. Its around 1500 lines. Here is the small script that i made to download one file.
does any one know why it stops the program from working?
the little script:
from Tkinter import *
from urllib2 import *
admin = Tk()
Admin = Tk()
listbox = Listbox(admin, bg="PURPLE")
listbox.pack()
def __init__(self, master):
def replay():
Admin.destroy()
os.system('WhaleWire.exe')
frame = Frame(master)
frame.pack()
image1 = PhotoImage(file="whalewire.gif")
w = image1.width()
h = image1.height()
master.geometry("%dx%d+0+0" % (w, h))
# tk.Frame has no image argument
panel1 = Label(master, image=image1)
panel1.pack(side='top', fill='both', expand='yes')
panel1.image = image1
self.e = Entry(frame)
self.e.grid(row=0, column=0)
b = Button(frame, text='Search', command=self.whale)
b.grid(row=0, column=1)
def library():
path = 'C:\WhaleWire\Downloaded'
aw=[]
for infile in glob.glob( os.path.join(path,'*.mp3') ):
libr = infile.split('Downloaded',1)
aw.append('\n')
aw.append(infile)
la = Label(Admin,width=100,height=50, text=aw).grid(row=0,column=7)
b2s = Button(Admin,text='Search', command=replay).grid(row=0,column=8)
b11 = Button(frame, text='Library', command=library)
b11.grid(row=0, column=3)
def fores():
chunks = 10000
dat = ''
song = '3 rounds and a sound'
url = 'http://bonton.sweetdarkness.net/music/Blind%20Pilot%20--%203%20Rounds%20and%20A%20Sound.mp3'
down = urlopen(url)
downso = 0
tota = down.info().getheader('Content-Length').strip()
tota = int(tota)
while 1:
a = down.read(chunks)
downso += len(a)
if not a:
break
dat += a
percent = float(downso) / tota
percent = round(percent*100, 1)
listbox.insert(END, percent)
listbox.update()
listbox.delete(0, END)
listbox.insert(END, percent)
listbox.update()
button = Button(Admin, text='Download', command=fores)
button.pack()
button = Button(Admin, text='Download', command=fores)
button.pack()
mainloop()
Most likely the problem is because you are calling update. You should never do that unless you know for certainty what the ramifications are. update causes a new event loop to be entered. Essentially, you end up with an infinite loop inside an infinite loop.
Try changing your update to update_idletasks and see if that solves your problem. This variation of update only processes "idle" events such as screen redraws and is considerably less likely to cause problems.
Also, you definitely don't need "update; insert; delete; update". That won't have any noticeable effect. A single call to update_idletasks after the delete is sufficient.
Finally, you can avoid the use of update_idletasks completely by rearranging your code. Write a function that reads a single chunk of data and updates the progress bar. Then, if it hasn't reached EOF, use after to call that function again a few milliseconds later. When it reaches EOF it stops calling itself. Doing this means you don't have to create your own potentially infinite loop, and the event loop is guaranteed to be entered once per iteration. Once this EOF is detected you can then call a function (again using after) to do any final processing.

Categories

Resources