Data display GUI with multi threading Python - python

I'm basically simulating acquiring data from the serial port and plotting it on a graph displayed in a GUI which is made using Tkinter. The incoming serial data is simulated by a simple while loop which calculates a sine function and adds the value to a queue which is size 100, the data generating part of the program is written under the class named DataThread
import Tkinter as tk
import numpy as np
import matplotlib as plt
from collections import deque
import threading
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from time import sleep
class DataThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.ISCONNECTED = 1
self.d = deque(maxlen=100)
def run(self):
t = 1
while True:
wave = np.sin(2*np.pi*100*t*0.001)
self.d.append(wave)
print(wave)
t = t+1
sleep(1)
if self.ISCONNECTED == 0:
break
The other class generates the GUI , it creates an instance of the DataThread class and calling the function start_d should start the thread which generates the data. I would like to stop the thread by pressing the stop button but I'm not sure how to stop the thread.
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
self.fig = Figure()
self.ax_1 = self.fig.add_subplot(111)
self.createWidgets()
self.a = DataThread()
def createWidgets(self):
self.frame = tk.Frame(borderwidth=5,
relief="sunken", width=300, height=20)
self.frame.grid(column=0, row=0, columnspan=10, rowspan=4,
sticky=(tk.N, tk.S, tk.E, tk.W))
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame)
self.canvas.get_tk_widget().grid()
self.canvas.show()
self.namelbl = tk.Label(text="Start DAQ")
self.namelbl.grid(column=10, row=0, columnspan=2,
sticky=(tk.S, tk.W), padx=5)
self.start = tk.Button( text='Start',
command=self.quit)
self.start.grid(column=10, row=1,sticky=tk.N+tk.S+tk.E+tk.W)
self.stop = tk.Button( text='Stop',
command=self.stop_serial)
self.stop.grid(column=11, row=1,sticky=tk.N+tk.S+tk.E+tk.W)
def start_d(self):
self.a.ISCONNECTED=1
self.a.start()
def readSensor(self):
data2plot = self.a.d
self.ax_1.plot(range(data2plot),data2plot)
self.root.update()
self.root.after(527, self.readSensor)
def stop_serial(self):
self.a.ISCONNECTED=0
def run(self):
self.mainloop()
And the last part which simply runs the GUI
if __name__ == "__main__":
Application().run()
I based my code of the following question: Dynamically updating Tkinter window based on serial data
The difference being that both, the GUI thread and the data thread, are created simultaneously, but in my case it wouldn't work because I want to start the thread only when the start button is pressed.

Related

How to correctly implement multithreading using a Tkinter GUI?

I am trying to implement Multithreading while using a GUI in tkinter. I came along this solution, but i am failing to implement it.
So basically i need to know:
How do i need to alter my Code to make the Progressbar interact nice and smoothly, i.e. not going in unresponsive mode when the GUI loses focus?
This is my code boiled down:
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
# Gui class
class MyGui(Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.queue = queue.Queue()
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.do_stuff)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
# start ThreadedTask here
def do_stuff(self):
ThreadedTask(self.queue).start()
self.queue_process(DoStuffClass(ui=self))
def queue_process(self, process, retry_time=10):
self.master.after(retry_time, process)
def update_status(self):
self.parent.update_idletasks()
class DoStuffClass:
def __init__(self, ui=None):
self.ui = ui
self.long_task()
def long_task(self):
# do stuff here and update the progressbar from MyGui
self.ui.progress['maximum'] = 10000
# some lengthy task ...
for i in range(10000):
print(i)
self.ui.progress.step()
self.ui.parent.update_idletasks()
# main
root = Tk()
root.geometry("150x80+50+50")
MyGui(root)
root.mainloop()
root.quit()
I think right now my problem is the wrong implementation of queues and Threading, i.e. self.queue_process(DoStuffClass(ui=self))... the behaviour is the same as if i wouldn't use a queue and Multithread at all. The progressbar works, as long as it stays "in focus", which means me not clicking anything else on the desktop. As soon as i'm clicking elsewhere on the desktop and the GUI loses focus, the GUI goes in "unresponsive" mode and the progress bar does not update anymore. Also, sometimes Tcl closes the wrong Thread, which makes the whole program crash.
So after a couple of tries i figured out what to do:
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(object):
def __init__(self, parent):
self.parent = parent
self.queue = queue.Queue()
self.gui = MyGui(parent, self.queue)
self.work_queue()
def work_queue(self):
""" Check every 100 ms if there is something new in the queue. """
try:
self.parent.after(200, self.work_queue)
print('working queue with task {}...'.format(self.queue.get_nowait()))
except queue.Empty:
pass
# Gui class
class MyGui(Frame):
def __init__(self, parent, queue):
super().__init__(parent)
self.parent = parent
self.queue = queue
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.init_button_loop)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
def start_thread(self, function_name, queue):
t = threading.Thread(target=function_name, args=(queue,))
# close thread automatically after finishing task
t.setDaemon(True)
t.start()
# execute button push by spawning a new thread
def init_button_loop(self):
self.start_thread(self.exec_button_loop, self.queue)
# execute long task
def exec_button_loop(self, queue):
self.progress['maximum'] = 10000
for i in range(10000):
# update progressbar
queue.put(self.progress.step())
# main
root = Tk()
root.geometry("150x80+50+50")
client = ThreadedTask(root)
root.mainloop()
The Difficulty was to figure out how to interact with queues and threads, while pressing buttons in the gui.
Basically my fault was to declare the queue in the wrong class and the wrong use of the after function while not knowing where to start the threads.
The new implementation follows the principle of filling the queue up in a new thread, which is spawned when pressing the gui button. The queue is periodically checked from the main thread whether theres something to do. This prevents unresponsiveness due to a safe communication between gui and mainthread.

