Python: Have gifs animate with TKinter while a thread is running - python

So, as I'm sure you can tell from my rather long subject what I'm asking but to reiterate. I am trying to have a gif animate while a thread in the background is running a calculation with in TKinter. For simplicities sake, I am using this gif: http://i.imgur.com/4Y9UJ.gif
The gif animation is currently working as you can see if you run the script. But as soon as the Generate button is pressed and the thread initiates, the gif pauses until it finishes. I'm using time.sleep() to simulate the large amount of calculations that I will be doing in the background (though I am unsure if this is what is actually causing the issue).
I'm sure it has something to do with not understanding exactly how the animation is functioning. Any advice?
Code as follows:
from Tkinter import *
import tkMessageBox
import time
import os
import threading
from PIL import Image, ImageTk
class Gif(Label):
def __init__(self, master, filename):
evanGif = Image.open(filename)
gifSeq = []
try:
while 1:
gifSeq.append(evanGif.copy())
evanGif.seek(len(gifSeq)) # skip to next frame
except EOFError:
pass # we're done
try:
#Special case for the evangalion gif
if evanGif.info['duration'] == 0:
self.delay = 100
else:
self.delay = evanGif.info['duration']
except KeyError:
self.delay = 100
gifFirst =gifSeq[0].convert('RGBA')
self.gifFrames = [ImageTk.PhotoImage(gifFirst)]
Label.__init__(self, master, image=self.gifFrames[0])
temp =gifSeq[0]
for image in gifSeq[1:]:
temp.paste(image)
frame = temp.convert('RGBA')
self.gifFrames.append(ImageTk.PhotoImage(frame))
self.gifIdx = 0
self.cancel = self.after(self.delay, self.play)
def play(self):
self.config(image=self.gifFrames[self.gifIdx])
self.gifIdx += 1
if self.gifIdx == len(self.gifFrames):
self.gifIdx = 0
self.cancel = self.after(self.delay, self.play)
class App:
generating = False
def __init__(self, master):
self.master=master
#Initializing frames
self.buttonFrame = Frame(master, background='light gray')
self.loadingFrame = Frame(master, background='light gray')
self.loadingFrame.grid(row=0)
self.buttonFrame.grid(row=1)
self.anim = Gif(self.loadingFrame, '4Y9UJ.gif').pack()
self.update_Thread = threading.Thread(target=time.sleep, args=(5,))
self.buttonSetup()
def buttonSetup(self):
#ALL THE BUTTONS
self.generateBt = Button(self.buttonFrame, text="Generate!", command=self.generate, background='light gray', highlightbackground='light gray')
self.generateBt.pack(side=LEFT)
self.quitBt = Button(self.buttonFrame, text="Quit!", fg="red", command=self.buttonFrame.quit, background='light gray', highlightbackground='light gray')
self.quitBt.pack(side=LEFT)
def generate(self):
self.hideForGen()
self.update_Thread.start()
while(self.update_Thread.isAlive()):
self.master.update_idletasks()
self.reset()
self.master.update_idletasks()
tkMessageBox.showinfo("Complete", "Report generation completed!")
def hideForGen(self):
self.buttonFrame.grid_forget()
def reset(self):
self.buttonFrame.grid(row=1)
root = Tk()
root.title('Test')
root.configure(background='light gray')
app = App(root)
root.mainloop()

