wxpython postevent from coroutine to a parent window (modal) - python-3.x

I got a coroutine running to catch events sent by a footswitch device. This coroutine is launched from the main window as following.
class FootswitchMonitor(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title,
size=(350, 150))
self.InitUI()
StartCoroutine(self.footswitch_callback, self)
self.Bind(EVT_FOOTSWITCH, self.pedal)
def on_footswitch(self, evt):
print(f"eid: {evt.GetId()} code: {evt.code} ")
async def footswitch_callback(self):
device = get_footswitch_device()
device.grab()
key_pressed = None
async for ev in device.async_read_loop():
if ev.type == 1 and ev.value == 1: # only key events and key_down
if key_pressed != ev.code:
event = footswitch_event(code=ev.code)
wx.PostEvent(self, event)
key_pressed = ev.code
else:
key_pressed = None
def setup_footswitch(self, event):
cDialog = ConfigDialog(None)
cDialog.ShowModal()
cDialog.Destroy()
In order to setup the device I use a ConfigDialog dialog box where I'd like to bind the same EVT_FOOTSWITCH.
class ConfigDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.InitUI()
self.SetSize(350, 450)
self.SetTitle("Configuration")
self.Bind(EVT_FOOTSWITCH, self.footswitch_setup_callback)
And this is where it hurts. The main coroutine has FootswitchMonitor frame as target: wx.PostEvent(self, event). So the ConfigDialog windows won't get it.
I cannot run another coroutine with this device in the parent windows because it is grabed, which is locked by the main coroutine.
So my question is how can I catch the EVT_FOOTSWITCH in ConfigDialog a parent windows. Is it possible to post the event globaly? Is it possible to stop the main coroutine to run anotheone in the modal window?

I have something similar where I run reading the footpedal in a separate thread.
If the user wishes to reconfigure the pedal settings, I simply stop the thread, for the duration of the reconfiguration, then restart it.
Stopping the thread also ungrabs the device.
I do insist the program is restarted, as I only read the configuration of the footpedal's settings at program start. I found it less complicated this way.
Your other option, if you wish to attack it from a different angle, is to Unbind the event i.e. self.Unbind(EVT_FOOTSWITCH) and ungrab the device.
Then Bind it to your reconfiguration routine, which Unbind's when it finishes and finally rebind the event back to the original, whilst regrabbing the device, when you return to the main app.
My rather vast code can be perused at https://sourceforge.net/projects/footswitch2 , if you need further clues.
When browsing the file fs2.py search for PedalThread

Related

Dart: Store heavy object in an isolate and access its method from main isolate without reinstatiating it

