Tkinter/Python and Arduino - python

I have a simple project that takes the serial reading from my potentiometer and Arduino. After I get the reading that is converted and is supposed to change the color of a blank window.
For some reason I can't get the window to update and I am REALLY unfamiliar with Tkinter. Thanks for any input.
import Tkinter
import serial
ser = serial.Serial("/dev/ttyACM0", 9600, timeout = 2)
def update():
while 1:
line = ser.readline()
color = "#%02x%02x%02x" %(000, 000, int(line))
return color
root = Tkinter.Tk()
root.geometry("500x500")
root.configure(background = update())
root.mainloop()

I recently did just this to monitor input from a serial device. You need to be able to receive serial data when it is ready and also keep the Tk event-loop running. You can either use a worker thread to read the serial data and post the inputs to the UI thread for display or use the Twisted framework instead. In my case I used twisted to avoid any blocking reads from the serial port and plot the values only a simple line graph using a canvas line.
#!/usr/bin/python
import sys
from Tkinter import *
from ttk import *
from twisted.internet import tksupport, reactor
from twisted.internet.serialport import SerialPort
from twisted.protocols import basic
class DeviceProtocol(basic.LineReceiver):
#delimiter = "\r\n" # default delimiter is crlf
def __init__(self, callback):
self.callback = callback
def lineReceived(self, data):
self.callback.OnDataReceived(data)
class MainWindow(Frame):
def __init__(self, parent, title):
self.data = [(n,1) for n in range(100)]
self.sample = len(self.data)
Frame.__init__(self, parent)
parent.wm_withdraw()
parent.wm_title(title)
self.CreateUI()
self.grid(sticky = (N,S,W,E))
parent.wm_protocol("WM_DELETE_WINDOW", self.onDestroy)
parent.grid_rowconfigure(0, weight = 1)
parent.grid_columnconfigure(0, weight = 1)
parent.wm_deiconify()
def CreateUI(self):
canvas = Canvas(self, background="white")
canvas.create_line((0, 0, 1, 1), tag='Plot', fill='red', smooth=True)
canvas.grid(sticky = (N,S,W,E))
self.canvas = canvas
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def onDestroy(self):
reactor.stop() # required to cleanly shutdown twisted event loop
def Monitor(self, port, baudrate = 9600):
self.serial = SerialPort(DeviceProtocol(self),
port, reactor, baudrate=baudrate)
def OnDataReceived(self, data):
print data
for v in data.split():
self.data.append((self.sample, int(v)))
self.sample += 1
if len(self.data) > 100:
self.data = self.data[-100:]
self.after_idle(self.OnUpdatePlot)
def OnUpdatePlot(self):
width = self.canvas.winfo_width()
height = self.canvas.winfo_height()
xS,yS = self.data[0]
xE,yE = self.data[-1]
coords = []
fx = width / (xE - xS)
fy = height / 100 # y values are 0 < y < 100
for (x,y) in self.data:
coords.append(fx * (x - xS))
coords.append(fy * (100 - y))
self.canvas.coords('Plot', *coords)
def main(argv = None):
if argv is None:
argv = sys.argv
if len(argv) != 2:
print "usage: plot_serial <serialport>"
return 1
root = Tk()
tksupport.install(root) # register the Tk event loop with twisted
main = MainWindow(root, "Plot serial data")
main.Monitor(argv[1])
reactor.run() # use twisted instead of root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main())

Related

after() hangs the output window

