I have created a tkinter GUI in the following structure:
import tkinter as tk
import threading
class App:
def __init__(self, master):
self.display_button_entry(master)
def setup_window(self, master):
self.f = tk.Frame(master, height=480, width=640, padx=10, pady=12)
self.f.pack_propagate(0)
def display_button_entry(self, master):
self.setup_window(master)
v = tk.StringVar()
self.e = tk.Entry(self.f, textvariable=v)
buttonA = tk.Button(self.f, text="Cancel", command=self.cancelbutton)
buttonB = tk.Button(self.f, text="OK", command=threading.Thread(target=self.okbutton).start)
self.e.pack()
buttonA.pack()
buttonB.pack()
self.f.pack()
def cancelbutton(self):
print(self.e.get())
self.f.destroy()
def okbutton(self):
print(self.e.get())
def main():
root = tk.Tk()
root.title('ButtonEntryCombo')
root.resizable(width=tk.NO, height=tk.NO)
app = App(root)
root.mainloop()
main()
I want to prevent the GUI from freezing when running a function (in the example code it's the function of the ok-button). For that I found the solution of using the thread-module as best practice. But the problem is that when I want to run the code once again, python returns this traceback:
RuntimeError: threads can only be started once
I'm totally aware of the problem that threads can be only starten once as stated in the error message. My question is: How can I stop a thread to start it a second time or does anybody has a better workaround for preventing the GUI from freezing and pressing a button/running a function multiple times?
BR and thank you
Lorenz
Your code will only create one thread and assign its start function reference to command option. Therefore same start() function will be called whenever the button is clicked.
You can use lambda instead:
command=lambda: threading.Thread(target=self.okbutton).start()
Then whenever the button is clicked, a new thread will be created and started.
Related
Ok so I'm pretty new to tkinter and I can not solve this problem that I am having.
When I run the program, it runs the function and after it ends the window with the photo pops up, but the loop does not start the program again.
import tkinter as tk
from tkinter import*
from PIL import ImageTk,Image
from tkinter import messagebox
import YuaChanMainFunc
import time
def on_click(event=None):
# `command=` calls function without argument
# `bind` calls function with one argument
print("Hey Yua!")
YuaChanMainFunc.statement="hey yua"
class Window(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
master.title("Yua-chan AI")
self.img = ImageTk.PhotoImage(Image.open("YuaChanAI/Yua Chan Artwork/YuaChan2.png"))
MainLB = tk.Label(master, image=self.img)
MainLB.bind('<Button-1>', on_click)
MainLB.pack()
b = tk.Button(root, text="Close", command=root.destroy)
b.pack()
#YuaChanMainFunc.YuaChanAIMainFunc()
root = tk.Tk()
#instance of the class
app = Window(root)
root.resizable(0,0)
root.geometry("310x500+1600+510")
YuaChanMainFunc.YuaChanAIMainFunc()
#Runs the application until we close
root.mainloop()
From what I understand you want "YuaChanMainFunc.YuaChanAIMainFunc()"
to run in the background while UI runs the foreground. For that you can start the "YuaChanMainFunc.YuaChanAIMainFunc()" in a different thread and run UI in the main thread itself. Now you can make "YuaChanMainFunc.YuaChanAIMainFunc()" an infinite loop and it wont block root.mainloop(). Also keep in mind root.mainloop() is also an infinite loop. SO anything you write after that will not execute until you close the program.
import threading
backend_thread = threading.Thread(target=YuaChanMainFunc.YuaChanAIMainFunc, args=())
backend_thread.daemon = True #This will make sure backend thread closes when you close ui
backend_thread.start()
root.mainloop()
So I have a window which is controlled by a thread that runs in the background and changes the GUI when necessary, at some point this thread will be instructed to change window (involving destroying the window it is in and starting up another window), but this never happens because the thread won't stop executing until the window is changed.
Below is a simplified example:
class Window1:
def __init__(...):
self.Master = tk.Tk()
# some code
self.BackgroundUpdates = threading.Thread(target=self.ActiveWindow)
self.BackgroundUpdates.start()
def ActiveWindow(self):
# gets some instruction
if instruction == 'next window':
nextWindow(self)
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
class Window2:
def __init__(...):
self.Master = tk.Tk()
# some code...
def StartWindow(self):
self.Master.mainloop()
def nextWindow(objectWindow):
objectWindow.KillWindow()
# when this function is called it never gets past the line above
nextWindow = Window2()
nextWindow.StartWindow()
application = Window1()
application.StartWindow()
Is there a way that I could rearrange the way I handle the thread so that I don't run into this problem?
a runnable example:
import tkinter as tk
import threading
class MainWindow:
def __init__(self):
self.Master = tk.Tk()
self.Frame = tk.Frame(self.Master, width=100, height=100)
self.Frame.pack()
self.Updates = threading.Thread(target=self.BackgroundUpdates)
self.Updates.start()
def BackgroundUpdates(self):
# imagine instructions to be a really long list with each element being a
# different instruction
instructions = ['instruction1', 'instruction2', 'next window']
while True:
if instructions[0] == 'next window':
ChangeWindow(self)
else:
instructions.remove(instructions[0])
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
class SecondaryWindow:
def __init__(self):
self.Master = tk.Tk()
self.Frame = tk.Frame(self.Master, width=100, height=100)
self.Frame.pack()
def StartWindow(self):
self.Master.mainloop()
def KillWindow(self):
self.Master.destroy()
def ChangeWindow(oldObject):
oldObject.KillWindow()
# the line above will halt the program, since it has to wait on the thread to
# finish before the window can be destroyed, but this function is being called
# from within the thread and so the thread will never stop executing
del oldObject
newObject = SecondaryWindow()
newObject.StartWindow()
window = MainWindow()
window.StartWindow()
I realised that tkinter is singularly threaded, it can be explained more here:
https://stackoverflow.com/a/45803955/11702354
The problem was that I was trying to destroy my window from a different thread to the one it was created in. To solve this problem I had to use the 'after' method from the Tkinter module as well as using a event, this meant that I could control the background stuff (i.e. wait on a specific command from my connected server) and when I needed to change the window I would set the event.
Part of my adapted code can be seen below:
def CheckEvent(self):
if LOBBY_EVENT.is_set():
ChangeWindow(self, 'game')
self.Master.after(5000, self.CheckEvent)
def StartWindow(self):
self.Master.after(5000, self.CheckEvent)
self.Master.after(2000, self.HandleInstruction)
self.Master.mainloop()
So whenever I was calling the StartWindow method for my window, it would check whether the event has been set every 5 seconds, and then every 2 seconds it would go to a separate function 'HandleInstruction' which allowed me to create a response in my GUI (I also used queues to pass information to this function)
I hope this clears up confusion if anyone is to stumble across it!
I want to create a GUI in tkinter that not only executes commands when a button is pressed, but responds to the state of a larger script running in a separate thread.
I have really dug around and tried to find some information on message passing, and I have found some great info on the pickle module, using multiprocessing and its built in tools and also threading, and queuing. I have even dug into David Beazley's lesson on concurrency located here. I just can't get the syntax right on any of those methods.
I have broken down my code into a small functional unit that should launch a little tkinter window like this:
tkinter window
The code below has a "launchGUI" function that launches my tkinter GUI, a "myLoop" function that starts the threads and will also loop to drive my larger program later, right now it just rotates the blink variable. I also have a blinkCheck method in my class that checks the status of the blink variable in the class.
I don't know if I am even putting my message receiver in the right place. In the following example code I am just trying to pass a global variable into the class. I know it is getting into the class, because the blinkCheck() method works even though uncommenting that method crashes the window. However, with the method turned off the label in the GUI never changes. I think the window crashing is the least of my worries, it must be because i have another while loop running.
What is the correct way to get that number in Label to change?
Here is my example code:
import tkinter as tk
from tkinter import Frame, Label
import time
import threading
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.label.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def launchGUI():
root = tk.Tk()
root.title("My Blinker")
app1 = MyClass(root)
app1.mainloop()
def myLoop():
global blink
t1=threading.Thread(target=launchGUI)
t1.daemon = True
t1.start()
print("blink in blinker function is {}".format(blink))
while True:
if blink == 0:
blink = 1
else:
if blink == 1:
blink = 0
time.sleep(2.5)
if __name__=="__main__":
myLoop()
In your description you have mentioned something about involving buttons. I do not see that in your provided snippet. But with buttons it is possible to configure the label, i.e:
from tkinter import Label, Button
blink = 0
class MyClass(tk.Frame):
def __init__(self, master):
self.master = master
super().__init__(self.master)
global blink
self.label = Label(master, text=blink)
self.button = Button(master, text="Button", command=lambda: foo(self.label))
self.label.pack()
self.button.pack()
#self.blinkCheck()
def blinkCheck(self):
global blink
while True:
print("blink in blinkCheck method is = {}".format(blink))
time.sleep(2.5)
def foo(self, label):
label.config(text=blink)
Conventionally, this would be the most simple way to configure a label within an active thread.
If anyone feels like this answer may not be fully correct, please do edit it because I am new to Stack Overflow!
First, the GUI must run in main thread, and must not blocked by a infinite loop. Use after instead. To communicate, use some appropriate object from threading, e.g. Event:
import tkinter as tk
import time
import threading
class MyClass(tk.Frame):
def __init__(self, master, event):
super().__init__(master)
self.master = master
self.event = event
self.label = tk.Label(master, text='')
self.label.pack()
self.after(100, self.blink_check)
def blink_check(self):
self.label['text'] = self.event.is_set()
self.after(100, self.blink_check)
def blink(event):
while True:
event.set()
time.sleep(2.5)
event.clear()
time.sleep(2.5)
def main():
root = tk.Tk()
root.title("My Blinker")
event = threading.Event()
t = threading.Thread(target=blink, args=(event,))
t.daemon = True
t.start()
frame = MyClass(root, event)
root.mainloop()
if __name__=="__main__":
main()
I wrote some code while learning to use Tkinter and when i attempted to thread it, it did not show the window however when it runs just the main loop it does.
import socket,threading,time
from Tkinter import *
class Chat(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.text=Text(self, bg='black', fg='white')
self.text.configure(state=DISABLED)
self.text.configure(state=NORMAL)
self.text.insert(END, 'hello\n'*40)
self.text.configure(state=DISABLED)
self.text.pack()
def main():
root=Tk()
root.configure(background='black')
c=Chat(root)
c.pack()
root.mainloop()
#t=threading.Thread(target=root.mainloop)
#t.start()
if __name__=='__main__':
main()
It seems to be a problem with the text widget but i don't know what is wrong with it. When i remove the insert line, the box appears with trheading but with that line, it does not appear. What is the problem with it?
I think your problem is that you are initialising Tkinter on the mainthread and then invoking the Chat frame (which uses root from the mainthread) on the background thread. I expected this might cause some problems. Without having much knowledge of the internals I decided to test this theory out by writing your code slightly differently. I have re-written your code so the initialisation of root and Chat is on the same thread and it does the trick.
import threading
from Tkinter import *
class Chat(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.text=Text(self, bg='black', fg='white')
self.text.configure(state=DISABLED)
self.text.configure(state=NORMAL)
self.text.insert(END, 'hello\n'*40)
self.text.configure(state=DISABLED)
self.text.pack()
def run():
root=Tk()
root.configure(background='black')
c=Chat(root)
c.pack()
root.mainloop()
def main():
t=threading.Thread(target=run)
t.start()
t.join()
if __name__=='__main__':
main()
Hope that helps.
I'm trying to make a simple program that continually displays and updates a label that displays the CPU usage, while having other unrelated things going on.
I've done enough research to know that threading is likely going to be involved. However, I'm having trouble applying what I've seen in simple examples of threading to what I'm trying to do.
What I currently have going:
import Tkinter
import psutil,time
from PIL import Image, ImageTk
class simpleapp_tk(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.labelVariable = Tkinter.StringVar()
self.label = Tkinter.Label(self,textvariable=self.labelVariable)
self.label.pack()
self.button = Tkinter.Button(self,text='button',command=self.A)
self.button.pack()
def A (self):
G = str(round(psutil.cpu_percent(), 1)) + '%'
print G
self.labelVariable.set(G)
def B (self):
print "hello"
if __name__ == "__main__":
app = simpleapp_tk(None)
app.mainloop()
In the above code I'm basically trying to get command A continually running, while allowing command B to be done when the users presses the button.
You should never attempt to alter a UI element from a thread that isn't the main thread.
What you probably want is after(delay_ms, callback, args). Some information can be over at http://www.pythonware.com/library/tkinter/introduction/x9507-alarm-handlers-and-other.htm.
As a sample, here's a quick script to show a clock (Note: I've never really used Tk).
from Tkinter import *
from time import strftime
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.label_var = StringVar()
self.label = Label(self, textvariable=self.label_var)
self.label.pack()
# Start the loop
self.go()
def go(self):
self.label_var.set(strftime("%H:%M:%S"))
# The callback is only called once, so call it every time
self.after(1000, self.go)
app = App()
mainloop()
You don't need threads for such a simple task. You can simply schedule your task to run every second or so, which can be done with the 'after' method;
First, add this method to your simpleapp_tk class:
def update(self):
G = str(round(psutil.cpu_percent(), 1)) + '%'
self.labelVariable.set(G)
self.after(1000, self.update)
Then, in your initialize method add this call:
self.update()
This will cause the label to be updated to the current cpu value. The update method will then re-schedule itself to run again in one second.