Tkinter application becoming less responsive over time

I am having problems with my TKinter application when run for long periods of time. The application is simple it receives serial data through usb and prints it on the TK window. It works fine for a long time but when left for hald a day or overnight it is not responsive and I get the generic windows (not responding) error on the application bar at the top, if I try to minimise or close the window it may take up to 5~10 minutes to do it.
I do not get any errors on the python terminal window
I have changed my battery and power setting on my computer to not sleep and normal performance, still not resolved problem
I have stripped my code to the bare minimum to see if it was a section of code cousing the trouble
Here is my code posted below, hopefully some one can shed some light on this for me.
import tkinter as tk
from tkinter import *
from tkinter import ttk
import serial
import numpy
import sys
arduinoData = serial.Serial('com7', 115200) #Creating our serial object named arduinoData
# Main Tkinter application
class Application(Frame):
# Init the variables & start measurements
def __init__(self, master=None):
Frame.__init__(self, master)
root.title( "Dashboard")
root.state('zoomed')
self.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=3)
self.B1 = StringVar()
self.createWidgets()
self.pack()
self.measure()
# Create display elements
def createWidgets(self):
self.temperature = Label(self, text="", font=('Verdana', 20)).grid(row=5, column=0,padx=100,pady=200)
# Read the incoming serial data and display it
def measure(self):
if(arduinoData.inWaiting()>0): #Wait till there is data to read
arduinoString = arduinoData.readline() #read serial data
arduinoString =str(arduinoString,'utf-8') #Removes the surrounding rubbish
self.B1.set(str(arduinoString)) #Set the label to the received arduino data
self.B1DO = Label(self, textvariable=self.B1, font=('Verdana', 15)).grid(row=0, column=0, sticky="nsew")
arduinoData.flushOutput() #Clear old data
arduinoData.flushInput()
self.after(1000,self.measure) #Wait 1 second between each measurement
# Create and run the GUI
root = Tk()
app = Application(master=root)
app.mainloop()
It looks like you are perpetually creating new B1DO Labels, which could create a resource leak in your application. Try taking the self.B1DO definition and put it in createWidgets so that the Label is only created once:
import tkinter as tk
from tkinter import *
from tkinter import ttk
import serial
import numpy
import sys
arduinoData = serial.Serial('com7', 115200) #Creating our serial object named arduinoData
# Main Tkinter application
class Application(Frame):
# Init the variables & start measurements
def __init__(self, master=None):
Frame.__init__(self, master)
root.title( "Dashboard")
root.state('zoomed')
self.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=3)
self.B1 = StringVar()
self.createWidgets()
self.pack()
self.measure()
# Create display elements
def createWidgets(self):
self.B1DO = Label(self, textvariable=self.B1, font=('Verdana', 15)).grid(row=0, column=0, sticky="nsew")
self.temperature = Label(self, text="", font=('Verdana', 20)).grid(row=5, column=0,padx=100,pady=200)
# Read the incoming serial data and display it
def measure(self):
if(arduinoData.inWaiting()>0): #Wait till there is data to read
arduinoString = arduinoData.readline() #read serial data
arduinoString =str(arduinoString,'utf-8') #Removes the surrounding rubbish
self.B1.set(str(arduinoString)) #Set the label to the received arduino data
arduinoData.flushOutput() #Clear old data
arduinoData.flushInput()
self.after(1000,self.measure) #Wait 1 second between each measurement
# Create and run the GUI
root = Tk()
app = Application(master=root)
app.mainloop()