The trouble is in your generate() method.
That while loop is unnecessary.
Bear in mind that you can't re-start a thread.
If you want to use the generate button multiple times, you'll need to create a new thread each time.
class App:
generating = False
def __init__(self, master):
self.master=master
#Initializing frames
self.buttonFrame = Frame(master, background='light gray')
self.loadingFrame = Frame(master, background='light gray')
self.loadingFrame.grid(row=0)
self.buttonFrame.grid(row=1)
self.anim = Gif(self.loadingFrame, '4Y9UJ.gif')
self.anim.pack()
## self.update_Thread = threading.Thread(target=time.sleep, args=(5,))
self.buttonSetup()
def buttonSetup(self):
#ALL THE BUTTONS
self.generateBt = Button(self.buttonFrame, text="Generate!", command=self.generate, background='light gray', highlightbackground='light gray')
self.generateBt.pack(side=LEFT)
self.quitBt = Button(self.buttonFrame, text="Quit!", fg="red", command=self.buttonFrame.quit, background='light gray', highlightbackground='light gray')
self.quitBt.pack(side=LEFT)
def wait_generate(self):
if self.update_Thread.isAlive():
self.master.after(500, self.wait_generate)
else:
tkMessageBox.showinfo("Complete", "Report generation completed!")
self.reset()
def generate(self):
self.hideForGen()
self.update_Thread = threading.Thread(target=time.sleep, args=(5,))
self.update_Thread.start()
## while(self.update_Thread.isAlive()):
## self.master.update_idletasks()
## self.reset()
## self.master.update_idletasks()
## tkMessageBox.showinfo("Complete", "Report generation completed!")
self.wait_generate()
def hideForGen(self):
self.buttonFrame.grid_forget()
def reset(self):
self.buttonFrame.grid(row=1)

In the method below:
def generate(self):
self.hideForGen()
self.update_Thread.start()
while(self.update_Thread.isAlive()):
self.master.update_idletasks()
self.reset()
self.master.update_idletasks()
tkMessageBox.showinfo("Complete", "Report generation completed!")
If you put
self.master.update()
instead of
self.master.update_idletasks()
it should work. At least it did for me.

Related

How to insert scrollbar into canvas filled with buttons?