is it possible in Dart to instantiate a class in an isolate, and then send message to this isolate to receive a return value from its methods (instead of spawning a new isolate and re instantiate the same class every time)? I have a class with a long initialization, and heavy methods. I want to initialize it once and then access its methods without compromising the performance of the main isolate.
Edit: I mistakenly answered this question thinking python rather than dart. snakes on the brain / snakes on a plane
I am not familiar with dart programming, but it would seem the concurrency model has a lot of similarities (isolated memory, message passing, etc..). I was able to find an example of 2 way message passing with a dart isolate. There's a little difference in how it gets set-up, and the streams are a bit simpler than python Queue's, but in general the idea is the same.
Basically:
Create a port to receive data from the isolate
Create the isolate passing it the port it will send data back on
Within the isolate, create the port it will listen on, and send the other end of it back to main (so main can send messages)
Determine and implement a simple messaging protocol for remote method call on an object contained within the isolate.
This is basically duplicating what a multiprocessing.Manager class does, however it can be helpful to have a simplified example of how it can work:
from multiprocessing import Process, Lock, Queue
from time import sleep
class HeavyObject:
def __init__(self, x):
self._x = x
sleep(5) #heavy init
def heavy_method(self, y):
sleep(.2) #medium weight method
return self._x + y
def HO_server(in_q, out_q):
ho = HeavyObject(5)
#msg format for remote method call: ("method_name", (arg1, arg2, ...), {"kwarg1": 1, "kwarg2": 2, ...})
#pass None to exit worker cleanly
for msg in iter(in_q.get, None): #get a remote call message from the queue
out_q.put(getattr(ho, msg[0])(*msg[1], **msg[2])) #call the method with the args, and put the result back on the queue
class RMC_helper: #remote method caller for convienience
def __init__(self, in_queue, out_queue, lock):
self.in_q = in_queue
self.out_q = out_queue
self.l = lock
self.method = None
def __call__(self, *args, **kwargs):
if self.method is None:
raise Exception("no method to call")
with self.l: #isolate access to queue so results don't pile up and get popped off in possibly wrong order
print("put to queue: ", (self.method, args, kwargs))
self.in_q.put((self.method, args, kwargs))
return self.out_q.get()
def __getattr__(self, name):
if not name.startswith("__"):
self.method = name
return self
else:
super().__getattr__(name)
def child_worker(remote):
print("child", remote.heavy_method(5)) #prints 10
sleep(3) #child works on something else
print("child", remote.heavy_method(2)) #prints 7
if __name__ == "__main__":
in_queue = Queue()
out_queue = Queue()
lock = Lock() #lock is used as to not confuse which reply goes to which request
remote = RMC_helper(in_queue, out_queue, lock)
Server = Process(target=HO_server, args=(in_queue, out_queue))
Server.start()
Worker = Process(target=child_worker, args=(remote, ))
Worker.start()
print("main", remote.heavy_method(3)) #this will *probably* start first due to startup time of child
Worker.join()
with lock:
in_queue.put(None)
Server.join()
print("done")

Stop all threads using threading.Event()

I am creating a multiple threads to execute a function that generates PDF. This process takes a lot of time, so the user has a choice to cancel the execution.
To stop a thread, I know that I can use threading.Event() to check if it will be set. However, the process of the function I am executing in my event loop is straight forward/linear (There is no loop to check regularly if the Event is set).
--threading class--
def execute_function(self, function_to_execute, total_executions, execution_time, controller):
self.event = threading.Event()
self.event_list.append(self.event)
self.loop = asyncio.get_event_loop()
self.future = self.loop.run_in_executor(self._executor, function_to_execute, self.event, total_executions,
execution_time, controller)
def stop_executor(self):
for event in self.event_list:
event.set()
self.event = None
if self._executor:
self._executor.shutdown(wait=False)
def *function_to_execute*(self, event, total_execution, seconds=SECONDS_DEFAULT, controller=None):
self.event = event
self.controller = controller
...
My problem is that, I can't implement the Event to interrupt the threads without looping to regularly check the Event.
Is there any other way around to stop all those threads?
Or if I will still use the Event, is there any other logic to implement it?
Thanks in advance!

kivy- need to wait until the thread is finish

