Pass a counter from a python thread to a wxpython gage? - python

I am trying to pass a counter from a python COM thread back into a wxpython tabpanel. Anyone have a solution that would work. Some of my code below:
This class is seperate from my wxPython code but is called out and data sent to this thread.
class Log_COM_thread(Thread):
............
def run(self):
.............
int_log_cnt = int_log_cnt + 1
int_log.TabPanel.gauge.SetValue(int_log_cnt)
if int_log_cnt == 64:
int_log.TabPanel.Int_Log_Status.SetLabel('Extraction Complete')
The seperate module, int_log, this counter value is being sent to is called out like this:
class TabPanel(wx.Panel):
def __init__(self, parent):
self.gauge = wx.Gauge(self, range=72, size=(250, 25))
.....and then more wxpython code.
The error I run into is:
Exception in thread Thread-8:
Traceback (most recent call last):
File "F:\Python27\lib\threading.py", line 551, in __bootstrap_inner
self.run()
File "F:\Documents and Settings\swhite\Desktop\OG GUI Working Jan 13\nbm.py", line 267, in run
int_log.TabPanel.gauge.SetValue(int_log_cnt)
AttributeError: type object 'TabPanel' has no attribute 'gauge'
How would I send my counter back to that gauge in wxpython from my external thread. Any help in direction of how to do this would be extremely helpful.

You cannot call wx methods directly from a separate thread. You need to use one of wxPython thread-safe methods to communicate back to the main GUI thread. They are wx.CallAfter, wx.CallLater and wx.PostEvent.
There is lots of information on threads and wx on the wxPython wiki. I also wrote a tutorial on the subject that might help you.

Related

Starting n threads with same function at diffrent times

I'm currently working with Tkinter to create a GUI for my Script. Among other things there is a function which writes and save some Data in some Files. To visualize the progress for the User i got an label which shows the progress. When i press the button to execute the function
button_download_data = tk.Button(text="Get Data",command=gatherData)
the window freezes and beside the counter for the progress increasaes it isnt shown due to the window is frozen. My solution was to start the function in a new thread using the threading modul.
button_download_data = tk.Button(text="Get Data",command=threading.Thread(target=gatherData).start)
Now the progress is showed but i cant press the button again because i get an error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\WPy64-31050\python-3.10.5.amd64\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\WPy64-31050\python-3.10.5.amd64\lib\threading.py", line 930, in start
raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once
I tried to "kill" the thread when the function is done with raise Exception() and sys.Exit() but it doesn't work at all.
I figuerd out that i can outsource the thread start out of the tkinter button line with:
def gatherDate():
do something
def threadStart():
threading.Thread(target=gatherData).start
button_download_data = tk.Button(text="Get Data",command=threadStart)
and i think it might help to start a new thread on button press and not the same again but i cant imagine how.
You should be able to handle this by creating a separate function to spawn new worker threads as needed - here's a very basic example
import tkinter as tk
from threading import Thread
from time import sleep # for example - simulate a long-running process
def get_data():
print('foo') # do whatever you need to do here
sleep(2.0) # simulate the thread 'working' on something...for example
def spawn_thread():
t = Thread(target=get_data, daemon=True)
t.start()
root = tk.Tk()
button_download_data = tk.Button(root, text='Get Data', command=spawn_thread)
button_download_data.pack()
if __name__ == '__main__':
root.mainloop()
You could simplify spawn_thread a little by skipping the variable assignment t=... and doing Thread(target=get_data, daemon=True).start() instead (as long as you don't need access to the Thread object t for anything else)

Python threading not waiting - could be related to OO design issue

To my mind, I have a fairly simple long-IO operation that could be refined using threading. I've built a DearPyGui GUI interface (not explicitly related to the problem - just background info). A user can load a file via the package's file loader. Some of these files can be quite large (3 GB). Therefore, I'm adding a pop-up window to lock the interface (modal) whilst the file is loading. The above was context, and the problem is not the DearPyGUI.
I'm starting a thread inside a method of a class instance, which in turn calls (via being the thread's target) a further method (from the same object) and then updates an attribute of that object, which is to be interrogated later. For example:
class IOClass:
__init__(self):
self.fileObj = None
def loadFile(self, fileName):
thread = threading.Thread(target=self.threadMethod, args=fileName)
thread.start()
#Load GUI wait-screen
thread.join()
#anything else..EXCEPTION THROWN HERE
print(" ".join(["Version:", self.fileObj.getVersion()]))
def threadMethod(self, fileName):
print(" ".join(["Loading filename", fileName]))
#expensive-basic Python IO operation here
self.fileObj = ...python IO operation here
class GUIClass:
__init__(self):
pass
def startMethod(self):
#this is called by __main__
ioClass = IOClass()
ioClass.loadFile("filename.txt")
Unfortunately, I get this error:
Exception in thread Thread-1 (loadFile):
Traceback (most recent call last):
File "/home/anthony/anaconda3/envs/CPRD-software/lib/python3.10/threading.py", line 1009, in _bootstrap_inner
self.run()
File "/home/anthony/anaconda3/envs/CPRD-software/lib/python3.10/threading.py", line 946, in run
self._target(*self._args, **self._kwargs)
TypeError: AnalysisController.loadFile() takes 2 positional arguments but 25 were given
Traceback (most recent call last):
File "/home/anthony/CPRD-software/GUI/Controllers/AnalysisController.py", line 117, in loadStudySpace
print(" ".join(["Version:", self.fileObj.getVersion()]))
AttributeError: 'NoneType' object has no attribute 'getVersion'
I'm not sure what's going on. The machine should sit there for at least 3 minutes as the data is loaded. But instead, it appears to perform join, but the main thread doesn't wait for the IO thread to load the file, instead attempting to class a method on what was loaded in.
I solved it. In the threading.Thread() do not call the method using self. Instead, pass self in as an argument to the thread method e.g.,
thread = threading.Thread(target=threadMethod, args=(self, fileName))
The target function doesn't change i.e. it remains as so:
def threadMethod(self, fileName):
#expensive-basic Python IO operation here
self.fileObj = ...python IO operation here

