Interrupting a GUI function call? (wxWindows Specifically)

Isaac Barona ibarona at tid.es
Wed May 30 12:40:41 EDT 2001


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