I'm trying to insert a scrollbar into a canvas filled with buttons (where, buttons can be switched to clickable text as well).
With the code below, the window seems OK.
However, when I uncomment the lines with the scrollbar, the window geometry scrambles.
Can anybody give a hand to figure out what's wrong with the code?
System: Windows 10, python 3.9.6.
import tkinter as tk
class tWindow(tk.Tk):
frame = None
canvas = None
scrollbar = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.geometry("640x480+50+50")
self.setup_frame()
self.setup_canvas()
def setup_frame(self):
self.frame = tk.Frame(master=self, width=640, height=480)
self.frame.configure(bg="#003163")
self.frame.place(x=0, y=0)
def setup_canvas(self):
self.update()
self.canvas = tk.Canvas(master=self.frame, bg="#006331",
width=int(self.frame.winfo_width() / 4), height=self.frame.winfo_height())
self.canvas.place(x=0, y=0)
self.update()
# self.scrollbar = tk.Scrollbar(master=self.canvas, orient=tk.VERTICAL)
# self.scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH, expand=tk.TRUE)
# self.canvas.configure(yscrollcommand=self.scrollbar.set)
# self.scrollbar.configure(command=self.canvas.yview)
# self.update()
tmp_pos = 0
for i in range(20):
btn_tmp = tk.Button(master=self.canvas, text=f"testing testing testing testing testing {i:02} ...",
justify=tk.LEFT, wraplength=self.canvas.winfo_width(), bg="#00c0c0", fg="#000000")
btn_tmp.place(x=0, y=tmp_pos)
self.update()
tmp_pos = btn_tmp.winfo_y() + btn_tmp.winfo_height()
def main():
app = tWindow()
app.mainloop()
if __name__ == "__main__":
main()
See comments in code as additional to the link I have provided. Also see why update is considered harmfull and how do I organize my tkinter gui for more details.
import tkinter as tk
class tWindow(tk.Tk):
frame = None
canvas = None
scrollbar = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.geometry("640x480+50+50")
self.setup_frame()
self.setup_canvas()
def setup_frame(self):
self.frame = tk.Frame(master=self, width=640, height=480,bg="#003163")
self.frame.pack(side='left',fill='both',expand=True)##pack left
## self.frame.place(x=0, y=0) ##place requiers too think to much
def setup_canvas(self):
## self.update() ##update is harmfull
self.canvas = tk.Canvas(master=self.frame, bg='orange',#"#006331",
width=int(self.frame.winfo_reqwidth() / 4), height=self.frame.winfo_reqheight())
##use reqwidth/hight to avoid the use of update.
##It takes the requested size instead the actual
self.canvas.pack(side='left')
self.canvas_frame = tk.Frame(self.canvas,bg="#006331")
##frame to pack buttons in canvas
self.canvas.create_window((0,0), window=self.canvas_frame, anchor="nw")
##show frame in canvas
self.scrollbar = tk.Scrollbar(master=self.frame, orient=tk.VERTICAL)
self.scrollbar.pack(side='left',fill='y')
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.scrollbar.configure(command=self.canvas.yview)
self.canvas_frame.bind('<Configure>',self.oncanvasconfigure)
##bind event configure to update the scrollregion
for i in range(20):
btn_tmp = tk.Button(master=self.canvas_frame, text=f"testing testing testing testing testing {i:02} ...",
justify=tk.LEFT, wraplength=self.canvas.winfo_reqwidth(), bg="#00c0c0", fg="#000000")
btn_tmp.pack()
def oncanvasconfigure(self,event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def main():
app = tWindow()
app.mainloop()
if __name__ == "__main__":
main()

Get access to a variable method to end threading loop?

I have question about how to call some variables, in this case from the method inside exportCsv that belongs to back_Gui class. I want to use the variables self.msg_ and self.opstat in the method __update from class _Gui to stop the reproduction of the gift and promt out the window to save the file. When I run the code and press the button it iterates in an infinite loop because the variable is not passing to. Also, I try to aggregate some threading to not to freeze the window when the button is pressed. Any solution?.
Also, I think these variables I mention would be inside the try but what happens in the except?. Do I have to create more variables to avoid infinite looping on the gift?
from tkinter import *
import tkinter as tk
from tkinter import filedialog
import sqlite3
import pandas as pd
from PIL import Image, ImageTk
import time
import threading
class back_Gui: #Superclass
'''Class that handle the data'''
def __init__(self, db_name = 'database.db'):
self.db_name = db_name
self.msg_ = None
self.opstat = None
.
.
.
def exportCSV(self):
df_to = self.df
try:
export_file = filedialog.asksaveasfilename(defaultextension='.csv')
df_to.to_csv(export_file, index=False, header=True)
except:
pass
#These are the variables that I need
self.msg_ = "Done."
self.opstat = -1
class _Gui(back_Gui): #Subclass
def __init__(self, window):
'''Gui of the windw tk '''
self.wind = window #child
super().__init__()
self.text_font = ('Helvetica', '10', 'bold')
self.Button_(self.wind)
def Button_(self, wind):
"""Run the gift while the csv is being generated"""
#Button
b1 = Button(self.wind, text="random",
font=self.text_font,
command=self.job_genCsv,
).grid(row=1, column=5, padx=5, pady=5 ,sticky=W)
def frame_maps(self, wind):
'''Frame Containter'''
self.frame = LabelFrame(self.wind, text='Hail Hydra', font=self.text_font, height = 500, width = 1300, bd=4)
self.frame.grid(row=2, columnspan=20, sticky=W+E)#, columnspan=3, padx=25, pady=25)
# create the canvas, size in pixels
self.canvas = Canvas(self.frame, width=1300, height=500, bg='white')
# pack the canvas into a frame/form
self.canvas.grid(row=0, columnspan=20, sticky=N, ipadx=20, ipady=20)#
# load the .gif image file
#Here it has to be use the self because is local variable
self.current_image_number = 0
file="cain.gif"
info = Image.open(file)
self.frames = info.n_frames
self.gif1 = [PhotoImage(file=file, format=f"gif -index {i}") for i in range(self.frames)]
def __update(self):
#self.job_genCsv()
''' update the gift '''
self.frame.update_idletasks()#update
if self.opstat >= 0.0:
#msg = self.image_on_canvas #
# next image
self.current_image_number += 1
# return to first image
if self.current_image_number == self.frames: #len(self.images):
self.current_image_number = 0
# change image on canvas
self.canvas.itemconfig(self.update, image=self.gif1[self.current_image_number])
_ = threading.Timer(0, self.__update).start()
print("loop")
else:
if self.msg_ == "Done.":
self.update = self.canvas.create_image(250, 50, anchor=NW, image=self.gif1[self.current_image_number])
del self.msg_
#control variable restablished
self.opstat = 0
print("ends")
def job_genCsv(self):
'''Runs his job and call frame_maps '''
self.frame_maps(self)
_ = threading.Timer(0, self.exportCSV).start()
_ = threading.Timer(0, self.__update).start()
if __name__ == '__main__':
window = Tk()
application = _Gui(window)
window.mainloop()
I'm getting this error:
File "C:\Users\Documents\run.py", line 214, in __update
if self.opstat >= 0.0:
AttributeError: '_Gui' object has no attribute 'opstat'

communication between tkinter toplevels

I'm working on a little project and made a little on-screen keyboard as a tkinter Toplevel
my application is buildt like this:
Root-Window (Tk-Widget)
input 1 (Entry-Widget)
input 2 (Entry-Widget)
input 3 (Text-Widget)
on_screen-keyboard (Toplevel-Widget)
the Toplevel-Widget contains Buttons, with callbacks that should enter text in the entries (just like keyboard-Buttons)
What I want is a communication between children of the keyboard/the keyboard and the last active input-Widget. My Problem is, that I don't know, how to say the keyboard, to which input-Widget it should send the message.
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.active_input = tk.Variable(value=None)
ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
self.text = tk.StringVar(value="")
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", lambda e: self.active_input.set(self.input1))
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", lambda e: self.active_input.set(self.input2))
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", lambda e: self.active_input.set(self.input3))
self.input1.pack()
self.input3.pack()
self.input2.pack()
class Keyboard(tk.Toplevel):
OPENED = False
NAME = "- Keyboard -"
NUM = [{"text":"1", "width":1},
{"text":"2", "width":1},
{"text":"3", "width":2}]
CHAR= [{"text":"A", "width":1},
{"text":"B", "width":1},
{"text":"C", "width":2}]
def __init__(self, master):
if not Keyboard.OPENED:
Keyboard.OPENED = True
print("keyboard opened!")
self.master = master
tk.Toplevel.__init__(self, master)
self.title(self.NAME)
self.protocol("WM_DELETE_WINDOW", self.close)
self.keyb_nb = ttk.Notebook(self)
self.keyb_nb.pack()
self.num_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.num_tab, Keyboard.NUM,2)
self.keyb_nb.add(self.num_tab, text="123")
self.char_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.char_tab, Keyboard.CHAR, 2)
self.keyb_nb.add(self.char_tab, text="ABC")
def createPad(self, master, pad:list, max_col):
self.co_count = 0
self.ro = 1
for button in pad:
button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=self.bclicked(button))
if self.co_count >= max_col:
self.ro = self.ro + 1
self.co_count = 0
button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
self.co_count = self.co_count+button["width"]
def bclicked(self, button:dict):
"""
reciver = self.master.active_input #I think the Problem here is, that the variable contains a string, not a widget
reciever.focus_force()
reciever.insert(index=tk.INSERT, string=button["text"])
"""
pass
def close(self):
Keyboard.OPENED = False
self.destroy()
print("keyboard closed!")
root = MainWindow()
root.mainloop()
Here the init of the Mainwindow and the bclicked of the Keyboard class are important...
the code is debug-ready
I would prefer a solution, similar to the communication in the internet (sender=button, receiver-id, message), but very welcome every working solution
btw: I'm also looking for a solution, how I don't have to force the input to focus and the Toplevel stays an the highest layer of the screen (that if I focus the Tk-Widget/one of the inputs/the button, the keyboard will stay in front of it)
SUMMARY: how do I find out, which of the 3 input-widgets was active at last, when the keyboard-toplevel has already the focus?
I may made more changes than needed, but mainly focus on the method keyboard_triger() and pass_key_to_master(), this two use the idea that the variable master implements, having access to call methods out of scope.
Olso the method set_focused_object() stores a reference to the last object beeng focused, note than it stores the widget and not the event, it's easyer than searching each time the object
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def keyboard_triger(self, key):
# to identify wath object is just use
# isinstance(self.active_input, ttk.Entry)
self.active_input.insert(tk.END, key)
def new_keyboard(self):
Keyboard(self)
def set_focused_object(self, event):
self.active_input = event.widget
def __init__(self):
tk.Tk.__init__(self)
self.active_input = None
ttk.Button(self, text="Show Keyboard", command=self.new_keyboard).pack()
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", self.set_focused_object)
self.input1.pack()
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", self.set_focused_object)
self.input2.pack()
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", self.set_focused_object)
self.input3.pack()
class Keyboard(tk.Toplevel):
def pass_key_to_master(self, key):
self.master.keyboard_triger(key)
def __init__(self, master):
tk.Toplevel.__init__(self, master)
self.master = master
self.title('Keyboard')
# this way of agruping keys stores the kwags
# of the drawing method
keys = {
'A': {'x': 0, 'y': 0},
'B': {'x': 20, 'y': 20},
'C': {'x': 50, 'y': 50}
}
# expected structure
# {string key: reference to the button}
self.buttons = {}
for i in keys:
self.buttons[i] = tk.Button( # i=i is required to make a instance
self, text=i, command=lambda i=i: self.pass_key_to_master(i)
)
self.buttons[i].place(**keys[i])
if __name__ == '__main__':
root = MainWindow()
root.mainloop()
Your code maybe could have a better construction.(But I didn't revise your code construction.)
Followed by your code,I use a global variable.And fix some errors in your code.And it could work normally in my computer.
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.active_input = tk.Variable(value=None)
ttk.Button(self, text="show Keyboard", command=lambda: Keyboard(self)).pack()
global focusedWidget
focusedWidget = None
self.text = tk.StringVar(value="")
self.input1 = ttk.Entry(self)
self.input1.bind("<FocusIn>", self.getFocusWidget)
self.input2 = ttk.Entry(self)
self.input2.bind("<FocusIn>", self.getFocusWidget)
self.input3 = tk.Text(self, height=3, width=15)
self.input3.bind("<FocusIn>", self.getFocusWidget)
self.input1.pack()
self.input3.pack()
self.input2.pack()
def getFocusWidget(self,event): # this function could be a static function
global focusedWidget
focusedWidget = event.widget
class Keyboard(tk.Toplevel):
OPENED = False
NAME = "- Keyboard -"
NUM = [{"text":"1", "width":1},
{"text":"2", "width":1},
{"text":"3", "width":2}]
CHAR= [{"text":"A", "width":1},
{"text":"B", "width":1},
{"text":"C", "width":2}]
def __init__(self, master):
if not Keyboard.OPENED:
Keyboard.OPENED = True
print("keyboard opened!")
self.master = master
tk.Toplevel.__init__(self, master)
self.title(self.NAME)
self.protocol("WM_DELETE_WINDOW", self.close)
self.keyb_nb = ttk.Notebook(self)
self.keyb_nb.pack()
self.num_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.num_tab, Keyboard.NUM,2)
self.keyb_nb.add(self.num_tab, text="123")
self.char_tab = ttk.Frame(self.keyb_nb)
self.createPad(self.char_tab, Keyboard.CHAR, 2)
self.keyb_nb.add(self.char_tab, text="ABC")
def createPad(self, master, pad:list, max_col):
self.co_count = 0
self.ro = 1
for button in pad:
button["id"] = ttk.Button(master, width=6*button["width"], text=button["text"], command=lambda button=button:self.bclicked(button)) # this lambda expression has some errors.
if self.co_count >= max_col:
self.ro = self.ro + 1
self.co_count = 0
button["id"].grid(row=self.ro, columnspan=button["width"], column=self.co_count)
self.co_count = self.co_count+button["width"]
def bclicked(self, button:dict):
global focusedWidget
"""
reciver = self.master.active_input #I think the Problem here is, that the variable contains a string, not a widget
reciever.focus_force()
reciever.insert(index=tk.INSERT, string=button["text"])
"""
if not focusedWidget: # If user hasn't click a entry or text widget.
print("Please select a entry or text")
return
if focusedWidget.widgetName=='ttk::entry': # use if statement to check the type of selected entry.
focusedWidget.insert(index=tk.INSERT,string=button["text"])
else:
focusedWidget.insert("end",button["text"])
def close(self):
Keyboard.OPENED = False
self.destroy()
print("keyboard closed!")
root = MainWindow()
root.mainloop()