How to properly use run_detached() and stop() in pystray?

I'm trying to use pystray without blocking the main thread. Based on the pystray docs we can use the function run_detached() to start without blocking.
I'm using pystray on windows so, apparently I don't need to pass any argument to run_detached() to work.
The first thing I tried is to run this code:
import pystray
from pystray import MenuItem as item
from PIL import Image, ImageTk
def show_window(icon):
print('Test')
def quit_window(icon):
icon.stop()
icon = 'icon.ico'
image=Image.open(icon)
menu=pystray.Menu(item('Show', show_window, default=True), item('Quit', quit_window))
icon=pystray.Icon("name", image, "My System Tray Icon", menu)
icon.run_detached()
But I received this error:
Exception in thread Thread-2:
Traceback (most recent call last):
File "...\lib\threading.py", line 973, in _bootstrap_inner
self.run()
File "...\lib\threading.py", line 910, in run
self._target(*self._args, **self._kwargs)
File "...\lib\site-packages\pystray\_base.py", line 384, in <lambda>
threading.Thread(target=lambda: self.run(setup)).start()
NameError: name 'setup' is not defined
So I tried to bypass this error by changing the line 384 in _base.py removing the setup variable
#threading.Thread(target=lambda: self.run(setup)).start()
threading.Thread(target=lambda: self.run()).start()
The code worked like expected and created the tray icon with the menu buttons working properly.
The problem is when I press "Quit" because the stop() function is not working like when I use icon.run().
The thread appears to keep running and the tray icon stay frozen and the program don't end.
Is there another way to make this work properly?
EDIT:
I found this issue in the official git repository LINK and it appears to be a bug already reported. I want to know if is possible to make a workaround.
Modifying the stop() function further to exit from the thread using os._exit will work if you don't need the calling thread to remain available.

I am having trouble to create a thread and running it through another definition in python