I am trying to build a real time project where the status gets updated every second, so some part of code repeats continuously. when i want to change the information which has to be get updated i will just click on new button which gives me the first window where i can update the new information. but by doing so gives me the following error. if i use after() instead of threading, the error wont be there but the output window gets hanged.Please help me with the idea to resolve this. Thank you.
Exception in thread Thread-2:
Traceback (most recent call last):
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 1177, in run
self.function(*self.args, **self.kwargs)
File "C:/Users/Desktop/Tool/t.py", line 47, in ae
self.treeview.insert('', 'end',image=self._img, value=(a))
File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\tkinter\ttk.py", line 1370, in insert
res = self.tk.call(self._w, "insert", parent, index, *opts)
_tkinter.TclError: invalid command name ".!treeview"
Code where i have problem with:
def aaa(self):
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in Demo2.t1])
self.process_incoming()
#threading.Timer(1.0, self.aaa).start()-this gives the error while pressing new button and updating information
self.master.after(100, self.aaa) #it hangs the output window
Sample code:
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
from tkinter import *
class Demo1: #window 1
data=[]
def __init__(self, master):
self.master = master
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.grid(row=1, column=1)
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.grid(row=2,column=1)
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.mainloop()
class Demo2: #window 2
value = []
display = []
num=0
def __init__(self, master):
self.master = master
self.queue = queue.Queue()
Demo2.value = Demo1.data
self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
cols = ('aa','bb')
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.grid(row=1, column=0)
self._img=tkinter.PhotoImage(file="green1.gif")
self.aaa()
def aaa(self):
num_threads = 5 * multiprocessing.cpu_count()
p = multiprocessing.dummy.Pool(num_threads)
p.map(self.ping_func, [x for x in Demo2.value])
self.process_incoming()
#threading.Timer(1.0, self.aaa).start()
self.master.after(100, self.aaa)
def ping_func(self,ip): #Ping every ip and append the result
ping_result = []
pingCmd = "ping -n 1 -w 1000 " + ip
childStdout = os.popen(pingCmd)
result = (childStdout.readlines())
childStdout.close()
ping_result.append(ip)
if(any('Reply from' in i for i in result)):
ping_result.append("success")
else:
ping_result.append("failed")
self.queue.put(ping_result) #Thread value to queue
def process_incoming(self): #add the ping result to treeview
while self.queue.qsize():
try:
if Demo2.num<len(Demo1.data):
self._img=tkinter.PhotoImage(file="green1.gif")
self._img1=tkinter.PhotoImage(file="red.gif")
msg = self.queue.get_nowait()
Demo2.display.append(msg) #adding queue value to variable(display)
if(len(Demo2.display)==len(Demo1.data)):
self.treeview.insert("","end",values=(0,0,0,0,0))
self.treeview.delete(*self.treeview.get_children())
for i,(a,b) in enumerate(Demo2.display):
if(Demo2.display[i][1]=='success' ):
self.treeview.insert('', 'end',image=self._img, value=(a,b))
else:
self.treeview.insert('', 'end',image=self._img1, value=(a,b))
Demo2.num=Demo2.num+1
Demo2.display.clear()
else:
Demo2.display.clear()
Demo2.num=0
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 new(self):
self.master.destroy() # close the current window
self.master = tkinter.Tk() # create another Tk instance
self.app = Demo1(self.master) # create Demo2 window
self.master.mainloop()
def main():
root = tkinter.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Main problem is that ping -w 1000 need a lot of time to run but Pool.map() waits for all results. You could even run results = p.map(...) without queue (but with return result) but it could also block tkinter
You may use map_async() to run it without waiting for results.
I also use starmap (or rather starmap_async()) to send two arguments ip, queue to every process.
I also made other changes - ie. rename variables, move some code to __init__ to create only once (images, Pool, Queue). I also send list of IP to other window as argument Window2(master, data) and back Window1(master, data) - so I can edit this list.
BTW: because I run on Linux so I changed arguments for ping and check different text to test if it get answer.
import os
import multiprocessing.dummy
import queue
import tkinter as tk
import tkinter.ttk as ttk
# --- classes ---
class Window1:
def __init__(self, master, data=None):
self.master = master
self.data = data
if self.data is None:
self.data = []
self.text = tk.Text(self.master, height=20, width=50)
self.text.grid(row=1, column=1)
self.button = tk.Button(self.master, height=3, width=10, text="OK", command=self.new_window)
self.button.grid(row=2, column=1)
# put self.data in Text
#for item in self.data:
# self.text.insert('end', item + '\n')
self.text.insert('end', '\n'.join(self.data))
def new_window(self):
text = self.text.get('1.0', 'end')
# remove empty lines
self.data = [item.strip() for item in text.split("\n") if item.strip()]
self.master.destroy()
root = tk.Tk()
Window2(root, self.data)
root.mainloop()
class Window2:
def __init__(self, master, data):
self.master = master
# keep list
self.data = data
# create dictionary for results
self.results = {ip: [ip, '???'] for ip in self.data}
self.button = tk.Button(self.master, height=2, width=11, text='New', command=self.new)
self.button.grid(row=0, column=0, sticky='w')
self.label = tk.Label(self.master, text='monitor', font=("Arial", 20))
self.label.grid(row=0, column=1)
cols = ('IP','Result')
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.grid(row=1, column=0, columnspan=3)
# create only once
self._image_green = None # tk.PhotoImage(file="green1.gif")
self._image_red = None # tk.PhotoImage(file="red.gif")
# to reduce number of processes for small `data`
n = min(5, len(self.data))
# create only once
self.queue = queue.Queue()
self.num_threads = n * multiprocessing.cpu_count()
self.p = multiprocessing.dummy.Pool(self.num_threads)
# to stop `after()`
self.running = True
# run first time
self.update_treeview() # to display it before running processes
self.run_processes()
self.processes_incoming() # first create window to display it faster
def run_processes(self):
if self.running:
self.p.starmap_async(self.ping, [(ip, self.queue) for ip in self.data])
self.after_ID2 = self.master.after(500, self.run_processes)
def ping(self, ip, queue):
#print('start ping:', ip)
#cmd = 'ping -n 1 -w 3 ' + ip
cmd = 'ping -w 1 ' + ip # Linux
child_stdout = os.popen(cmd)
result = child_stdout.readlines()
child_stdout.close()
#print('end ping:', ip)
#if any('Reply from' in line for line in result):
if any('bytes from' in line for line in result): # Linux
value = [ip, 'success']
else:
value = [ip, 'failed']
queue.put(value)
def update_treeview(self):
self.treeview.delete(*self.treeview.get_children())
for ip in self.data:
ip, value = self.results[ip]
if value == 'success':
image = self._image_green
elif value == 'failed':
image = self._image_red
else:
image = None
#self.treeview.insert('', 'end', image=image, value=(ip, valueb))
self.treeview.insert('', 'end', value=(ip, value))
def processes_incoming(self):
if self.running:
# get all from queue
new_values = False
while self.queue.qsize():
#while not self.queue.empty:
data = self.queue.get_nowait()
ip, value = data
self.results[ip] = data
new_values = True
# update only if new values
if new_values:
self.update_treeview()
# repeate after 100ms
self.after_ID1 = self.master.after(100, self.processes_incoming)
def new(self):
# to stop all `after()`
self.running = False
self.master.after_cancel(self.after_ID1)
self.master.after_cancel(self.after_ID2)
self.master.destroy()
root = tk.Tk()
Window1(root, self.data)
root.mainloop()
# --- functions ---
def main():
examples = [
'127.0.0.1', # localhost
'10.0.0.1', # IP in local network
'192.168.0.1', # IP in local network
'8.8.8.8', # Google DNS
'8.8.4.4', # Google DNS
]
root = tk.Tk()
Window1(root, examples)
root.mainloop()
# --- main ---
if __name__ == '__main__':
main()