Python tkinter threading and window refresh

I'm using python and tkinter to build a visualization tool that can refresh and visualize an updating object. Right now, the object can't change because the threading is not working. Any help or general knowledge would be appreciated. I'm relatively new to threading and tkinter.
example object I want to ingest
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
my visualization code
class my_visual(threading.Thread):
def __init__(self, col1):
threading.Thread.__init__(self)
self.start()
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
Code that works
c = color1('RED')
test = my_visual(c)
test.viz()
Code that doesn't work
In this version, the refresh works, but the threading doesn't. When the threading is working, the refresh won't pick up that the object has changed.
c.change_col('BLUE')
If you extend the threading.Thread class you need to override the run() method with your custom functionality. With no run method, the thread dies immediately. You can test whether a thread is alive with my_visual.is_alive().
The problem is that your test.viz() is an infinite loop because of self.root.mainloop(), so you cannot do anything once you called that function. The solution is to use a thread for test.viz(), and your thread for the class my_visual is no more necessary.
I added a time.sleep of 2 seconds before the refresh makes it blue, otherwise the color is blue at beginning.
Here you go :
import threading
from tkinter import *
import time
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
class my_visual():
def __init__(self, col1):
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
print("self.col1", self.col1, self.col1.color)
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
c = color1('RED')
test = my_visual(c)
t2 = threading.Thread(target = test.viz)
t2.start()
time.sleep(2)
print('Now you can change to blue when refreshing')
c.change_col('BLUE')

