I am trying to manipulate some data in a thread from the main function. The issue I am facing about modifying some of the variables which are part of the function which is running on a thread.
So I am trying to run a Tkinter based GUI loop in a thread to ensure it is always running. And want to modify some of the label corresponding to the status in the main function execution. I am facing an issue where it is unable to locate the label variables in the main loop as it is part of the function running on the thread.
Below is a simplified psuedo code for that approach. Please suggest if this a correct way to the above task or is there is any better and efficient way.
import threading
def thread_func():
i = 0
while True:
print('i from thread: ', i)
if __name__ == '__main__':
t = threading.Thread(target=thread_func)
t.start()
while True:
i += 1
Actual scaled down simplified code
import threading
import tkinter as tk
def gui():
window = tk.Tk()
label = tk.Label(text='On')
label.pack()
window.mainloop()
if __name__ == '__main__':
t = threading.Thread(target=gui)
t.start()
while True:
label['text'] = 'Active'
Error:
Traceback (most recent call last):
File "test.py", line 17, in <module>
label['text'] = 'Active'
NameError: name 'label' is not defined
Is there a better way to keep the tkinter gui always on and perform some task in the loop?
Using class and threading:
import tkinter
class Test(tkinter.Label):
x = False
def __init__(self):
super().__init__()
self.pack()
self['text'] = 'On'
def gui(self):
if not self.x:
self['text'] = 'Active'
self.mainloop()
else:
self.mainloop()
if __name__ == '__main__':
label = Test()
while isinstance(label, Test):
t = threading.Thread(target=label.gui())
t.start()
When you write code of label there then you will get error because when program start it starts from creating thread, that thread will only end when tkinter window is close and same for previous thread_fuc code. And you wrote the label code after tkinter window is closed.
The above issue will be solved by doing this :
import threading
def thread_func():
while True:
print('i from thread: ', i)
def tt():
global i
while True:
i += 1
if __name__ == '__main__':
i=0
t = threading.Thread(target=thread_func)
t.start()
yt = threading.Thread(target=tt)
yt.start()
Making i global and running 2 function parallely. We have to global because we can't use the variable of one function to another. And additionally we are running 2 function in 2 thread.
And for your tkinter file as #TheLizzard suggest, you can use .after insted of using thread in tkinter if you want to change content constantly/want to use loop.
Here's the basic example how you can implement it:
import random
import tkinter as tk
app = tk.Tk()
app.geometry("200x220")
label = tk.Label(app, text="0")
label.pack()
def change(b=0):
if b < 30:
a = random.randrange(1, 7, 1)
label.config(text=a)
app.after(100, change, b+1)
b1 = tk.Button(app, text="Get New Number", command=change)
b1.pack()
app.mainloop()
For more explanation about it you may visit here.
Related
I want to stop a loop when I press a button. If I do it all in the same files I don´t have a problem but when I put the loop as a routine from another file It doesn´t work and I don´t know why. I hope you can help me! Thanks!!
First of all, this is subroutine.py, the file where is the loop allocated
subroutine.py
def routine (pressed):
while True:
print ("hello")
if pressed== 1:
break #Break while loop when stop = 1
And this is the "main" file
import tkinter as tk
import threading
import subroutine
pressed = 0
def Start_Scan():
global pressed
pressed = 0
# Create and launch a thread
t = threading.Thread (target = subroutine.routine (pressed))
t.start()
def Stop():
global pressed
pressed = 1
#main
master = tk.Tk()
app = tk.Frame(master)
app.grid()
start = tk.Button(app, text="Start Scan",command=Start_Scan)
stop = tk.Button(app, text="Stop",command=Stop)
start.grid()
stop.grid()
app.mainloop()
I use thread and a global variable, I thought about not using a global variable but I didn't find a way for the two threads to communicate that was not blocking. Thanks!!
You can use threading.Events to pass a flag between the main thread and the worker thread
# main.py
stop_event = threading.Event()
def Start_Scan():
# Create and launch a thread
t = threading.Thread (
target=subroutine.routine,
args=(stop_event,) # pass the Event object to the worker target
)
t.start()
def Stop():
stop_event.set()
Just update your subroutine to keep an eye on that event
# subroutine.py
def routine(stop_event):
while not stop_event.is_set(): # loop until the Event is 'set'
print ("hello")
is there a way to automatically run a function besides my main.py every hour. Using while loop, it does not work, because my main.py is a tkinter class, which after its initialization must not remain "trapped" in a while loop.
This is a initialization part of my main.py:
class ChatApp:
index = 0
def __init__(self):
self.create_index()
self.window = Tk()
self._setup_main_window()
self.start_model()
...
if __name__ == "__main__":
app = ChatApp()
app.run()
You can run a separate thread which waits an hour between executing.
Although it depends on what you want the task to be.
import threading
import time
def func():
while True:
#do something
time.sleep(3600)
t1 = threading.thread(target=func)
class ChatApp:
index = 0
def __init__(self):
self.create_index()
self.window = Tk()
self._setup_main_window()
self.start_model()
...
if __name__ == "__main__":
t1.start()
app = ChatApp()
app.run()
I am trying to display a count variable from a background task in the main task which is my tkinter GUI. Why? I want to display that the long time taking background task is performing and later use this count variable to visualize it with a progress bar.
My problem is, that even when using a Queue, I am not able to display the count variable. Maybe I've got problems in understanding python and its behaviour with objects and/or threads.
import threading
import time
import Queue
import Tkinter as Tk
import Tkconstants as TkConst
from ScrolledText import ScrolledText
from tkFont import Font
import loop_simulation as loop
def on_after_elapsed():
while True:
try:
v = dataQ.get(timeout=0.1)
except:
break
scrText.insert(TkConst.END, str(v)) # "value=%d\n" % v
scrText.see(TkConst.END)
scrText.update()
top.after(100, on_after_elapsed)
def thread_proc1():
x = -1
dataQ.put(x)
x = loop.loop_simulation().start_counting()
# th_proc = threading.Thread(target=x.start_counting())
# th_proc.start()
for i in range(5):
for j in range(20):
dataQ.put(x.get_i())
time.sleep(0.1)
# x += 1
time.sleep(0.5)
dataQ.put(x.get_i())
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
f = Font(family='Courier New', size=12)
scrText = ScrolledText(master=top, height=20, width=120, font=f)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True)
th = threading.Thread(target=thread_proc1)
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
th.join()
In thread_proc1() I want to get the value of the counter of background task. This is the background task:
import time
class loop_simulation:
def __init__(self):
self.j = 0
# self.start_counting()
def start_counting(self):
for i in range(0, 1000000):
self.j = i
time.sleep(0.5)
def get_i(self):
return str(self.j)
The reason the count variable isn't being displayed is due to the
x = loop.loop_simulation().start_counting()
statement in thread_proc1(). This creates a loop_simulation instance and calls its start_counting() method. However, other than already inserting a -1 into the dataQ, thread_proc1() doesn't do anything else until start_counting() returns, which won't be for a long time (500K seconds).
Meanwhile, the rest of your script is running and displaying only that initial -1 that was put in.
Also note that if start_counting() ever did return, its value of None is going to be assigned to x which later code attempts to use with: x.get_i().
Below is reworking of your code that fixes these issues and also follows the PEP 8 - Style Guide for Python Code more closely. To avoid the main problem of calling start_counting(), I changed your loop_simulation class into a subclass of threading.Thread and renamed it LoopSimulation, and create an instance of it in thread_proc1, so there are now two background threads in addition to the main one handling the tkinter-based GUI.
import loop_simulation as loop
from ScrolledText import ScrolledText
import threading
import Tkinter as Tk
import Tkconstants as TkConst
from tkFont import Font
import time
import Queue
def on_after_elapsed():
# removes all data currently in the queue and displays it in the text box
while True:
try:
v = dataQ.get_nowait()
scrText.insert(TkConst.END, str(v)+'\n')
scrText.see(TkConst.END)
except Queue.Empty:
top.after(100, on_after_elapsed)
break
def thread_proc1():
dataQ.put(-1)
ls = loop.LoopSimulation() # slow background task Thread
ls.start_counting()
while ls.is_alive(): # background task still running?
for i in range(5):
for j in range(20):
dataQ.put(ls.get_i())
time.sleep(0.1)
time.sleep(0.5)
dataQ.put('background task finished')
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
font = Font(family='Courier New', size=12)
scrText = ScrolledText(top, height=20, width=120, font=font)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15,
expand=TkConst.YES)
th = threading.Thread(target=thread_proc1)
th.daemon = True # OK for main to exit even if thread is still running
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
loop_simulation.py module:
import threading
import time
class LoopSimulation(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True # OK for main to exit even if instance still running
self.lock = threading.Lock()
self.j = 0
start_counting = threading.Thread.start # an alias for starting thread
def run(self):
for i in range(1000000):
with self.lock:
self.j = i
time.sleep(0.5)
def get_i(self):
with self.lock:
return self.j
I have a Tkinter GUI application that I need to enter text in. I cannot assume that the application will have focus, so I implemented pyHook, keylogger-style.
When the GUI window does not have focus, text entry works just fine and the StringVar updates correctly. When the GUI window does have focus and I try to enter text, the whole thing crashes.
i.e., if I click on the console window or anything else after launching the program, text entry works. If I try entering text immediately (the GUI starts with focus), or I refocus the window at any point and enter text, it crashes.
What's going on?
Below is a minimal complete verifiable example to demonstrate what I mean:
from Tkinter import *
import threading
import time
try:
import pythoncom, pyHook
except ImportError:
print 'The pythoncom or pyHook modules are not installed.'
# main gui box
class TestingGUI:
def __init__(self, root):
self.root = root
self.root.title('TestingGUI')
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
def ButtonPress(self, scancode, ascii):
self.search.set(ascii)
root = Tk()
TestingGUI = TestingGUI(root)
def keypressed(event):
key = chr(event.Ascii)
threading.Thread(target=TestingGUI.ButtonPress, args=(event.ScanCode,key)).start()
return True
def startlogger():
obj = pyHook.HookManager()
obj.KeyDown = keypressed
obj.HookKeyboard()
pythoncom.PumpMessages()
# need this to run at the same time
logger = threading.Thread(target=startlogger)
# quits on main program exit
logger.daemon = True
logger.start()
# main gui loop
root.mainloop()
I modified the source code given in the question (and the other one) so that the pyHook
related callback function sends keyboard event related data to a
queue. The way the GUI object is notified about the event may look
needlessly complicated. Trying to call root.event_generate in
keypressed seemed to hang. Also the set method of
threading.Event seemed to cause trouble when called in
keypressed.
The context where keypressed is called, is probably behind the
trouble.
from Tkinter import *
import threading
import pythoncom, pyHook
from multiprocessing import Pipe
import Queue
import functools
class TestingGUI:
def __init__(self, root, queue, quitfun):
self.root = root
self.root.title('TestingGUI')
self.queue = queue
self.quitfun = quitfun
self.button = Button(root, text="Withdraw", command=self.hide)
self.button.grid()
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
self.root.bind('<<pyHookKeyDown>>', self.on_pyhook)
self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
self.hiding = False
def hide(self):
if not self.hiding:
print 'hiding'
self.root.withdraw()
# instead of time.sleep + self.root.deiconify()
self.root.after(2000, self.unhide)
self.hiding = True
def unhide(self):
self.root.deiconify()
self.hiding = False
def on_quit(self):
self.quitfun()
self.root.destroy()
def on_pyhook(self, event):
if not queue.empty():
scancode, ascii = queue.get()
print scancode, ascii
if scancode == 82:
self.hide()
self.search.set(ascii)
root = Tk()
pread, pwrite = Pipe(duplex=False)
queue = Queue.Queue()
def quitfun():
pwrite.send('quit')
TestingGUI = TestingGUI(root, queue, quitfun)
def hook_loop(root, pipe):
while 1:
msg = pipe.recv()
if type(msg) is str and msg == 'quit':
print 'exiting hook_loop'
break
root.event_generate('<<pyHookKeyDown>>', when='tail')
# functools.partial puts arguments in this order
def keypressed(pipe, queue, event):
queue.put((event.ScanCode, chr(event.Ascii)))
pipe.send(1)
return True
t = threading.Thread(target=hook_loop, args=(root, pread))
t.start()
hm = pyHook.HookManager()
hm.HookKeyboard()
hm.KeyDown = functools.partial(keypressed, pwrite, queue)
try:
root.mainloop()
except KeyboardInterrupt:
quit_event.set()
I have the following code:
import time, os
from tkinter import *
class Chat():
def sPrint():
s=0
while s < 11:
s+=1
print (s, 'Sec')
time.sleep(1)
if s ==5:
print ("5..")
myapp.OpenWindow1()
if s ==10:
print ("10..")
myapp.OpenWindow2()
class App(Frame):
def ConnectButton1(self):
self.con1 = Button(self)
self.con1["text"] = "Connect1",
self.con1["command"] = lambda: Chat.sPrint()
self.con1.grid(row=0,column=2,padx=5, pady=3,sticky=W)
def OpenWindow1(self):
win1 = Toplevel()
def OpenWindow2(self):
win2 = Toplevel()
myapp = App()
if __name__ == "__main__":
myapp.ConnectButton1()
myapp.pack()
myapp.mainloop()
Problem is - the "print" works perfect every 5 seconds from inside the loop,
but the Toplevel functions runs only when the "while" end (and then shows two
toplevel windows at the same time)
Same happen when i use the
threading.Timer(1, sPrint1).start()
for running a function in a loop.. i can't load a new tkinter buttons/labels
while the function is in a loop.
Any suggestions?