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()
Related
I am writing an application which involves a fair amount of data munging at launch. What I'd like to do is create a splash screen that tells the user what stage of the data loading process is happening in real time.
My plan was to create a Label and pass new text to that Label depending on what calculations were going on in that moment. However in my various attempts the best I've done is get the labels to show up only after the munging is complete.
I saw this, which helped me a bit, but still not getting all the way there:
Tkinter Show splash screen and hide main screen until __init__ has finished
Below is my current best attempt (taking all the actual dataframe stuff out to make it minimally executable)
[EDIT TO ADD] Ideally I'd like to do this in a way that doesn't require all the data munging to occur inside the class. IOW, phase 1 launches the splash screen, phase 2 runs the data munging in the main code, phase 3 launches the primary UI
import time
from tkinter import *
class LoadScreen(Toplevel):
def __init__(self, parent):
Toplevel.__init__(self, parent)
self.title('Loading')
self.update()
class UserInterface(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent=parent
self.withdraw()
loader = LoadScreen(self)
self.load_label = Label(loader, text='Loading')
self.load_label.grid(row=0, column=0, padx=20, pady=20)
self.stage_label = Label(loader, text='Preparing dataframe')
self.stage_label.grid(row=1, column=0, padx=20, pady=20)
#loader.destroy()
self.main_screen()
def main_screen(self):
self.deiconify()
self.load_label = Label(self, text='Done')
self.load_label.grid(row=0, column=0, padx=20, pady=20)
self.close_button = Button(self, text='Close',
command = lambda: self.destroy())
self.close_button.grid(row=1, column=0, padx=20, pady=20)
ui = UserInterface(None)
#Pretend I'm doing some dataframe munging
print('1')
time.sleep(2)
ui.stage_label['text'] = 'Running some calculations'
print('2')
time.sleep(2)
ui.stage_label['text'] = 'Finishing up'
print('3')
time.sleep(2)
ui.mainloop()
time.sleep will block the main thread. Here's a minimal sample on how I usually do it.
import time
from tkinter import *
root = Tk()
root.withdraw()
Label(root,text="I am main window").pack()
class SplashScreen:
def __init__(self):
self.a = Toplevel()
self.percentage = 0
Label(self.a,text="I am loading screen").pack()
self.load = Label(self.a,text=f"Loading...{self.percentage}%")
self.load.pack()
self.load_bar()
def load_bar(self):
self.percentage +=5
self.load.config(text=f"Loading...{self.percentage}%")
if self.percentage == 100:
self.a.destroy()
root.deiconify()
return
else:
root.after(100,self.load_bar)
SplashScreen()
root.mainloop()
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.
I'm just getting started coding in Python/Tkinter for a small Pymol plugin. Here I'm trying to have a toggle button and report its status when it is clicked. The button goes up and down, but toggleAVA never gets called. Any ideas why?
from Tkinter import *
import tkMessageBox
class AVAGnome:
def __init__(self, master):
# create frames
self.F1 = Frame(rootGnome, padx=5, pady=5, bg='red')
# checkbuttons
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(self.F1, text='AVA', indicatoron=0, variable=self.AVAselected)
# start layout procedure
self.layout()
def layout(self):
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
#entry and buttons
self.AVAbutton.pack(side=LEFT)
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
def __init__(self):
open_GnomeUI()
def open_GnomeUI():
# initialize window
global rootGnome
rootGnome = Tk()
rootGnome.title('AVAGnome')
global gnomeUI
gnomeUI = AVAGnome(rootGnome)
I tested your code with Pymol.
Problem is because you use Tk() to create your window. You have to use Toplevel() and then it will work correctly with trace() or with command=.
Pymol is created with tkinter which can have only one window created with Tk() - it is main window in program. Every other window has to be created with Toplevel().
I have attached a working version of your code below. You can refer to it to learn where you went wrong. Generally, you have to mind how you structure your code if you are using a class format.This will help you visualize your code and debug better. You can read this discussion to help you.
from Tkinter import *
import tkMessageBox
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
if __name__ == '__main__':
rootGnome = Tk()
rootGnome.title('AVAGnome')
gnomeUI = AVAGnome(rootGnome)
gnomeUI.pack(fill="both", expand=True)
gnomeUI.mainloop()
Update: The above code structure is for standalone tkinter programme. I am attempting to convert this working code to follow Pymol plugin example. Revised code is posted below and is susceptible to further revision.
# https://pymolwiki.org/index.php/Plugins_Tutorial
# I adapted from the example in the above link and converted my previous code to
#
from Tkinter import *
import tkMessageBox
def __init__(self): # The example had a self term here.
self.open_GnomeUI()
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
# Note, I added a "self" term throughout function.
# Try w/ & w/o "self" to see which works.
def open_GnomeUI(self):
self.rootGnome = Tk()
self.rootGnome.title('AVAGnome')
self.gnomeUI = AVAGnome(self.rootGnome)
self.gnomeUI.pack(fill="both", expand=True)
self.gnomeUI.mainloop()
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.
I'm trying to pack the button below the Text and Scrollbar widget.
#!/usr/bin/python
try:
from Tkinter import *
except ImportError:
from tkinter import *
class Chat(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.pack(anchor=N, fill=BOTH)
self.create_widgets()
self.count = 0
def create_widgets(self):
self.scrolly = Scrollbar(self)
self.scrolly.pack(side=RIGHT, fill=Y)
self.chattext = Text(self, borderwidth=5, yscrollcommand=self.scrolly.set)
self.chattext.pack(side=LEFT)
self.scrolly.config(command=Text.yview(self.chattext))
self.button1 = Button(self, text="Add text", command=self.add_text)
self.button1.pack()
def add_text(self):
self.count += 1
self.chattext.insert("end", "%i\n" % self.count)
self.chattext.update_idletasks()
def main():
root = Tk()
root.title("Test Chat Client")
root.geometry("600x500")
#root.resizable(0,0)
app = Chat(root)
root.mainloop()
if __name__ == "__main__":
main()
This is what it looks like
I want the button to be below and not in between the other widgets.
I have tried the following:
self.button1.pack(after=self.scrolly)
self.button1.pack(after=self.chattext)
How may i pack the button at the bottom?
Another issue is that the scrollbar does not work, when i try to scroll nothing happens.
(Yes, i have tried to fill the Text widget with alot of lines, more than it can view.)
Also, why is the scrollbar viewed/packed outside/"far" away from the Text widget?
Try using the grid geometry manager instead.
http://www.tkdocs.com/tutorial/grid.html
I think you should consider replacing the text field with a ScrolledText field.
It's a lot easier to use and doesn't require manual scrollbar placement.
(Don't use pack to place it though. Use grid)
import tkinter as tk
import tkinter.scrolledtext as tkst
self.chattext = tkst.ScrolledText(
master = self,
wrap = tk.WORD,
width = 20,
height = 10
)