Interrupting a GUI function call? (wxWindows Specifically)

Daniel Frame dframe at tampabay.rr.com
Fri May 25 08:56:05 EDT 2001


Thanks Dave!

After trying both of the example "strategies" that you presented, it
looks like the thread example does work better than continuously
calling wxYield. (As you stated it probably would...)

Thanks again  for your very excellent response.

 Damn, I love Python!



On 25 May 2001 00:54:39 -0400, David Bolen <db3l at fitlinxx.com> wrote:

>dframe at tampabay.rr.com (Daniel Frame) writes:
>
>> Basically, I've constructed a GUI with wxPython which runs a very long
>> function when a 'START' button is pressed.
>> 
>> Of course, the GUI is 'locked up' until this function completes
>> itself.  I was wanting to add a 'STOP' button to my application which
>> would halt this other function if the user got tired of waiting for it
>> to complete.
>> 
>> Is it possible to accomplish this without resorting to using threads?
>> 
>> If so, How can I have my running function check occaisonally to see if
>> the stop button was pressed?
>> 
>> I've heard of wxYield, but I can't seem to  implement it properly.
>
>There's really three possibilities I would think of - threading,
>wxYield, or chunking up your processing in a wxEVT_IDLE handler.  I've
>given a small sample of each below.
>
>I personally like threads for this sort of thing (I think they leave
>the UI the most responsive), but there's no hard and fast rule.  So
>whichever you're more comfortable with.
>
>Threading really isn't that hard - you can just start up a thread to
>do your processing, and have it send an event to your main GUI thread
>when it has completed.  While it is working, it can check an event
>object or some other flag variable to indicate that it should give up
>and stop.
>
>For a simple sort of example, here's a small bit of code that presents
>a simple (really dumb visually) frame, and uses a worker thread to
>simulate some processing (that takes 10s, resulting in a value of 10),
>while permitting it to be aborted.  This could be extrapolated to
>doing any sort of lengthy processing and returning any sort of result.
>Intermediate events could be generated during the processing to give
>some indication to the main GUI thread of how things were proceeding
>(perhaps updating something like a gauge or some other visual
>indicator).
>
>    import time
>    from threading import *
>    from wxPython.wx import *
>
>    # Button definitions
>    ID_START = wxNewId()
>    ID_STOP  = wxNewId()
>
>    # Define notification event for thread completion
>
>    EVT_RESULT_ID = wxNewId()    
>
>    def EVT_RESULT(win, func):
>        win.Connect(-1, -1, EVT_RESULT_ID, func)
>
>    class ResultEvent(wxPyEvent):
>        """Simple event to carry arbitrary result data"""
>
>        def __init__(self, data):
>            wxPyEvent.__init__(self)
>            self.SetEventType(EVT_RESULT_ID)
>            self.data = data
>
>    # Thread class that executes processing
>
>    class WorkerThread(Thread):
>
>        def __init__(self, notify_window):
>            Thread.__init__(self)
>            self._notify_window = notify_window
>            self._want_abort = 0
>            # This starts the thread running on creation, but you could
>            # also make the GUI thread responsible for calling this
>            self.start()
>
>        def run(self):
>            # This is the code executing in the new thread. Simulation of
>            # a long process (well, 10s here) as a simple loop - you will
>            # need to structure your processing so that you periodically
>            # peek at the abort variable
>            for i in range(10):
>                time.sleep(1)
>                if self._want_abort:
>                    # Use a result of None to acknowledge the abort (of
>                    # course you can use whatever you'd like or even
>                    # a separate event type)
>                    wxPostEvent(self._notify_window,ResultEvent(None))
>                    return
>            # Here's where the result would be returned (this is an
>            # example fixed result of the number 10, but it could be
>            # any Python object)
>            wxPostEvent(self._notify_window,ResultEvent(10))
>
>        def abort(self):
>            # Method for use by main thread to signal an abort           
>            self._want_abort = 1
>
>    # GUI Frame class that spins off the worker thread
>
>    class MainFrame(wxFrame):
>        def __init__(self, parent, id):
>            wxFrame.__init__(self,parent,id,'Thread Test')
>
>            # Dumb sample frame with two buttons
>            wxButton(self,ID_START,'Start',pos=(0,0))
>            wxButton(self,ID_STOP,'Stop',pos=(0,50))
>            self.status = wxStaticText(self,-1,'',pos=(0,100))
>
>            EVT_BUTTON(self,ID_START,self.OnStart)
>            EVT_BUTTON(self,ID_STOP,self.OnStop)
>
>            # Set up event handler for any worker thread results
>            EVT_RESULT(self,self.OnResult)
>
>            # And indicate we don't have a worker thread yet
>            self.worker = None
>
>        def OnStart(self, event):
>            # Trigger the worker thread unless it's already busy
>            if not self.worker:
>                self.status.SetLabel('Starting computation')
>                self.worker = WorkerThread(self)
>
>        def OnStop(self, event):
>            # Flag the worker thread to stop if running
>            if self.worker:
>                self.status.SetLabel('Trying to abort computation')
>                self.worker.abort()
>
>        def OnResult(self, event):
>            if event.data is None:
>                # Thread aborted (using our convention of None return)
>                self.status.SetLabel('Computation aborted')
>            else:
>                # Process results here
>                self.status.SetLabel('Computation Result: %s' % event.data)
>            # In either event, the worker is done
>            self.worker = None
>
>    class MainApp(wxApp):
>
>        def OnInit(self):
>            self.frame = MainFrame(NULL,-1)
>            self.frame.Show(true)
>            self.SetTopWindow(self.frame)
>            return true
>
>    if __name__ == '__main__':
>
>        app = MainApp(0)
>        app.MainLoop()
>
>Oh, and if you're concerned with hanging on an exit if your thread
>doesn't terminate for some reason, just add a "self.setDaemon(1)" to
>the __init__ and Python won't wait for it to terminate.
>
>The second approach, using wxYield, should be fine too - just add a
>call to wxYield() somewhere within the computation code such that it
>executes periodically.  At that point, any pending window events will
>be dispatched (permitting the window to refresh, process button
>presses, etc...).  Then, it's similar to the above in that you set a
>flag so that when the original code gets control after the wxYield()
>returns it knows to stop processing.
>
>As with the threading case, since all events go through during the
>wxYield() you need to protect against trying to run the same operation
>twice.
>
>Here's the equivalent of the above but placing the computation right
>inside the main window class.  Note that one difference is that unlike
>with threading, the responsiveness of your GUI is now directly related
>to how frequently you call wxYield, so you may have delays refreshing
>your window dependent on that frequency.  You should notice that this is
>a bit more sluggish with its frequency of a wxYield() each second.
>
>    import time
>    from wxPython.wx import *
>
>    # Button definitions
>    ID_START = wxNewId()
>    ID_STOP  = wxNewId()
>
>    # GUI Frame class that spins off the worker thread
>
>    class MainFrame(wxFrame):
>        def __init__(self, parent, id):
>            wxFrame.__init__(self,parent,id,'wxYield Test')
>
>            # Dumb sample frame with two buttons
>            wxButton(self,ID_START,'Start',pos=(0,0))
>            wxButton(self,ID_STOP,'Stop',pos=(0,50))
>            self.status = wxStaticText(self,-1,'',pos=(0,100))
>
>            EVT_BUTTON(self,ID_START,self.OnStart)
>            EVT_BUTTON(self,ID_STOP,self.OnStop)
>
>            # Indicate we aren't working on it yet
>            self.working = 0
>
>        def OnStart(self, event):
>            # Start the processing - this simulates a loop - you need to call
>            # wxYield at some periodic interval.
>            if not self.working:
>                self.status.SetLabel('Starting Computation')
>                self.working = 1
>                self.need_abort = 0
>
>                for i in range(10):
>                    time.sleep(1)
>                    wxYield()
>                    if self.need_abort:
>                        self.status.SetLabel('Computation aborted')
>                        break
>                else:
>                    # Here's where you would process the result
>                    # Note you should only do this if not aborted.
>                    self.status.SetLabel('Computation Completed')
>
>                # In either event, we aren't running any more
>                self.working = 0
>
>        def OnStop(self, event):
>            if self.working:
>                self.status.SetLabel('Trying to abort computation')
>                self.need_abort = 1
>
>    class MainApp(wxApp):
>
>        def OnInit(self):
>            self.frame = MainFrame(NULL,-1)
>            self.frame.Show(true)
>            self.SetTopWindow(self.frame)
>            return true
>
>    if __name__ == '__main__':
>
>        app = MainApp(0)
>        app.MainLoop()
>
>
>And finally, you can do your work within an idle handler.  In this
>case, you let wxPython generate an IDLE event whenever it has
>completed processing normal user events, and then you perform a
>"chunk" of your processing in each such case.  This can be a little
>tricker depending on your algorithm since you have to be able to
>perform the work in discrete pieces.  Inside your IDLE handler, you
>request that it be called again if you aren't done, but you want to
>make sure that each pass through the handler doesn't take too long.
>Effectively, each event is similar to the gap between wxYield() calls
>in the previous example, and your GUI responsiveness will be subject
>to that latency just as with the wxYield() case.
>
>I'm also not sure you can remove an idle handler once established (or
>at least I think I had problems with that in the past), so the code
>below just establishes it once and the handler only does work if it's
>in the midst of a computation.
>
>    import time
>    from wxPython.wx import *
>
>    # Button definitions
>    ID_START = wxNewId()
>    ID_STOP  = wxNewId()
>
>    # GUI Frame class that spins off the worker thread
>
>    class MainFrame(wxFrame):
>        def __init__(self, parent, id):
>            wxFrame.__init__(self,parent,id,'Idle Test')
>
>            # Dumb sample frame with two buttons
>            wxButton(self,ID_START,'Start',pos=(0,0))
>            wxButton(self,ID_STOP,'Stop',pos=(0,50))
>            self.status = wxStaticText(self,-1,'',pos=(0,100))
>
>            EVT_BUTTON(self,ID_START,self.OnStart)
>            EVT_BUTTON(self,ID_STOP,self.OnStop)
>            EVT_IDLE(self,self.OnIdle)
>
>            # Indicate we aren't working on it yet
>            self.working = 0
>
>        def OnStart(self, event):
>            # Set up for processing and trigger idle event
>            if not self.working:
>                self.status.SetLabel('Starting Computation')
>                self.count = 0
>                self.working = 1
>                self.need_abort = 0
>
>        def OnIdle(self, event):
>            if self.working:
>                # This is where the processing takes place, one bit at a time
>                if self.need_abort:
>                    self.status.SetLabel('Computation aborted')
>                else:
>                    self.count = self.count + 1
>                    time.sleep(1)
>                    if self.count < 10:
>                        # Still more work to do so request another event
>                        event.RequestMore()
>                        return
>                    else:
>                        self.status.SetLabel('Computation completed')
>
>                # Reaching here is an abort or completion - end in either case
>                self.working = 0
>
>        def OnStop(self, event):
>            if self.working:
>                self.status.SetLabel('Trying to abort computation')
>                self.need_abort = 1
>
>    class MainApp(wxApp):
>
>        def OnInit(self):
>            self.frame = MainFrame(NULL,-1)
>            self.frame.Show(true)
>            self.SetTopWindow(self.frame)
>            return true
>
>    if __name__ == '__main__':
>
>        app = MainApp(0)
>        app.MainLoop()
>
>
>--
>-- David
>-- 
>/-----------------------------------------------------------------------\
> \               David Bolen            \   E-mail: db3l at fitlinxx.com  /
>  |             FitLinxx, Inc.            \  Phone: (203) 708-5192    |
> /  860 Canal Street, Stamford, CT  06902   \  Fax: (203) 316-5150     \
>\-----------------------------------------------------------------------/




More information about the Python-list mailing list