I've been trying to build an application which takes input as text and gives output as speech.
I referred this site to get to know about Text-To-Speech modules in python:
https://pythonprogramminglanguage.com/text-to-speech/
when i ran the program it did the job perfectly but i couldn't use other functions like pause or resume. So i tried to create a new thread for the speech function so that i can alter it speech whenever i want to.
Here is the program:
import threading
import win32com.client as wincl
speak = wincl.Dispatch("SAPI.SpVoice")
t=threading.Event()
def s():
global t
t.set()
data="""This is a story of two tribal Armenian boys who belonged to the
Garoghlanian tribe. """
s=speak.Speak(data)
t1=threading.Thread(target=s)
t1.start
However i am trying to implement the program in GUI using tkinter.
I want the application to read the text when the user is clicking the button.
Since tkinter's button takes command as a function, i made a function for the initialization and starting of the new thread but it is producing an error which i could not interpret and find a solution.
Here is the program thats making error:
import threading
import win32com.client as wincl
speak = wincl.Dispatch("SAPI.SpVoice")
t=threading.Event()
def s():
global t
t.set()
data="""This is a story of two tribal Armenian boys who belonged to the
Garoghlanian tribe. """
s=speak.Speak(data)
def strt():
t1=threading.Thread(target=s)
t1.start()
Here is the error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Application\Python\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Application\Python\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\absan\Desktop\Python\Project-SpeakIt\SI-1.py", line 32, in
speakITheart
s=speak.Speak(data)
File "C:\Users\absan\AppData\Local\Temp\gen_py\3.6\C866CA3A-32F7-11D2-9602-
00C04F8EE628x0x5x4.py", line 2980, in Speak
, 0)
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None,
None, 0, -2147221008), None)
EDIT:
Guys i somehow found a way to fix it when i was writing this post. I just added these lines to the program
import pyttsx3
engine = pyttsx3.init()
i really don't know how or why it fixed the error but it works!!
So this post might be helpful for someone who is facing the same problem.
Cheers!!
I like the solution from the comments. Just include in non-main thread this line to be able to use COM in it (right before the Speak call)
pythoncom.CoInitialize()
self.engine.Speak(self.msg)

How to handle errors in tkinter mainloop?

I have a python program which is scraping web data for a client. tkinter is used for the interface. Outline is:
Window 1 lets the user select what information to scrape.
Window 1 closes
Separate thread is started for the scraper. This thread will in turn spawn more threads to allow multiple pages to be downloaded at once.
Window 2 opens to show download progress (e.g. "downloading client 5 of 17")
User closes Window 2 to end program.
The program will work for the first few hundred pages, but then it starts spitting out the error message:
Traceback (most recent call last):
File "C:\Users\Me\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 248, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
Exception ignored in: <bound method Variable.__del__ of <tkinter.IntVar object at 0x03245510>>
multiple times until all the threads have been stopped. No idea what could be causing this error. The actual code is:
import scraper, threading
import tkinter as tk
from queue import Queue
outputQueue = Queue()
class OutputRedirect(object):
def __init__():
super().__init__()
def write(self, string):
outputQueue.put(string)
def getInformation():
stdout = sys.stdout
sys.stdout = OutputRedirect()
scraper.startThreads()
scraper.startPulling()
sys.stdout = stdout
def updateTextField(window, root):
if not outputQueue.empty():
string = outputQueue.get()
window.textArea.insert("insert", string)
outputQueue.task_done()
root.after(1, updateTextField, window, root)
'''widget/window definitions - not important'''
guiInfo = {"stuff1": [], "stuff2": []}
root = tk.Tk()
window1 = Window1(root, guiInfo)
window1.mainloop()
pullThread = threading.Thread(target=pullClaims,
args=(list(guiInfo["stuff1"]),
list(guiInfo["stuff2"])), daemon=True)
pullThread.start()
root = tk.Tk()
window2 = Window2(root)
root.after(0, updateTextField, window2, root)
window2.mainloop()
The scraper program (which works fine on its own) uses print statements for user feedback. Rather than re-write everything, I just pointed stdout to a queue. The main thread uses the "after" function to check on the queue a few times a second. If there is anything in it then it gets printed to the Text widget on the window.
I've put try/catch just about everywhere in the code, but they haven't caught a thing. I'm convinced the problem is in the mainloop itself, but I can't find any up to date information for how to stick something new in it. Any help would be greatly appreciated.
To handle tkinter errors you do the following
class TkErrorCatcher:
'''
In some cases tkinter will only print the traceback.
Enables the program to catch tkinter errors normally
To use
import tkinter
tkinter.CallWrapper = TkErrorCatcher
'''
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
try:
if self.subst:
args = self.subst(*args)
return self.func(*args)
except SystemExit as msg:
raise SystemExit(msg)
except Exception as err:
raise err
import tkinter
tkinter.CallWrapper = TkErrorCatcher
But in your case please do not do this. This should only ever be done in the case of wanting to hide error messages from your users in production time. As commented above you have a nono's going on.
To spawn multiple windows you can use tkinter.Toplevel
I would recommend in general to read
http://www.tkdocs.com/tutorial/index.html
http://effbot.org/tkinterbook/tkinter-hello-tkinter.htm
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
and for your specific problem of threading in tkinter this blog post nails it. Basically you need to have the tkinter mainloop blocking the programs main thread, then use after calls from other threads to run other code in the mainloop.
http://stupidpythonideas.blogspot.de/2013/10/why-your-gui-app-freezes.html

Categories

Resources