Tkinter: Separating Processes - python

I am trying to build a Tkinter program that supports multiprocessing. I need to read from multiple Modbus devices and display the output onto the GUI.
I have successfully done this with a command line by using processes, but in Tkinter, my GUI freezes every time I attempt to read.
Here is my code:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.process)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def reader():
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
When I press the start button the program will freeze until the process is done. How do I correctly set up a system to have the GUI operational while having processes run?

The simplest solution would be to put this all on a separate thread. The reason your GUI freezes up is because the process method has consumed the Main Thread and until it completes, Tkinter won't update anything.
Here's a simple example:
self.timer_button = tk.Button(self, text='Start', command=lambda: threading.Thread(target=self.process).start())
However, this doesn't stop the user from clicking the button twice. You could create a new method which controls this. Example:
import os
from multiprocessing import Process
import threading
import queue
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
import time as ttt
import minimalmodbus
import serial
minimalmodbus.CLOSE_PORT_AFTER_EACH_CALL = True
THREAD_LOCK = threading.Lock()
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.gas = minimalmodbus.Instrument('COM3', 1)
self.gas.serial.baudrate = 9600
self.gas.serial.parity = serial.PARITY_NONE
self.gas.serial.bytesize = 8
self.gas.serial.stopbits = 1
self.gas.serial.timeout = 0.25
self.gas.mode = minimalmodbus.MODE_RTU
self.pack()
self.create_widgets()
def create_widgets(self):
self.first_gas_labelframe = LabelFrame(self, text="Gas 1", width=100)
self.first_gas_labelframe.pack()
self.value_label = Label(self.first_gas_labelframe, text="Value")
self.value_label.pack()
self.unit_label = Label(self.first_gas_labelframe, text="Unit")
self.unit_label.pack()
self.temp_label = Label(self.first_gas_labelframe, text="Temp")
self.temp_label.pack()
self.timer_button = tk.Button(self, text='Start', command=self.start_thread)
self.quit = tk.Button(self, text="QUIT", fg="red", command=root.destroy)
self.quit.pack()
self.gas_list = [self.gas]
def check_thread(self):
if self.thread.is_alive():
root.after(50, self.check_thread)
else:
# Thread completed
self.timer_button.config(state='normal')
def start_thread(self):
self.timer_button.config(state='disabled')
self.thread = threading.Thread(target=self.process)
self.thread.start()
root.after(50, self.check_thread)
def reader(self):
self.read = gas_num.read_registers(0,42)
self.value_label.config(text=self.read[0])
self.unit_label.config(text=self.read[1])
self.temp_label.config(text=self.read[2])
def process(self):
with THREAD_LOCK:
for sen in self.gas_list:
self.proc = Process(target=self.reader, args=(sen,))
self.proc.start()
self.proc.join()
if __name__ == '__main__':
root = tk.Tk()
app = Application()
app.mainloop()
The Lock is to ensure no two threads are running at the same time. I've also disables the button so that the user can't click until it's done. By using the root.after method, you can create callbacks that wait for a period of time before running.
As far as the multiprocessing, you are running the processes on a separate process, but only one at a time. If you want to run many at one time, then you need to move the join call somewhere else. I'm not sure how many processes are running at once but you could do something like this:
processes = []
for sen in self.gas_list:
proc = Process(target=self.reader, args=(sen,))
processes.append(proc)
proc.start()
[x.join() for x in processes]
In this implementation, I've removed assigning proc as a class variable.
I do not have the libraries or data to properly test all this, but it should work...
EDIT
This will initiate a pool of 6 processes that loop through self.gas_list, passing the item to self.reader. When those complete, it will check to make sure a second has gone by (waiting if it hasn't) and restarting the above process. This will run forever or until an exception is raised. You will need to import Pool from the multiprocessing module.
def process(self):
with THREAD_LOCK:
pool = Pool(6)
while 1:
start_time = time.time()
pool.map(self.reader, self.gas_list)
execution_time = time.time() - start_time
if execution_time < 1:
time.sleep(1-execution_time)

Related

trying to Browse processes with gui using tkinter

Im trying to make a program and i need to link two process together. if one of them stoped the other one stops too and for some reason my gui lagging when trying to browse process and check the conditions i made for it.here is the short video from my problem enter link description here.
import tkinter as tk
from tkinter import messagebox
import psutil
import os
class myapp():
def __init__(self):
self.win = tk.Tk()
self.win.title("test")
self.win.geometry('200x100')
self.win.config(bg='black')
self.win.resizable(False,False)
self.ctr = 0
self.tk_var = tk.StringVar()
self.tk_var.set("0")
lab=tk.Label(self.win, textvariable=self.tk_var,
bg='black', fg='#FF0000')
lab.place(x=90, y=45)
btn = tk.Button(self.win,text='test',command='',
bd=0,bg='#7E1600',width=10,
activebackground='#6D0000')
btn.pack(pady=20)
self.upd()
self.test()
self.win.mainloop()
def upd(self):
self.ctr += 1
self.tk_var.set(str(self.ctr))
self.win.after(50,self.upd)
def test(self):
self.service = "notepad.exe" in (i.name() for i in psutil.process_iter())
if self.service != True :
er = messagebox.showerror(title='error', message='notepad.exe has been stopped')
if er == 'ok':
self.win.destroy()
else:
self.win.after(500,self.test)
os.popen('notepad')
myapp()
problem
I'm still working on my English.
import tkinter as tk
from tkinter import messagebox
import psutil
import os
class myapp():
def __init__(self):
self.win = tk.Tk()
self.win.title("test")
self.win.geometry('200x100')
self.win.config(bg='black')
self.win.resizable(False, False)
self.ctr = 0
self.tk_var = tk.StringVar()
self.tk_var.set("0")
lab = tk.Label(self.win, textvariable=self.tk_var,
bg='black', fg='#FF0000')
lab.place(x=90, y=45)
btn = tk.Button(self.win, text='test', command='',
bd=0, bg='#7E1600', width=10,
activebackground='#6D0000')
btn.pack(pady=20)
self.upd()
self.win.mainloop()
def upd(self):
self.ctr += 1
self.tk_var.set(str(self.ctr))
self.win.after(50, self.test)
def test(self):
self.service = "notepad.exe" in (i.name() for i in psutil.process_iter())
if self.service != True:
er = messagebox.showerror(title='error', message='notepad.exe has been stopped')
if er == 'ok':
self.win.destroy()
self.win.after(500, self.upd)
os.popen('notepad')
myapp()
upd->test->upd->test... just It only takes one call upd. No need to call test. Also, the after function call is wrong. See my modified code. It's hard to explain in my short English.
I renamed some functions in my example for better understanding...
I see that your check is too fast, which can cause lag on lower computers, also, keep in mind that when you call the 'check_notepad' function when initializing the class, whenever you click on the 'test' button it adds a new check , that is, another check loop comes into action and this cascades making your app increasingly heavy in memory...
My solution, in addition to some improvements in the code, is just to start the checker once, so as not to create cascades of checks, and from that your app does not lag anymore, if you need to leave it 'on' using a checkbox instead of a button can do more sense...
import os
import tkinter as tk
from tkinter import messagebox
import psutil
class myapp(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# styles
self.geometry('200x100')
# vars
self.timer = 500
# widgets
self.tk_var = tk.IntVar(value=0)
lab = tk.Label(self, textvariable=self.tk_var)
lab.place(x=90, y=45)
self.check_var = tk.BooleanVar()
btn = tk.Checkbutton(self, text='Test', variable=self.check_var)
btn.pack(pady=20)
# Starts
self.update_label()
self.check_notepad()
def update_label(self):
self.tk_var.set(self.tk_var.get() + 1)
self.after(self.timer, self.update_label)
def check_notepad(self):
if self.check_var.get():
if not notepad_is_open():
er = messagebox.showerror(title='error',
message='notepad.exe has been stopped')
if er == 'ok':
self.destroy()
# call back loop
self.after(self.timer, self.check_notepad)
def notepad_is_open():
service = "notepad.exe" in (i.name() for i in psutil.process_iter())
return service
if __name__ == '__main__':
if not notepad_is_open():
os.popen('notepad')
myapp = myapp()
myapp.mainloop()

Main thread is not in main loop

I am trying to ping and get the result of 100+ IPs within 1-2 sec.But running this code using Tkinter is giving me the following error please help me with this.
RuntimeError: main thread is not in main loop
Attaching the code. Please have a look at it and let me know if any other information is needed
Thank you.
import tkinter.messagebox
from tkinter import ttk
import os
import socket
import sys
import subprocess
import re
import threading
from tkinter import *
import multiprocessing.dummy
import multiprocessing
class Demo1:
data=[]
def __init__(self, master):
self.master = master
self.label=tkinter.Label(text="Add IP/Hostname")
self.label.pack()
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.pack()
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.pack()
def new_window(self):
self.inputValue=self.t.get("1.0",'end-1c')
Demo1.data=self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.configure(bg='#6EBFE4')
self.master.mainloop()
class Demo2(Demo1):
t1=[]
s1=True
display=[]
def __init__(self, master):
self.master=master
self.kas(master)
def kas(self,master):
Demo2.t1=Demo1.data
self.master = master
cols = ('IP','Ping status')
self.listBox = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.listBox.heading(col, text=col)
self.listBox.column(col,minwidth=0,width=170)
self.listBox.column('#0',width=50)
self.listBox.grid(row=1, column=0, columnspan=2)
self.ping_range(Demo2.t1)
def ping_func(self,ip):
p=[]
pingCmd = "ping -n 1 -w 1000 " + ip
childStdout = os.popen(pingCmd)
result = (childStdout.readlines())
childStdout.close()
p.append(ip)
if (any('Reply from' in i for i in result)) and (any('Destination host unreachable' not in i for i in result)):
p.append("sucess")
else:
p.append("failed")
for i,(a) in enumerate(p):
self.listBox.insert('', 'end',value=(a))
return result
def ping_range(self,ip_list):
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in ip_list])
def main():
root = tkinter.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
I have tried the code using queue and after(). but still getting the same error. not have much idea on these please guide me where i am going wrong and what can i do to correct it. Thank you
import tkinter.messagebox
from tkinter import ttk
import os
import socket
import sys
import subprocess
import re
import threading
import multiprocessing.dummy
import multiprocessing
import time, queue
class Demo1:
data=[]
def __init__(self, master):
self.master = master
self.label=tkinter.Label(text="Add IP/Hostname")
self.label.pack()
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.pack()
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.pack()
def new_window(self):
self.inputValue=self.t.get("1.0",'end-1c')
Demo1.data=self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.configure(bg='#6EBFE4')
self.master.mainloop()
class Demo2(Demo1):
t1=[]
s1=True
display=[]
def __init__(self, master):
self.master=master
self.kas(master)
def kas(self,master):
self.running = True
self.queue = queue.Queue() #queue
Demo2.t1=Demo1.data
self.master = master
cols = ('IP','Ping status')
self.listBox = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.listBox.heading(col, text=col)
self.listBox.column(col,minwidth=0,width=170)
self.listBox.column('#0',width=50)
self.listBox.grid(row=1, column=0, columnspan=2)
#self.ping_range(Demo2.t1)
self.running = True
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in Demo2.t1])
def ping_func(self,ip):
while self.running:
pi=[]
pingCmd = "ping -n 1 -w 1000 " + ip
childStdout = os.popen(pingCmd)
result = (childStdout.readlines())
childStdout.close()
pi.append(ip)
if (any('Reply from' in i for i in result)) and (any('Destination host unreachable' not in i for i in result)):
pi.append("sucess")
else:
pi.append("failed")
self.queue.put(pi) #Thread value to queue
m = self.queue.get_nowait()
print(m) #getting the correct value but after this statement, getting error as main thread is not in main loop
for i,(a) in enumerate(m):
self.listBox.insert('', 'end',value=(a))
self.periodic_call()
def periodic_call(self):
self.master.after(200, self.periodic_call) #checking its contents periodically
self.ping_func()
if not self.running:
import sys
sys.exit(1)
def main():
root = tkinter.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
The reason you're still getting the RuntimeError even with the changes, is because you're is still trying to update the GUI from a thread — the one running the ping_func() method — that is not the same one running the tkinter GUI (which is a required because it doesn't support multithreading).
To fix that I have split ping_func() up into two separate parts, one the runs the ping command in another process and appends the results to the queue, and another that updates the GUI — the latter now being done in new method I added named process_incoming() (similar to the example to which I referred you).
Also note that the Demo2 class is no longer a subclass of Demo1 since there's no reason to do so (and it might confuse matters). I also changed the self.listBox attribute to self.treeview because that's what it is.
Although those changes alone would avoid the RuntimeError, in theory the GUI could still "freeze" until all the tasks completed because the pool.map() function blocks until all the tasks have completed, which could interfere with tkinter's mainloop() depending on how long that takes. To avoid that I changed pool.map() to pool.async() — which doesn't block because it's unnecessary given the Queue's contents are repeatedly polled.
import multiprocessing.dummy
import multiprocessing
import os
import socket
import sys
import subprocess
import re
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
class Demo1:
data = []
def __init__(self, master):
self.master = master
self.label=tkinter.Label(text="Add IP/Hostname")
self.label.pack()
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.pack()
self.button = tkinter.Button(self.master,height=3,width=10, text="OK",
command=self.new_window)
self.button.pack()
def new_window(self):
self.inputValue = self.t.get("1.0",'end-1c')
Demo1.data = self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.configure(bg='#6EBFE4')
self.master.mainloop()
class Demo2:
t1 = []
s1 = True
display = []
def __init__(self, master):
self.master = master
self.kas(master)
def kas(self,master):
self.running = True
self.queue = queue.Queue()
Demo2.t1 = Demo1.data
self.master = master
cols = ('IP','Ping status')
self.treeview = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.treeview.heading(col, text=col)
self.treeview.column(col,minwidth=0,width=170)
self.treeview.column('#0',width=50)
self.treeview.grid(row=1, column=0, columnspan=2)
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map_async(self.ping_func, [x for x in Demo2.t1 if x])
self.periodic_call() # Start polling the queue for results.
def ping_func(self, ip):
pi = []
pingCmd = "ping -n 1 -w 1000 " + ip
with os.popen(pingCmd) as childStdout:
result = childStdout.readlines()
pi.append(ip)
if(any('Reply from' in i for i in result)
and any('Destination host unreachable' not in i for i in result)):
pi.append("success")
else:
pi.append("failed")
self.queue.put(pi) #Thread value to queue
def process_incoming(self):
""" Process any messages currently in the queue. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
print(msg)
self.treeview.insert('', 'end', value=(msg)) # Update GUI.
except queue.Empty: # Shouldn't happen.
pass
def periodic_call(self):
self.master.after(200, self.periodic_call) # checking its contents periodically
self.process_incoming()
if not self.running:
import sys
sys.exit(1)
def main():
root = tkinter.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()

python tkinter- more than one windows to show and do different processes

I have a GUI application. It has more than one windows. Main windows is updating every second with new values. I am using threading and queue here. There is a button in the main window. Pressing it will open a new window. What I want is, when new window is shown, stop the process in the main window (updating the values process) and some new process will be shown in the new window. Closing the new window should revoke the main window again and continue the process. Do i need to use multi threading or multi processing?
Unfortunately, I am asking this question for the second time. sorry for that.
import tkinter
import time
import threading
import random
import queue
class GuiPart:
def __init__(self, master, queue, endCommand,newWindow):
self.queue = queue
self.pause = False
# Set up the GUI
console = tkinter.Button(master, text='Done', command=endCommand)
console.pack()
console2 = tkinter.Button(master, text='New', command=newWindow)
console2.pack()
self.output = tkinter.StringVar()
#output.set(1)
output_1_label = tkinter.Label(master, textvariable= self.output, height=2, width=12)
output_1_label.pack()
# Add more GUI stuff here
self.temp_process()
def temp_process(self):
if not self.pause:
print ("handling messages")
else:
print ("Not handling messages")
root.after(1000,self.temp_process)
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
print (msg)
self.output.set(msg)
except queue.Empty:
pass
class ThreadedClient:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.endApplication,self.create_window)
self.running = 1
self.thread1 = threading.Thread(target=self.workerThread1) #this is for sending data to queue.
# what about second window?
self.thread1.start()
self.periodicCall()
def on_quit(self):
self.gui.pause = False
self.window.destroy()
def create_window(self):
self.window = tkinter.Toplevel(root)
self.gui.pause = True
self.window.protocol("WM_DELETE_WINDOW",self.on_quit)
def periodicCall(self):
self.gui.processIncoming()
if not self.running:
import sys
sys.exit(1)
self.master.after(1000, self.periodicCall)
def workerThread1(self):
while self.running:
time.sleep(rand.random() * 1)
msg = rand.random()
self.queue.put(msg)
def endApplication(self):
self.running = 0
rand = random.Random()
root = tkinter.Tk()
client = ThreadedClient(root)
root.mainloop()
You can have your function processIncoming check for a flag, and pause/resume when required.
class GuiPart:
def __init__(self, master, queue, endCommand,newWindow):
self.queue = queue
self.pause = False
....
def processIncoming(self):
while self.queue.qsize() and not self.pause:
try:
msg = self.queue.get(0)
print (msg)
self.output.set(msg)
except queue.Empty:
pass

Starting and stopping a thread from a gui

I'm currently developing a program with a GUI to gather data from a sensor and visualise it within the GUI.
The data from the sensor is stored in a list for further calculations.
What I'm currently trying to do, is starting the logging process in a new thread.
This should stop the GUI from freezing.
My current code:
import tkinter as tk
import time
import threading
class GUI:
def __init__(self, tk_object):
self.gui = tk_object
self.gui.title("Logger")
self.gui.resizable(width=True, height=True)
self.testlabel = tk.Label(self.gui, text="Recording in process")
self.testlabel.grid(row = 7, column = 0)
self.testlabel.config(bg='white')
btn1 = tk.Button(master, text="Start Recording", width=16, height=5, command=lambda: self.start_functions())
btn1.grid(row=2,column=0)
btn2 = tk.Button(master, text="Stop Recording", width=16, height=5, command=lambda: self.stop_functions())
btn2.grid(row=3,column=0)
def start_functions(self):
"""Calls the method get_sample in a new thread and changes the bg-color of the label to red"""
Thread_1 = threading.Thread(target=self.get_sample(), name='Thread1')
Thread_1.start()
self.testlabel.config(bg='red')
def stop_functions(self):
"""Stops all active threads and changes bg-color of the label back to white"""
#stop_threads = threading.Event()
#stop_threads.set()
threading.Event().set()
self.testlabel.config(bg='white')
def get_sample(self):
"""get data and append it to a list"""
while not threading.Event().is_set():
time.sleep(1)
res_cel.append(res_cel[-1]+1)
x_value.append(x_value[-1]+1)
print(res_cel)
print(x_value)
master = tk.Tk()
master_object = GUI(master)
master.mainloop()
Currently, the get_sample method contains a placeholder.
I'm trying to stop the Thread1 (where the get_sample method is running in) via the Event() handler.
while not threading.Event().is_set():
This doesn't seem to work in a proper way.
Are there better ways to do this?
Before this approach I tried using a class to handle the threads (This was found on stackoverflow, but I can't seem to find it anymore, sorry):
class Controller(object):
def __init__(self):
self.thread1 = None
self.stop_threads = Event()
def run(self):
.. do somehting
and starting / stopping the threads via:
def starting_thread(self):
self.stop_threads.clear()
self.thread1 = Thread(target = self.run)
self.thread1.start()
def stopping_thread(self):
self.stop_threads.set()
self.thread1 = None
Note: These functions are within the Controller Class.
With this solution I wasn't able to alter the lables background color in the GUI Class, since it doesn't know what object it is referencing to.
Im fairly new to programming python so I'd be glad if someone could explain to me what I'm missing here.
Thanks
xSaturn
You'd better create your own Thread object with a method to stop it. A thread stop running when it goes of its run method. Look at the example bellow
import time
import tkinter as tk
from threading import Thread
class MyThread(Thread):
thread_running = False
def run(self):
k = 0
self.thread_running = True
while self.thread_running:
print(k)
k +=1
time.sleep(1)
print("thread ended")
def stop(self):
self.thread_running = False
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.thread = None
tk.Button(self, text="launch thread", command=self.launch_thread)\
.grid(row=1, column=0)
tk.Button(self, text="stop thread", command=self.stop_thread)\
.grid(row=2, column=0)
def launch_thread(self):
if self.thread:
print("thread already launched")
else:
print("thread launched")
self.thread = MyThread()
self.thread.start()
def stop_thread(self):
if self.thread:
self.thread.stop()
self.thread = None
else:
print("no thread running")
if __name__ == '__main__':
win = Window()
win.mainloop()
See this topic for more information
Is there any way to kill a Thread?

Create a tkinter window every x minutes and then automatically close it after y seconds

I'm trying to build a simple program to remind me to take breaks while using the computer. I have a reasonable understanding of python but have never played with GUI programming or threading before, so the following is essentially copying/pasting from stackoverflow:
import threading
import time
import Tkinter
class RepeatEvery(threading.Thread):
def __init__(self, interval, func, *args, **kwargs):
threading.Thread.__init__(self)
self.interval = interval # seconds between calls
self.func = func # function to call
self.args = args # optional positional argument(s) for call
self.kwargs = kwargs # optional keyword argument(s) for call
self.runable = True
def run(self):
while self.runable:
self.func(*self.args, **self.kwargs)
time.sleep(self.interval)
def stop(self):
self.runable = False
def microbreak():
root = Tkinter.Tk()
Tkinter.Frame(root, width=250, height=100).pack()
Tkinter.Label(root, text='Hello').place(x=10, y=10)
threading.Timer(3.0, root.destroy).start()
root.mainloop()
return()
thread = RepeatEvery(6, microbreak)
thread.start()
This gives me the first break notification but fails before giving me a second break notification.
Tcl_AsyncDelete: async handler deleted by the wrong thread
fish: Job 1, “python Documents/python/timer/timer.py ” terminated by
signal SIGABRT (Abort)
Any ideas? I'm happy to use something other than tkinter for gui-stuff or something other than threading to implement the time stuff.
Based on the answers below, my new working script is as follows:
import Tkinter as Tk
import time
class Window:
def __init__(self):
self.root = None
self.hide = 10 #minutes
self.show = 10 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#Tk.Button(text = 'click to end', command = self.close).pack()
self.root.after(self.show*1000, self.loop)
def loop(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().loop()
I think it would be easier for you to achieve this without threads, which Tkinter does not integrate with very well. Instead, you can use the after and after_idle methods to schedule callbacks to run after a certain timeout. You can create one method that shows the window and schedules it to be hidden, and another that hides the window and schedules it to be shown. Then they'll just call each other in an infinite loop:
import tkinter
class Reminder(object):
def __init__(self, show_interval=3, hide_interval=6):
self.hide_int = hide_interval # In seconds
self.show_int = show_interval # In seconds
self.root = Tkinter.Tk()
tkinter.Frame(self.root, width=250, height=100).pack()
tkinter.Label(self.root, text='Hello').place(x=10, y=10)
self.root.after_idle(self.show) # Schedules self.show() to be called when the mainloop starts
def hide(self):
self.root.withdraw() # Hide the window
self.root.after(1000 * self.hide_int, self.show) # Schedule self.show() in hide_int seconds
def show(self):
self.root.deiconify() # Show the window
self.root.after(1000 * self.show_int, self.hide) # Schedule self.hide in show_int seconds
def start(self):
self.root.mainloop()
if __name__ == "__main__":
r = Reminder()
r.start()
I agree with dano. I thought i'd also contribute though, as my way is somewhat smaller than dano's, but uses time for the gap between when the window is visible. hope this helps #vorpal!
import Tkinter
import time
root = Tkinter.Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'click to shutdown', command = close)
def hide():
root.withdraw()
time.sleep(10)
show()
button = Tkinter.Button(text = 'click for hide for 10 secs', command = hide)
button.pack()
root.mainloop()

Categories