How to start and stop animation.FuncAnimation by pressing a button?

I'm new to python programming, so sorry if it's a stupid question.
I am using python, matplotlib and tkinter to plot a live graph of data in a tkinter gui. Therefore I followed the tutorial of this link: https://pythonprogramming.net/plotting-live-bitcoin-price-data-tkinter-matplotlib/
Here, the animation function is used, to plot the data. I would like to start and stop this animationFunc by pressing a button on the gui, but don't know how to realize it.
I would be grateful for any kind of help!
Thanks
Here is the code:
import Tkinter as tk
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import serial
import sys
ser = serial.Serial(port='COM3', baudrate = 19200, bytesize=8, parity='N',
stopbits=1, timeout=None,rtscts=0)
LARGE_FONT = ("Verdana", 12)
style.use('ggplot')
f = Figure(figsize=(5,5), dpi = 100)
a = f.add_subplot(111)
yList = []
def animate(i):
# I receive data as a string with a hex number, so here I read it, take
#the number and append it to the yList
r = ser.readline()
he = r.split(" ")[2]
g = int(he,16)
d = str(g)
yList.append(g)
a.clear()
a.plot(yList)
class Sensors(tk.Tk):
def __init__(self,*args, **kwargs ):
tk.Tk.__init__(self,*args,**kwargs)
container = tk.Frame(self)
container.pack(side="top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (ChooseSensor, Sensor3):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(ChooseSensor)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
#here I want to define the function to stop the animate function and to
#stop the serial port
def stopPlot(event,cont):
#command to serial port to stop sensor
ser.write(b'S1,P,0\r\n')
#here i want to create the function to start the animate function
def animate_start(self,cont):
#command to configure and start sensor
ser.write(b'S1,S,2\r\n')
ser.write(b'S1,T,1\r\n')
ser.write(b'S1,P,1\r\n')
class ChooseSensor(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text = "Choose Sensor", font= LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self,text="Graph Page",
command=lambda:controller.show_frame(Sensor3))
button1.pack()
class Sensor3(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text = "Graph Page", font= LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self,text="Start Plot",
command=lambda:controller.animate_start(self))
button1.pack()
button2 = tk.Button(self,text="Stop Plot",
command=lambda:controller.stopPlot(self))
button2.pack()
button3 = tk.Button(self,text="Back to Home",
command=lambda:controller.show_frame(ChooseSensor))
button3.pack()
canvas = FigureCanvasTkAgg(f, self)
canvas.show()
canvas.get_tk_widget().pack(side=tk.TOP, fill = tk.BOTH, expand = True)
app = Sensors()
ani = animation.FuncAnimation(f,animate, interval=1000)
app.mainloop()
EDIT:
I tried to do something like this:
def animate_start(self,cont):
global anim_start
anim_start=True
#Konfiguriert und schreibt Sensor1
ser.write(b'S1,S,2\r\n')
ser.write(b'S1,T,1\r\n')
ser.write(b'S1,P,1\r\n')
return anim_start
def stopPlot(event,cont):
anim_start=False
ser.write(b'S1,P,0\r\n')
return anim_start
And then execute the animation function only, when anim_start == True:
app = Sensors()
if anim_start == True:
ani = animation.FuncAnimation(f,animate, interval=1000)
app.mainloop()
Now I can see, that the commands are send and data is received (LED lights at the USB Serial bridge blink), but there is no data added to the yList anymore and so no graph is plotted

tkinter cursor not changing until after action despite update_idletasks()

I am trying to change the cursor in my tkinter program to show the program is working but the cursor only changes to the working cursor until after the work is done, this is as compressed as I can make the code
warning: to demonstrate working it will count to 99,999,999 when you press go to page one
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=self.go)
button1.pack()
def go(self):
# do something for like 5 seconds to demonstrate working
working(True)
l = [x for x in range(99999999)]
self.controller.show_frame('PageOne')
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=self.back)
button.pack()
def back(self):
working(False)
self.controller.show_frame('StartPage')
def working(yesorno):
if yesorno==True:
app.config(cursor='wait')
else:
app.config(cursor='')
app.update_idletasks()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Edit: I would like to thank Switch between two frames in tkinter for this app layout example
This code was tested in windows 10 and Python 3. I found the cursor would not change until control was returned to mainloop. The code here outlines how to consistently display the busy cursor during a long running task. Further, this code demonstrates how to retrieve the data from the long running task (like results from a database query).
#! python3
'''
Everything you need to run I/O in a separate thread and make the cursor show busy
Summary:
1. Set up to call the long running task, get data from windows etc.
1a. Issue a callback to the routine that will process the data
2. Do the long running task - absolutely no tkinter access, return the data
3. Get the data from the queue and process away. tkinter as you will
'''
import tkinter as tk
import tkinter.ttk as ttk
from threading import Thread
from threading import Event
import queue
class SimpleWindow(object):
def __init__(self):
self._build_widgets()
def _build_widgets(self):
# *************************************************************************************************
# * Build buttons and some entry boxes
# *************************************************************************************************
g_col = 0
g_row = 0
WaiterFrame = ttk.Frame()
WaiterFrame.pack( padx=50)
i = 0
g_row += 1
longWaitButton = ttk.Button(WaiterFrame, text='Long Wait',command=self.setup_for_long_running_task)
longWaitButton.grid(row = g_row, column = i, pady=4, padx=25)
i += 1
QuitButton = ttk.Button(WaiterFrame, text='Quit', command=self.quit)
QuitButton.grid(row = g_row, column = i,pady=4, padx=25)
i += 1
self.Parm1Label = ttk.Label(WaiterFrame, text="Parm 1 Data")
self.Parm1Label.grid(row = g_row-1, column = i, pady=4, padx=2)
self.Parm1 = ttk.Entry(WaiterFrame)
self.Parm1.grid(row = g_row, column = i, pady=4, padx=2)
i += 1
self.Parm2Label = ttk.Label(WaiterFrame, text="Parm 2 Data")
self.Parm2Label.grid(row = g_row-1, column = i, pady=4, padx=2)
self.Parm2 = ttk.Entry(WaiterFrame)
self.Parm2.grid(row = g_row, column = i, pady=4, padx=2)
i += 1
self.Parm3Label = ttk.Label(WaiterFrame, text="Parm 3 Data")
self.Parm3Label.grid(row = g_row-1, column = i, pady=4, padx=2)
self.Parm3 = ttk.Entry(WaiterFrame)
self.Parm3.grid(row = g_row, column = i, pady=4, padx=2)
i += 1
self.Parm4Label = ttk.Label(WaiterFrame, text="Parm 4 Data")
self.Parm4Label.grid(row = g_row-1, column = i, pady=4, padx=2)
self.Parm4 = ttk.Entry(WaiterFrame)
self.Parm4.grid(row = g_row, column = i, pady=4, padx=2)
def quit(self):
root.destroy()
root.quit()
def setup_for_long_running_task(self):
# ********************************************************************************************************
# * Do what needs to be done before starting the long running task in a thread
# ********************************************************************************************************
Parm1, Parm2, Parm3, Parm4 = self.Get_Parms()
root.config(cursor="wait") # Set the cursor to busy
# ********************************************************************************************************
# * Set up a queue for thread communication
# * Invoke the long running task (ie. database calls, etc.) in a separate thread
# ********************************************************************************************************
return_que = queue.Queue(1)
workThread = Thread(target=lambda q, w_self, p_1, p_2, p_3, p_4: \
q.put(self.long_running_task(Parm1, Parm2, Parm3, Parm4)),
args=(return_que, self, Parm1, Parm2, Parm3, Parm4))
workThread.start()
# ********************************************************************************************************
# * Busy cursor won't appear until this function returns, so schedule a callback to accept the data
# * from the long running task. Adjust the wait time according to your situation
# ********************************************************************************************************
root.after(500,self.use_results_of_long_running_task,workThread,return_que) # 500ms is half a second
# ********************************************************************************************************
# * This is run in a thread so the cursor can be changed to busy. NO tkinter ALLOWED IN THIS FUNCTION
# ********************************************************************************************************
def long_running_task(self, p1,p2,p3,p4):
Event().wait(3.0) # Simulate long running task
p1_out = f'New {p1}'
p2_out = f'New {p2}'
p3_out = f'New {p3}'
p4_out = f'New {p4}'
return [p1_out, p2_out, p3_out, p4_out]
# ********************************************************************************************************
# * Waits for the thread to complete, then gets the data out of the queue for the listbox
# ********************************************************************************************************
def use_results_of_long_running_task(self, workThread,return_que):
ThreadRunning = 1
while ThreadRunning:
Event().wait(0.1) # this is set to .1 seconds. Adjust for your process
ThreadRunning = workThread.is_alive()
while not return_que.empty():
return_list = return_que.get()
self.LoadWindow(return_list)
root.config(cursor="") # reset the cursor to normal
def LoadWindow(self, data_list):
self.Parm1.delete(0, tk.END)
self.Parm2.delete(0, tk.END)
self.Parm3.delete(0, tk.END)
self.Parm4.delete(0, tk.END)
i=0; self.Parm1.insert(0,data_list[i])
i+=1; self.Parm2.insert(0,data_list[i])
i+=1; self.Parm3.insert(0,data_list[i])
i+=1; self.Parm4.insert(0,data_list[i])
# ********************************************************************************************************
# * The long running task thread can't get to the tkinter self object, so pull these parms
# * out of the window and into variables in the main process
# ********************************************************************************************************
def Get_Parms(self):
p1 = self.Parm1Label.cget("text")
p2 = self.Parm2Label.cget("text")
p3 = self.Parm3Label.cget("text")
p4 = self.Parm4Label.cget("text")
return p1,p2,p3,p4
def WaitForBigData():
global root
root = tk.Tk()
root.title("Wait with busy cursor")
waitWindow = SimpleWindow()
root.mainloop()
if __name__ == '__main__':
WaitForBigData()
I suspect that all events need to be proceeded to change a cursor's look, because cursor depends on operating system and there're some events to handle (I assume that), since update_idletask has no effect - your cursor really change look only when code flow reaches a mainloop. Since you can treat an update as mainloop(1) (very crude comparison) - it's a good option if you know what you doing, because noone wants an endless loop in code.
Little snippet to represent idea:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text='Toggle cursor', command=self.toggle_business)
self.button.pack()
def toggle_business(self):
if self['cursor']:
self.config(cursor='')
else:
self.config(cursor='wait')
# self.update_idletasks() # have no effect at all
# self.update() # "local" mainloop(1)
# simulate work with time.sleep
# time.sleep(3)
# also your work can be scheduled so code flow can reach a mainloop
# self.after(500, lambda: time.sleep(3))
app = App()
app.mainloop()
To overcome this problem you can use:
update method (note warnings)
after method for scheduled work (opportunity to reach a mainloop for a code flow)
threading for "threaded" work (another opportunity, but GUI is responsive, you can handle other events and even simulate unresponsiveness, in other hand threading adds complexity, so use it if you really need it).
Note: There's no difference in behaviour between universal and native cursors on Windows platform.