Using keyword or wake word to activate Tkinter window in python

I found this amazing wake-word program called Porcupine. I got it to work and want to implement it into a Tkinter window. The idea is to have a state of on/off with the wake word turning it on. I have working code but the problem is that no matter where I put the listener in the code it will not make the window until I use the wake word.
I want the following behavior: I would like the window to be created and to appear and then to run the wake-word module so I can change the self.state variable. This wake word will then put me in a loop that will create other frames and features. I am assuming there is a threading solution but I could not figure it out.
from Tkinter import *
import threading
import ttk
import os
import sys
sys.path.append('Porcupine/demo/python')
import porcupine__demo
class FullscreenWindow:
def __init__(self):
self.state = False
self.tk = Tk()
self.tk.configure(background='black')
self.listen()
print(self.state)
def listen(self):
self.state = porcupine_demo.listen_for_keyword()
if __name__ == '__main__':
w = FullscreenWindow()
w.tk.mainloop()
The method *.listen_for_keyword() is a method I wrote that returns True when the wake word is captured.
I found an amazing blog post that answers this question. I have changed their code to reflect my needs. The blog is here:
from Tkinter import *
import threading
import sys
sys.path.append('Porcupine/demo/python')
import porcupine_demo
class App(threading.Thread):
def __init__(self, tk_root):
self.root = tk_root
threading.Thread.__init__(self)
self.start()
def run(self):
listening = True
while listening:
self.state = porcupine_demo.listen_for_keyword()
if self.state:
print("heard you:",self.state)
LABEL = Label(self.root, text="Hello, world!")
LABEL.pack()
ROOT = Tk()
ROOT.configure(background='black')
APP = App(ROOT)
ROOT.mainloop()
The porcupine package comes with a demo of how to use porcupine in a non-blocking mode, which is necessary to allow mainloop to process tkinter events. The key is to attach a callback to the audio stream.
I've never used porcupine before so I don't know how well this works in the real world, but it works for me on my mac, and doesn't require explicit threading.
import sys
sys.path.append("/tmp/Porcupine/binding/python")
import Tkinter as tk
import struct
from datetime import datetime
from porcupine import Porcupine
import pyaudio
library_path = "/tmp/Porcupine/lib/mac/x86_64/libpv_porcupine.dylib"
model_file_path = "/tmp/Porcupine/lib/common/porcupine_params.pv"
keyword_file_paths = ["/tmp/Porcupine/words_mac.ppn",]
num_keywords = len(keyword_file_paths)
sensitivities = [0.5] * num_keywords
class TkPorcupine(object):
def __init__(self):
self.initialize_ui()
self.initialize_porcupine()
def initialize_ui(self):
self.state = False
self.root = tk.Tk()
self.text = tk.Text(self.root, width=60, height=8)
self.vsb = tk.Scrollbar(self.root, orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.text.pack(fill="both", expand=True)
def initialize_porcupine(self):
self.porcupine = Porcupine(library_path, model_file_path,
keyword_file_paths=keyword_file_paths,
sensitivities=sensitivities)
self.audio_stream = pyaudio.PyAudio().open(
rate=self.porcupine.sample_rate,
channels=1,
format=pyaudio.paInt16,
input=True,
frames_per_buffer=self.porcupine.frame_length,
input_device_index=None,
stream_callback=self.audio_callback)
self.audio_stream.start_stream()
def audio_callback(self, in_data, frame_count, time_info, status):
if frame_count >= self.porcupine.frame_length:
pcm = struct.unpack_from("h" * self.porcupine.frame_length, in_data)
result = self.porcupine.process(pcm)
if num_keywords == 1 and result:
message = '[%s] detected keyword' % str(datetime.now())
self.text.insert("end", message + "\n")
self.text.see("end")
elif num_keywords > 1 and result >= 0:
message = '[%s] detected keyword #%d' % (str(datetime.now()), result)
self.text.insert("end", message + "\n")
self.text.see("end")
return None, pyaudio.paContinue
if __name__ == '__main__':
demo = TkPorcupine()
tk.mainloop()

Display log lines on screen using parallel GUI

I'm looking for elegant way to display log lines on screen while the script is running.
from time import sleep
from threading import Thread
import tkinter as tk
class WaitGuiPrallel(Thread):
def __init__(self, TXT='Wait!', ttl='Logs'):
self.txt = TXT
Thread.__init__(self)
self.ttl = ttl
self.start() # This is starting the self.run()
def run(self):
self.root = tk.Tk()
self.root.attributes("-topmost", True)
self.root.title(self.ttl)
self.label = tk.Label(self.root, text=self.txt, font=("Helvetica", 20))
self.label.pack()
self.Location()
self.root.mainloop()
def Exit(self):
self.root.quit()
def Location(self):
w = 500 # width for the Tk root
h = 150 # height for the Tk root
ws = self.root.winfo_screenwidth() # width of the screen
self.root.geometry('%dx%d+%d+%d' % (w, h, ws - w - 20, 10))
def Update(self, newText):
self.txt1 = newText
self.label.destroy()
self.label = tk.Label(self.root, text=self.txt1,
font=("Helvetica", 12))
self.label.pack()
self.root.update()
Wait = WaitGuiPrallel(TXT='Wait! Do not touch mouse or keyboard')
sleep(2)
for t in range(5):
sleep(1)
Wait.Update(newText='Log line %s' % t)
Wait.Update(newText='Done!')
sleep(1)
Wait.Exit()
The current script got few issues:
it is not elegant - there must be a better way
it has problems when updated from different Threads
Sometime running it twice from Spyder+IPython is not possible (IPython freeze)
Tkinter doesn't really play well with threads. Using a StringVar is a little more thread friendly than other methods (in my experience). Here's how to do that plus a couple other fixes:
from time import sleep
from threading import Thread
import tkinter as tk
class WaitGuiPrallel(Thread):
def __init__(self, TXT='Wait!', ttl='Logs'):
Thread.__init__(self)
self.txt = TXT
self.ttl = ttl # what's this for?
self.daemon = True # this thread will terminate when the main thread terminates
self.start() # This is starting the self.run()
def run(self):
self.root = tk.Tk()
self.root.attributes("-topmost", True)
self.root.title(self.ttl)
self.txt = tk.StringVar(value=self.txt)
self.label = tk.Label(self.root, textvariable=self.txt, font=("Helvetica", 20))
self.label.pack()
self.Location()
self.root.mainloop()
def Location(self):
w = 500 # width for the Tk root
h = 150 # height for the Tk root
ws = self.root.winfo_screenwidth() # width of the screen
self.root.geometry('%dx%d+%d+%d' % (w, h, ws - w - 20, 10))
Wait = WaitGuiPrallel(TXT='Wait! Do not touch mouse or keyboard')
sleep(2)
for t in range(5):
sleep(1)
Wait.txt.set('Log line %s' % t)
Wait.txt.set('Done!')
sleep(1)
If you have multiple threads calling this then I would consider using a Queue and a third thread to monitor the queue.

Python GUI Threading

I want to have a function run continuously within a tkinter GUI. I have attached some shell code:
#!/usr/bin/env python3
import tkinter as tk
from time import sleep
import os
import sys
class Application(Frame):
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
......
root = Tk()
def run_continously:
... -- calls sleep -- ...
root.after(3000, run_continuously)
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
app = Application(root)
root.after(3000, run_continuously)
root.mainloop()
When running the GUI it tends to run the 'run_continuously' function once and the GUI freezes up. I suspect from poking around that this is due to the sleep function (which I call in the run_continuously function)
How would I go about implementing the 'run_continuously' function in a very simple thread to get around this issue? Would running the function in a thread even get me around the problem? The 'run_continuously' function does not need to interact at all with the Application class. I want it to run simply in the background and stop when the mainloop is finished.
Here is the end of the code:
def run_continuously(quit_flag):
print("in it")
if not quit_flag:
GPIO.output(DIR_PIN, True)
for i in range(steps):
print("in loop")
GPIO.output(STEP_PIN, True)
sleep(sDelay)
GPIO.output(STEP_PIN, False)
sleep(sDelay)
sleep(wait_time)
GPIO.output(DIR_PIN, False)
for i in range(steps):
GPIO.output(STEP_PIN, True)
sleep(sDelay)
GPIO.output(STEP_PIN, False)
sleep(sDelay)
print("run motor")
root.after(1000, run_continuously(quit_flag,))
#=================================================================
# main
#=================================================================
root = Tk() # Create the GUI root object
press1 = StringVar()
press2 = StringVar()
x = 275
y = 50
w = 580
h = 250
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
app = Application(root) # Create the root application window
quit_flag = False
root.after(0, app.read_pressure)
motor_thread = threading.Thread(target=run_continuously, args=(quit_flag,)).start()
root.mainloop()
quit_flag=True
motor_thread.join()
This is a minimal, complete, and verifiable example that exits cleanly if the 'QUIT' button is pressed or Ctrl-C is pressed:
from Tkinter import *
import multiprocessing
import threading
import time
import logging
class Application(Frame):
def create_widgets(self):
self.quit_button = Button(self)
self.quit_button['text'] = 'QUIT'
self.quit_button['fg'] = 'red'
self.quit_button['command'] = self.quit
self.quit_button.pack({'side': 'left'})
def __init__(self, master=None):
Frame.__init__(self, master)
self.quit_button = None
self.pack()
self.create_widgets()
self.poll()
def poll(self):
"""
This method is required to allow the mainloop to receive keyboard
interrupts when the frame does not have the focus
"""
self.master.after(250, self.poll)
def worker_function(quit_flag):
counter = 0
while not quit_flag.value:
counter += 1
logging.info("Work # %d" % counter)
time.sleep(1.0)
format = '%(levelname)s: %(filename)s: %(lineno)d: %(message)s'
logging.basicConfig(level=logging.DEBUG, format=format)
root = Tk()
app = Application(master=root)
quit_flag = multiprocessing.Value('i', int(False))
worker_thread = threading.Thread(target=worker_function, args=(quit_flag,))
worker_thread.start()
logging.info("quit_flag.value = %s" % bool(quit_flag.value))
try:
app.mainloop()
except KeyboardInterrupt:
logging.info("Keyboard interrupt")
quit_flag.value = True
logging.info("quit_flag.value = %s" % bool(quit_flag.value))
worker_thread.join()

configure tkinter object from separate class

I have a simple program with two classes, one controls a relay board via a serial.serial connection. The other class is for a GUI which will send commands to the relay class and then display the status of the relay board.
I'm having an issue with sending messages from the relay class to the tkinter class. The messages only appear once the relay command has finished. I've cut down my program below. Test.test() represents a function in my relay class where as the MainWindow class is my GUI.
Someone has pointed out to use threading to handle the messages being passed between the classes. Is that my only option? I have not delved into threading yet.
from Tkinter import *
import time
import ScrolledText
class Test():
def test(self):
main.textboxupdate(" test start ")
time.sleep(2)
main.textboxupdate(" test middle ")
time.sleep(2)
main.textboxupdate(" test end ")
class MainWindow(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(width=1200,height=700)
self.canvas.pack(expand=YES,fill=BOTH)
self.frame = Frame(self.canvas)
self.TextBox = ScrolledText.ScrolledText(self.frame)
self.open = Button(self.frame, text="Open Cover",
command=test.test)
def createtextbox(self, statusmsg):
self.frame.place(x=0,y=0)
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 2, width = 35)
self.TextBox.see(END)
self.TextBox.grid(columnspan=2, rowspan = 1)
self.open.grid()
def textboxupdate(self, statusmsg):
statusmsg = statusmsg +'\n'
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 10, width = 50)
self.TextBox.see(END)
test = Test()
root = Tk()
main = MainWindow(root)
main.createtextbox('Startup\n')
root.mainloop()
Here's one option:
from Tkinter import *
import time
import ScrolledText
import threading, Queue
class Test():
def __init__(self):
self.msg_queue = Queue.Queue()
def test(self):
self.msg_queue.put(" test start ")
time.sleep(2)
self.msg_queue.put(" test middle ")
time.sleep(2)
self.msg_queue.put(" test end ")
class MainWindow(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
self.canvas = Canvas(width=1200,height=700)
self.canvas.pack(expand=YES,fill=BOTH)
self.frame = Frame(self.canvas)
self.TextBox = ScrolledText.ScrolledText(self.frame)
self.open = Button(self.frame, text="Open Cover",
command=self.create_thread)
self.test_thread = None
self.createtextbox("")
def create_thread(self):
self.test_thread = threading.Thread(target=test.test)
self.test_thread.start()
self.after(10, self.update_textbox)
def update_textbox(self):
while not test.msg_queue.empty():
self.textboxupdate(test.msg_queue.get())
if self.test_thread.is_alive():
self.after(10, self.update_textbox)
else:
self.test_thread = None
def createtextbox(self, statusmsg):
self.frame.place(x=0,y=0)
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 2, width = 35)
self.TextBox.see(END)
self.TextBox.grid(columnspan=2, rowspan = 1)
self.open.grid()
def textboxupdate(self, statusmsg):
statusmsg = statusmsg +'\n'
self.TextBox.config(state = NORMAL)
self.TextBox.insert(END, statusmsg,('error'))
self.TextBox.config(state = 'disabled', height = 10, width = 50)
self.TextBox.see(END)
self.update_idletasks()
test = Test()
main = MainWindow()
main.pack()
main.mainloop()
The first change is that instead of calling a function, Test.test puts the messages onto a queue. Test.test is started in a separate thread by MainWindow.start_thread. MainWindow.start_thread also schedules a check on the thread by asking tkinter to call update_textbox after 10 milliseconds (self.after(10, self.update_textbox)). This function takes all the new messages off the queue, and displays them. Then, if the thread is still running, it reschedules itself, otherwise it resets the MainWindow.

Categories

Resources