I am developing a Tkinter app with Python. I have a two background operations and one operation with user demand. Here is my sample code:
from threading import Thread
import tkinter as tk
import time
class Controller(object):
def __init__(self, master):
self.master = master
self.btn1 = tk.Button(self.master, text="Start Recording", width=16, height=5, command=lambda: self.start_background_opt())
self.btn1.grid(row=2, column=0)
self.btn3 = tk.Button(self.master, text="Fly", width=16, height=5, command=lambda: self.fly_button())
self.btn3.grid(row=3, column=0)
self.entry = tk.Entry(self.master)
self.entry.grid(row=4, column=0)
self.connect_button_clicked = False
self.thread1 = None
self.thread2 = None
self.thread3 = None
self.flight_completed = False
def background_opt1(self):
while True:
if self.connect_button_clicked:
print("Battery Fetching")
else:
return
def background_opt2(self):
while True:
if self.connect_button_clicked:
print("Signal Fetching")
else:
return
def start_background_opt(self):
if not self.connect_button_clicked:
self.connect_button_clicked = True
self.thread1 = Thread(target=self.background_opt1).start()
self.thread2 = Thread(target=self.background_opt2).start()
else:
self.connect_button_clicked = False
self.thread1 = None
self.thread2 = None
def flight_operation_controller(self):
if self.flight_completed:
self.thread3 = None
def fly_button(self):
self.flight_completed = False
self.thread3 = Thread(target=self.static_sim()).start()
def static_sim(self):
while True:
if not self.flight_completed:
for _ in range(100):
print('Simulating')
time.sleep(0.1)
print("Simulation completed")
self.flight_completed = True
else:
return
if __name__ == '__main__':
root = tk.Tk()
# Set the window size
root.geometry("900x600+0+0")
control = Controller(root)
root.mainloop()
So when user click to "start recording", it starts 2 background operations. They should run as a background. Then when user click to "fly" button, fly operation should be executed.
In order to not blocking my main UI, I have put them in seperate threads.
Actually my all operations are working properly. I have put time.sleep
for replicating my fly operation; but when it runs, it blocks my entire, even though it is running in seperate thread.
Could you please tell me why I am seeing this?
Is my interpretation okey regarding the multithreading in Pyhton tkinter?
Best Regards
Take a look at this line of code:
self.thread3 = Thread(target=self.static_sim()).start()
The above code works exactly the same way as this code;
result = self.static_sim()
self.thread3 = Thread(target=result).start()
See the problem? You are calling your function outside of the thread. Because static_sim() has an infinite loop, it never returns.
When you set the target for Thread, it must be a callable. Change the code to this (note the lack of trailing ()):
self.thread3 = Thread(target=self.static_sim).start()
Related
I have a function that refreshes a string. It has 2 buttons, on and off. If you push on it will print 'Test' multiple times per second.
def blink():
def run():
while (switch):
print('Test')
thread = threading.Thread(target=run)
thread.start()
def on():
global switch
switch = True
blink()
def off():
global switch
switch = False
I also have a toggle function that's is one button that toggles 'True' and 'False'. It displays 'Test' when True.
def toggle():
if button1.config('text')[-1] == 'False':
button1.config(text='True')
Label(root, text='Test').place(x=30, y=0, relheight=1, relwidth=0.7)
else:
button1.config(text='False')
Label(root, text='').place(x=30, y=0, relheight=1, relwidth=0.7)
How do I combine these 2? What I want is that instead of having an on/off button, I have one toggle-able button.
I tried making a class:
class Toggle:
def blink():
def run():
while (switch):
print('Test')
Label(root, text='Test').place(x=30, y=0, relheight=1, relwidth=0.7)
else:
Label(root, text='').place(x=30, y=0, relheight=1, relwidth=0.7)
thread = threading.Thread(target=run)
thread.start()
def toggle():
if button1.config('text')[-1] == 'False':
button1.config(text='True')
global switch
switch = True
blink()
else:
button1.config(text='False')
global switch
switch = False
But I get an error:
File "C:\Users\Daniel\PycharmProjects\stockalarm test\main.py", line 29
global switch
^
SyntaxError: name 'switch' is assigned to before global declaration
I tried looking into it but I cant figure out what to do.
As mentioned in the comments, if you use a class then switch should be an attribute of the class instead of being a global variable.
Additionally, what you did is more CLI oriented and what I suggest below is to use a more tkinter oriented approach.
You want a "toggle-able" button, which is, I think, like a tk.Checkbutton with the indicatoron option set to False.
Instead of using the switch global variable, you can use a tk.BooleanVar connected to the state of the button1 checkbutton.
This depends on what you actually want to do in the run() function but in your example using threading.Thread is an overkill. You can use tkinter .after(<delay in ms>, <callback>) method instead.
I have made the Toggle class inherit from tk.Frame to put both the label and toggle button inside. Here is the code:
import tkinter as tk
class Toggle(tk.Frame):
def __init__(self, master=None, **kw):
tk.Frame.__init__(self, master, **kw)
self.label = tk.Label(self)
self.switch = tk.BooleanVar(self)
self.button1 = tk.Checkbutton(self, text='False', command=self.toggle,
variable=self.switch, indicatoron=False)
self.label.pack()
self.button1.pack()
def blink(self):
if self.switch.get(): # switch is on
print('Test')
self.after(10, self.blink) # re-execute self.blink() in 10 ms
def toggle(self):
if self.switch.get(): # switch on
self.button1.configure(text='True') # set button text to True
self.label.configure(text='Test') # set label text to Test
self.blink() # start blink function
else: # switch off
self.button1.configure(text='False')
self.label.configure(text='')
root = tk.Tk()
Toggle(root).pack()
root.mainloop()
I have a Python program using Tkinter to show a value (var peso inside capturarpeso() function) in realtime.
But the while loop in capturarPeso() doesn't work, the loop only works the first time then the script is "waiting".
If I remove the TK component, it works perfectly. I simplified the script:
import tkinter as tk
from tkinter import *
import threading
import random
def capturarPeso():
global peso
while True:
peso = random.randrange(0, 101, 2)
print (peso)
return(peso)
def capturarPesoHilo():
hilo = threading.Thread(target=capturarPeso, name=None, group=None, args=(), kwargs=None, daemon=True)
hilo.start()
hilo.join()
class ActualizarPeso(Label):
def __init__(self, parent, *args, **kwargs):
Label.__init__(self, parent, *args, **kwargs)
self.tick()
def tick(self):
self.config(text= peso)
self.after(500, self.tick)
capturarPesoHilo()
window = tk.Tk()
window.title('Capturador pesos')
window.resizable(width=False, height=False)
pesoLabel = ActualizarPeso(window, font="Arial 60", fg="red", bg="black", width=8, height= 1)
pesoLabel.grid(row=15, column=0)
window.mainloop()
Any ideas on how to continue? Thank you
The function captuarPeso() has a return statement which will exit the while loop, this is why you only get 1 number printed to the screen.
Removing the return makes it so your program is stuck in that while loop which only prints peso because when you do hilo.join() to a thread it's actually waiting for the thread to exit before continuing, and since we got rid of the return in the first step, the thread never exits and so it's again stuck in a loop. To fix this I changed your while loop to while self.peso != -999: and after calling .mainloop() you set self.peso = -999 which will tell the program: the user has exited the Tkinter interface, exit my loop.
Since you used a class to put some of your tkinter gui in, why not put it all in? Generaly most people would put the entire tkinter interface in a class, I've gone ahead and restructured the program for you but tried to leave as much as the original by itself so you can analyze it and see how it works.
import tkinter as tk
import threading
import random
import time
class ActualizarPeso:
def __init__(self):
self.window = tk.Tk()
self.window.title('Capturador pesos')
self.window.resizable(width=False, height=False)
self.pesoLabel = self.crearLabel()
self.peso = 0
self.tick()
hilo1 = self.capturarPesoHilo()
self.window.mainloop()
self.peso = -999
hilo1.join()
def crearLabel(self):
pesoLabel = tk.Label(self.window, font="Arial 60", fg="red", bg="black", width=8, height=1)
pesoLabel.grid(row=15, column=0)
return pesoLabel
def tick(self):
self.pesoLabel.config(text=self.peso)
self.pesoLabel.after(500, self.tick)
def capturarPeso(self):
while self.peso != -999:
self.peso = random.randrange(0, 101, 2)
print(self.peso)
time.sleep(1)
def capturarPesoHilo(self):
hilo = threading.Thread(target=self.capturarPeso)
hilo.start()
return hilo
ActualizarPeso()
Let me know if you need something explained, and Happy Holidays!
Oke i got the problem days ago and someone helped me with treading but my code was really ugly (im totaly new to coding) now i try to make it better and on an smarter way but now my gui get a frezze all time.
i tryed to do it on the way like my last code but it dosent work this time.
What have i to do this time i cant understand it but want understand it.
some Helpful Tips and tricks ?
Or better ways to do it smart, faster, and more powerfull, or mybae the gui more Beautyfule ?
import time
import sys
from tkinter import *
import threading
root = Tk()
root.geometry("600x400")
global start
start = 1
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
def user_input():
global nameInput
global start
nameInput = textBox.get("1.0","end-1c")
start = 0
if start < 1:
while True:
apex = ApexLegends("APIKey")
player = apex.player(nameInput)
print("Gesamt Kills: " + player.kills + "\n" + 'Gesamt Damage: ' + player.damage)
time.sleep(3)
else:
print("stop")
anleitung=Label(text="Gib einen Spielernamen ein und drücke Start")
anleitung.pack()
textBox=Text(root, height=1, width=30)
textBox.pack()
startButton=Button(root, height=1, width=10, text="Start", command=lambda:startUp())
startButton.pack()
Tkinter isn't designed to be accessed by more than one thread. Here is an excellent answer by one of the guys who has a very deep understainding of how tcl & tk works (the libraries that tkinter depends on), explaining why this is so.
Callback to python function from Tkinter Tcl crashes in windows
This is the first of the two paragraphs in that answer:
Each Tcl interpreter object (i.e., the context that knows how to run a Tcl procedure) can only be safely used from the OS thread that creates it. This is because Tcl doesn't use a global interpreter lock like Python, and instead makes extensive use of thread-specific data to reduce the number of locks required internally. (Well-written Tcl code can take advantage of this to scale up very large on suitable hardware.)
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
This doesn't look right. You're calling user_input() in both the main thread and the child thread. If you only want it to run in the child thread, don't call it that first time.
def startUp():
thr = threading.Thread(target=user_input)
thr.start()
Hi #Trason I've played with your code and I suggest an oo approach.
In the code below I've try to adapt a functional script to your code.
First of all I've use a variable as
self.nameInput = tk.IntVar()
to store the user input on
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
I've use an Entry widget instead of Text but it should be the same.
Furthermore I use a class to manage thread start and stop operation.
Look, I changed your 'start' variable with 'check' because start is a reserved word
in python thread module.
I tried to recreate the functionality of your code.
Try to import and use your ApexLegends.
import tkinter as tk
import threading
import queue
import datetime
import time
class MyThread(threading.Thread):
def __init__(self, queue,nameInput):
threading.Thread.__init__(self)
self.queue = queue
self.nameInput = nameInput
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
# apex = ApexLegends("APIKey")
#player = apex.player(self.nameInput.get())
x = "Gesamt Kills: " + "player.kills" + "\n" + 'Gesamt Damage: ' + "player.damage"+ "\n"
s = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = "{} datetime: {} nameInput {}".format(x,s,self.nameInput.get())
time.sleep(3)
self.queue.put(msg)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.nameInput = tk.IntVar()
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Label(w, text = "Gib einen Spielernamen ein und drücke Start").pack()
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
w.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w = tk.Frame()
tk.Button(w, text="Start", command=self.startUp).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def startUp(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue,self.nameInput)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
msg = "%s"%(ret)
print(msg)
except queue.Empty:
pass
def on_close(self):
if(threading.active_count()!=0):
if self.my_thread is not None:
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
The following code is runnable, you can just copy/paste:
from tkinter import *
import multiprocessing
startingWin = Tk()
def createClientsWin():
def startProcess():
clientsWin = Tk()
label = Label(clientsWin, text="Nothing to show")
label.grid()
clientsWin.mainloop()
if __name__ == "__main__":
p = multiprocessing.Process(target=startProcess)
p.start()
button = Button(startingWin, text="create clients", command=lambda: createClientsWin())
button.grid()
startingWin.mainloop()
So I simply want to create a completely separated Tk() window using multiprocessing. When I click on the create button, I just get the original window (not the intended one) and it gives me this error:
AttributeError: Can't pickle local object 'createClientsWin.<locals>.startProcess'
*Could someone explain how to start a separate new Tk() window using multiprocessing? *
Update: Not A Duplicate
Even if I follow the solution provided in the possible duplicate question, that doesn't help solving my question. Simply because, Tkinter is being used in my case. The modified code:
def createClientsWin():
clientsWin = Tk()
label = Label(clientsWin, text="Nothing to show")
label.grid()
clientsWin.mainloop()
def createClientsWinProcess():
if __name__ == "__main__":
p = multiprocessing.Process(target=createClientsWin)
p.start()
startingWin = Tk()
button = Button(startingWin, text="create clients", command=lambda: createClientsWinProcess())
button.grid()
startingWin.mainloop()
Function in global scope should be used for multiprocess target function, so the startProcess() should be moved into global scope.
Also the checking of if __name__ == "__main__" inside startProcess() will cause the code inside the if block not being executed.
Finally the creation of startingWin should be put inside if __name__ == "__main__" block, otherwise every process started will create the startingWin.
Below is the proposed changes to solve the above issues:
from tkinter import *
import multiprocessing
def startProcess():
clientsWin = Tk()
label = Label(clientsWin, text="Nothing to show")
label.grid()
clientsWin.mainloop()
def createClientsWin():
p = multiprocessing.Process(target=startProcess)
p.start()
if __name__ == '__main__':
startingWin = Tk()
button = Button(startingWin, text="create clients", command=createClientsWin)
button.grid()
startingWin.mainloop()
It is easier to use classes when using multiprocessing with tkinter. Try the following code:
import tkinter as Tk
import multiprocessing as mp
class A:
def __init__(self, master):
self.master = master
label = Tk.Label(self.master, text = 'A')
label.pack()
root_b = Tk.Toplevel()
GUI_B = B(root_b)
mp.Process(target = GUI_B.mainloop())
def mainloop(self):
self.master.mainloop()
class B:
def __init__(self, master):
self.master = master
label = Tk.Label(self.master, text = 'B')
label.pack()
def mainloop(self):
self.master.mainloop()
if __name__=='__main__':
root = Tk.Tk()
GUI_A = A(root)
mp.Process(target = GUI_A.mainloop())
I am trying to make a program that deals with a heavy computational load in an iterative fashion. I want to be able to interrupt the program and restart.
I have built a simple GUI using Tkinter to allow the iterative loop to be interrupted using an "after_cancel". This works fine in testing with a low computational load, but under a high computational load, the program does not respond to the button click that is meant to do the "after_cancel".
For simplicity's sake, I have replaced the program with fibonacci. The effect is the same (after a while).
How do I design this so that the "Stop" button remains responsive?
Here's the code:
from tkinter import *
import time
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
class App:
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack(expand=1, fill=BOTH)
self.master = master
self.quitter = Button(self.frame, text="Quit", fg="red", command=self.master.destroy)
self.quitter.pack(side=LEFT)
self.starter = Button(self.frame, text="Start", command=self.start)
self.starter.pack(side=LEFT)
self.stopper = Button(self.frame, text="Stop", command=self.stop)
self.stopper.pack(side=LEFT)
self.counter = 0
def start(self):
print((fibonacci(self.counter)))
self.counter += 1
self.afterid = self.master.after(1, self.start)
def stop(self):
print('Stop')
self.master.after_cancel(self.afterid)
root = Tk()
app = App(root)
root.mainloop()
Your fibonacci function will run on your GUI thread if you call it from a widget callback. To avoid freezing your GUI, run fibonacci on a separate thread and set a flag to stop its execution:
from tkinter import *
import threading
import time
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
class App:
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack(expand=1, fill=BOTH)
self.master = master
self.quitter = Button(self.frame, text="Quit", fg="red", command=self.master.destroy)
self.quitter.pack(side=LEFT)
self.starter = Button(self.frame, text="Start", command=self.start)
self.starter.pack(side=LEFT)
self.stopper = Button(self.frame, text="Stop", command=self.stop)
self.stopper.pack(side=LEFT)
self.counter = 0
self.running = False
def start(self):
self.running = True
threading.Thread(target=self.start_fibonacci).start()
def start_fibonacci(self):
while self.running:
print(fibonacci(self.counter))
self.counter += 1
time.sleep(1)
def stop(self):
self.running = False
print('Stop')
if __name__ == "__main__":
root = Tk()
root.title("Fibonacci")
app = App(root)
root.mainloop()
In the mainprog function, you compute the fibonacci once, and then register an alarm callback as self.mainprog, that is called after 1 ms. In other words, there is an interval time between each call to fibonacci function. And your stopprog button could only respond your in the interval time, when the control right is idle.
Under a high computational load, the execution right is always retained by your task (in ONE call of fibonacci function). As a result, no button could respond you.
You would better implement your tast(such as fibonacci computation) in a multi-thread or multi-process manner to avoid this issue.