Tkinter Canvas Update

I am building a script having one counter and an image. Both are in different canvas on single window. I am trying to update the canvas image once the timer stops. Below is my code. I am not getting whats wrong with this
from Tkinter import *
import Tkinter as tk
import stopwatch
import stone_image
class App():
def __init__(self, root):
self.root = root
self.root.configure(background="yellow")
global stoneImagePanel
stopwatchPanel = tk.Canvas(self.root, background="yellow")
stoneImagePanel = tk.Canvas(self.root, background="yellow")
stopwatchPanel.grid(row=0, column=0, columnspan=4, sticky="e")
stoneImagePanel.grid(row=1, column=0, rowspan=2, columnspan=4, sticky="news")
self.stoneImage_canvas(stoneImagePanel)
self.stopWatch_canvas(stopwatchPanel)
def stopWatch_canvas(self, parent):
stopwatch.timerStart(parent, 500)
stoneImagePanel.after(500, self.update(stoneImagePanel))
def stoneImage_canvas (self, parent):
displayImage = stone_image.DisplayImage(parent)
displayImage.stoneImg('images/stone.jpg')
def update(self, parent):
displayImage = stone_image.DisplayImage(parent)
displayImage.stoneImg('images/UP.png')
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
Neither the stopwatch is working smoothly nor the image is updating. Please help.

Categories