i have in my app thread that append something to list and then i want to print it in other screen, but, the program run the thread after the print and it is give me error that there is no thing in my list. i am need to stop the program until the thread done, how can i do this? i tried to use .join() but it is didnt work... thanks for help
my app:
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text='username'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='Password'))
self.password = TextInput(multiline=False, password=True)
self.add_widget(self.password)
self.submit_button = Button(text='sumbit',size_hint=(.5,
.25),font_size=20)
self.submit_button.bind(on_press=self.submit_username)
self.add_widget(self.submit_button)
def submit_username(self, *args):
self.msg=threading.Thread(target=send_data(self.username.text))
self.msg.start()
self.msg.join()
sm.current = 'searchi'
sm.transition.direction = 'left'
def send_data(name):
my_socket = socket.socket()
my_socket.connect(('127.0.0.1', 8093))
my_socket.send(name.encode('utf-8'))
name,address = my_socket.recvfrom(1024)
msg = name.decode('utf-8')
alist.append(msg)
my_socket.close()
# Declare both screens
class Searchi(Screen):
def __init__(self, **kwargs):
super(Searchi, self).__init__(**kwargs)
self.add_widget(Label(text=alist[0]))
the list have to "msg" if i am delete the line"self.add_widget(Label(text=alist[0]))" then it is no problem in the recv line. i just need to wait until the thread is finish.
Why bother with a Thread if you are going to wait for it anyway? You could just call send_data(self.username.text) and be done with it.
But doing this is usually bad practice (doing blocking calls without a thread, or waiting for the thread to finish in a blocking way, which is equivalent), what you want, instead of waiting for the task to be done before proceeding, is to react to the task being done, that is, at the end of your thread, do something that will allow your app to proceed.
you could have a callback to move your user to the new screen, called at the end of the thread.
def submit_username(self, *args):
def callback():
sm.current = 'searchi'
sm.transition.direction = 'left'
threading.Thread(target=send_data(self.username.text, callback).start()
def send_data(name, callback):
my_socket = socket.socket()
my_socket.connect(('127.0.0.1', 8093))
my_socket.send(name.encode('utf-8'))
name,address = my_socket.recvfrom(1024)
msg = name.decode('utf-8')
alist.append(msg)
my_socket.close()
callback()
if you want your user to touch anything while the action happens, i would advise putting a Popup with auto_dismiss=False, and a content indicating that data is being processed, and to close it in the callback.

Gifs shown inside a label doesn't update itself regulary in Pyqt5

I have a loading widget that consists of two labels, one is the status label and the other one is the label that the animated gif will be shown in. If I call show() method before heavy stuff gets processed, the gif at the loading widget doesn't update itself at all. There's nothing wrong with the gif btw(looping problems etc.). The main code(caller) looks like this:
self.loadingwidget = LoadingWidgetForm()
self.setCentralWidget(self.loadingwidget)
self.loadingwidget.show()
...
...
heavy stuff
...
...
self.loadingwidget.hide()
The widget class:
class LoadingWidgetForm(QWidget, LoadingWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
pince_directory = SysUtils.get_current_script_directory() # returns current working directory
self.movie = QMovie(pince_directory + "/media/loading_widget_gondola.gif", QByteArray())
self.label_Animated.setMovie(self.movie)
self.movie.setScaledSize(QSize(50, 50))
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie.start()
self.not_finished=True
self.update_thread = Thread(target=self.update_widget)
self.update_thread.daemon = True
def showEvent(self, QShowEvent):
QApplication.processEvents()
self.update_thread.start()
def hideEvent(self, QHideEvent):
self.not_finished = False
def update_widget(self):
while self.not_finished:
QApplication.processEvents()
As you see I tried to create a seperate thread to avoid workload but it didn't make any difference. Then I tried my luck with the QThread class by overriding the run() method but it also didn't work. But executing QApplication.processEvents() method inside of the heavy stuff works well. I also think I shouldn't be using seperate threads, I feel like there should be a more elegant way to do this. The widget looks like this btw:
Processing...
Full version of the gif:
Thanks in advance! Have a good day.
Edit: I can't move the heavy stuff to a different thread due to bugs in pexpect. Pexpect's spawn() method requires spawned object and any operations related with the spawned object to be in the same thread. I don't want to change the working flow of the whole program
In order to update GUI animations, the main Qt loop (located in the main GUI thread) has to be running and processing events. The Qt event loop can only process a single event at a time, however because handling these events typically takes a very short time control is returned rapidly to the loop. This allows the GUI updates (repaints, including animation etc.) to appear smooth.
A common example is having a button to initiate loading of a file. The button press creates an event which is handled, and passed off to your code (either via events directly, or via signals). Now the main thread is in your long-running code, and the event loop is stalled — and will stay stalled until the long-running job (e.g. file load) is complete.
You're correct that you can solve this with threads, but you've gone about it backwards. You want to put your long-running code in a thread (not your call to processEvents). In fact, calling (or interacting with) the GUI from another thread is a recipe for a crash.
The simplest way to work with threads is to use QRunner and QThreadPool. This allows for multiple execution threads. The following wall of code gives you a custom Worker class that makes it simple to handle this. I normally put this in a file threads.py to keep it out of the way:
import sys
from PyQt5.QtCore import QObject, QRunnable
class WorkerSignals(QObject):
'''
Defines the signals available from a running worker thread.
error
`tuple` (exctype, value, traceback.format_exc() )
result
`dict` data returned from processing
'''
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(dict)
class Worker(QRunnable):
'''
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
'''
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#pyqtSlot()
def run(self):
'''
Initialise the runner function with passed args, kwargs.
'''
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
To use the above, you need a QThreadPool to handle the threads. You only need to create this once, for example during application initialisation.
threadpool = QThreadPool()
Now, create a worker by passing in the Python function to execute:
from .threads import Worker # our custom worker Class
worker = Worker(fn=<Python function>) # create a Worker object
Now attach signals to get back the result, or be notified of an error:
worker.signals.error.connect(<Python function error handler>)
worker.signals.result.connect(<Python function result handler>)
Then, to execute this Worker, you can just pass it to the QThreadPool.
threadpool.start(worker)
Everything will take care of itself, with the result of the work returned to the connected signal... and the main GUI loop will be free to do it's thing!

QDoppelganger (clone a QWidget into another container)

Overflowers: We have a complex dock widget with several complex, aggregate QWidgets in it.
I need to allow users to minimize the dock widget down to a ribbon (as a QToolBar), containing only the most important widgets. But because these widgets are rather complex, I would rather not reproduce them in code, and painstakingly transfer their custom values back and forth. Clicking one copy of a widget should behave exactly the same as clicking another.
So it seems I would like to invent a QDoppelganger widget, which delegates input and render events back and forth, to make a functioning copy of another control:
class QDoppelganger(QtGui.QWidget):
def __init__(self, dupeMe, parent):
super(QDoppelganger, self).__init__(parent)
self.dupeMe = dupeMe
formerResizer = self.dupeMe.resizeEvent
def resizeMe(qResizeEvent):
self.setFixedSize(self.dupeMe.size())
return formerResizer(qResizeEvent)
self.dupeMe.resizeEvent = resizeMe
formerPainter = self.dupeMe.paintEvent
def paintMe(qPaintEvent):
self.update()
return formerPainter(qPaintEvent)
self.dupeMe.paintEvent = paintMe
def mouseMoveEvent(self, qMouseEvent): return self.dupeMe.mouseMoveEvent(qMouseEvent)
def mousePressEvent(self, qMouseEvent): return self.dupeMe.mousePressEvent(qMouseEvent)
def mouseReleaseEvent(self, qMouseEvent): return self.dupeMe.mouseReleaseEvent(qMouseEvent)
def paintEvent(self, qPaintEvent):
pix = QtGui.QPixmap(self.dupeMe.width(), self.dupeMe.height())
painter = QtGui.QPainter()
painter.begin(pix)
self.dupeMe.render(painter)
painter.end()
qp = QtGui.QPainter()
qp.begin(self)
qp.drawImage(0, 0, pix.toImage())
qp.end()
Its driver code is trivial:
button = QtGui.QPushButton('dupe me')
doppel = QtUtil.QDoppelganger(button, None)
Stick one widget into the dock widget, and another into the QToolBar, and we are done.
If I have nailed it, then this post is a useful snippet.
If not, may I request a review of the techniques? No matter how few lines of PyQt code I write, it seems someone always comes along with some "cleverSignal.connect(cleverSlot)" to simply plug things together with even fewer lines of code. Specifically, instead of rendering one control into a QPixmap, then painting that into the doppelganger control, can't I simply connect the paint event directly?

Resources