Python: update tkinter canvas widget from thread events

I am trying to make a status light on my tkinter GUI. At this point I just want it to rotate from green to red to show that the script hasn't frozen. The python traceback errors that I get all point to __libraries that I don't understand. I feel like this must be a namespace problem, but I'm ripping out my hair trying to put my finger on it.
The eventCheck() method worked great at creating a label that toggled between 0 and 1 before I created the canvas object and tried passing c into it. There is so little information out there on what I am trying to do, maybe there is a better way?
Here is a condensed version of my script:
import tkinter as tk
from tkinter import Canvas
import time
import threading
class myGUI(tk.Frame):
def __init__(self, master, event):
self.master = master
self.event = event
super().__init__(master)
self.label = tk.Label(self, text="")
self.label.grid()
self.after(1, self.eventCheck)
c = tk.Canvas(self, bg='white', width=80, height=80)
c.grid()
self.eventCheck(c)
def redCircle(self, c):
c.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self,c):
c.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self, c):
self.label['text'] = self.event.is_set()
if self.label['text'] == 0:
self.redCircle(c)
else:
self.greenCircle(c)
self.after(2000, self.eventCheck(c))
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t=threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__=="__main__":
main()
I found two major issues with your code. First, this isn't doing what you think it should:
def eventCheck(self, c):
# ...
self.after(2000, self.eventCheck(c))
Because you passed the result of a call to self.eventCheck(c) to after() instead of the method self.eventCheck, this is an infinite recursion that takes place immediately.
The second issue is that if you comment out all your timing and event stuff, your interface never actually comes up, so there's never anything to see. I've condensed (simplified) your example script even further into one that basically works:
import tkinter as tk
import threading
import time
class myGUI:
def __init__(self, master, event):
self.master = master
self.event = event
self.label = tk.Label(master, text="")
self.label.pack()
self.canvas = tk.Canvas(master, bg='white', width=80, height=80)
self.canvas.pack()
self.eventCheck()
def redCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self):
flag = self.event.is_set()
self.label['text'] = flag
if flag:
self.greenCircle()
else:
self.redCircle()
self.master.after(2000, self.eventCheck)
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t = threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__ == "__main__":
main()
Now you should be able to add back your Frame superclass. Make sure to add the frame that is myGUI to your root object.

Categories

Resources