X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1ca848a2a97eb5830b281420d2c890204ad5cea0..8bbd1bbf18bfc3ee0363870df5c26f585001c2d8:/wxPython/demo/DelayedResult.py?ds=inline diff --git a/wxPython/demo/DelayedResult.py b/wxPython/demo/DelayedResult.py new file mode 100644 index 0000000000..f72fab5b31 --- /dev/null +++ b/wxPython/demo/DelayedResult.py @@ -0,0 +1,236 @@ +""" +This demonstrates a simple use of delayedresult: get/compute +something that takes a long time, without hanging the GUI while this +is taking place. + +The top button runs a small GUI that uses wx.lib.delayedresult.startWorker +to wrap a long-running function into a separate thread. Just click +Get, and move the slider, and click Get and Abort a few times, and +observe that GUI responds. The key functions to look for in the code +are startWorker() and __handleResult(). + +The second button runs the same GUI, but without delayedresult. Click +Get: now the get/compute is taking place in main thread, so the GUI +does not respond to user actions until worker function returns, it's +not even possible to Abort. +""" + +import wx +from wx.lib.delayedresult import startWorker + +class FrameSimpleDelayedGlade(wx.Frame): + def __init__(self, *args, **kwds): + # begin wxGlade: FrameSimpleDelayed.__init__ + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + self.checkboxUseDelayed = wx.CheckBox(self, -1, "Use delayedresult") + self.buttonGet = wx.Button(self, -1, "Get") + self.buttonAbort = wx.Button(self, -1, "Abort") + self.slider = wx.Slider(self, -1, 0, 0, 10, size=(100,-1), style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS) + self.textCtrlResult = wx.TextCtrl(self, -1, "", style=wx.TE_READONLY) + + self.__set_properties() + self.__do_layout() + + self.Bind(wx.EVT_BUTTON, self.handleGet, self.buttonGet) + self.Bind(wx.EVT_BUTTON, self.handleAbort, self.buttonAbort) + # end wxGlade + + def __set_properties(self): + # begin wxGlade: FrameSimpleDelayed.__set_properties + self.SetTitle("Simple Examle of Delayed Result") + self.checkboxUseDelayed.SetValue(1) + self.checkboxUseDelayed.Enable(False) + self.buttonAbort.Enable(False) + # end wxGlade + + def __do_layout(self): + # begin wxGlade: FrameSimpleDelayed.__do_layout + sizerFrame = wx.BoxSizer(wx.VERTICAL) + sizerGetResult = wx.BoxSizer(wx.HORIZONTAL) + sizerUseDelayed = wx.BoxSizer(wx.HORIZONTAL) + sizerUseDelayed.Add(self.checkboxUseDelayed, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 5) + sizerFrame.Add(sizerUseDelayed, 1, wx.EXPAND, 0) + sizerGetResult.Add(self.buttonGet, 0, wx.ADJUST_MINSIZE, 0) + sizerGetResult.Add(self.buttonAbort, 0, wx.ADJUST_MINSIZE, 0) + sizerGetResult.Add(self.slider, 0, wx.ADJUST_MINSIZE, 0) + sizerGetResult.Add(self.textCtrlResult, 0, wx.ADJUST_MINSIZE, 0) + sizerFrame.Add(sizerGetResult, 1, wx.ALL|wx.EXPAND, 5) + self.SetAutoLayout(True) + self.SetSizer(sizerFrame) + sizerFrame.Fit(self) + sizerFrame.SetSizeHints(self) + self.Layout() + # end wxGlade + + +class FrameSimpleDelayed(FrameSimpleDelayedGlade): + """This demos simplistic use of delayedresult module.""" + + def __init__(self, *args, **kwargs): + self.jobID = 1 + FrameSimpleDelayedGlade.__init__(self, *args, **kwargs) + self.Bind(wx.EVT_CLOSE, self.handleClose) + + def setLog(self, log): + self.log = log + + def handleClose(self, event): + """Only needed because in demo, closing the window does not kill the + app, so worker thread continues and sends result to dead frame; normally + your app would exit so this would not happen.""" + if self.buttonAbort.IsEnabled(): + self.Hide() + import time + time.sleep(5) + self.Destroy() + + def handleGet(self, event): + """Compute result in separate thread, doesn't affect GUI response.""" + self.buttonGet.Enable(False) + self.buttonAbort.Enable(True) + + self.log( "Starting job %s in producer thread: GUI remains responsive" % self.jobID ) + startWorker(self.__handleResult, self.__resultCreator, + wargs=(self.jobID,), jobID=self.jobID) + + def __resultCreator(self, jobID): + """Pretend to be a complex worker function or something that takes + long time to run due to network access etc. GUI will freeze if this + method is not called in separate thread.""" + import time + time.sleep(5) + return jobID + + def handleAbort(self, event): + """Abort actually just means 'ignore the result when it gets to + handler, it is no longer relevant'. We just increase the job ID, + this will let handler know that the result has been cancelled.""" + self.log( "Aborting result for job %s" % self.jobID ) + self.buttonGet.Enable(True) + self.buttonAbort.Enable(False) + self.jobID += 1 + + def __handleResult(self, delayedResult): + # See if we still want the result for last job started + jobID = delayedResult.getJobID() + if jobID != self.jobID: + self.log( "Got obsolete result for job %s, ignored" % jobID ) + return + + # we do, get result: + try: + result = delayedResult.get() + except Exception, exc: + self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) + self.jobID += 1 + return + + # output result + self.log( "Got result for job %s: %s" % (jobID, result) ) + self.textCtrlResult.SetValue(str(result)) + + # get ready for next job: + self.buttonGet.Enable(True) + self.buttonAbort.Enable(False) + self.jobID += 1 + + +class FrameSimpleDirect(FrameSimpleDelayedGlade): + """This does not use delayedresult so the GUI will freeze while + the GET is taking place.""" + + def __init__(self, *args, **kwargs): + self.jobID = 1 + FrameSimpleDelayedGlade.__init__(self, *args, **kwargs) + self.checkboxUseDelayed.SetValue(False) + + def setLog(self, log): + self.log = log + + def handleGet(self, event): + """Use delayedresult, this will compute + result in separate thread, and won't affect GUI response. """ + self.buttonGet.Enable(False) + self.buttonAbort.Enable(True) + + self.log( "Doing job %s without delayedresult (same as GUI thread): GUI hangs (for a while)" % self.jobID ) + result = self.__resultCreator(self.jobID) + self.__handleResult( result ) + + def __resultCreator(self, jobID): + """Pretend to be a complex worker function or something that takes + long time to run due to network access etc. GUI will freeze if this + method is not called in separate thread.""" + import time + time.sleep(5) + return jobID + + def handleAbort(self, event): + """can never be called""" + pass + + def __handleResult(self, result): + # output result + self.log( "Got result for job %s: %s" % (self.jobID, result) ) + self.textCtrlResult.SetValue(str(result)) + + # get ready for next job: + self.buttonGet.Enable(True) + self.buttonAbort.Enable(False) + self.jobID += 1 + + +#--------------------------------------------------------------------------- +#--------------------------------------------------------------------------- + +class TestPanel(wx.Panel): + def __init__(self, parent, log): + self.log = log + wx.Panel.__init__(self, parent, -1) + + vsizer = wx.BoxSizer(wx.VERTICAL) + b = wx.Button(self, -1, "Long-running function in separate thread") + vsizer.Add(b, 0, wx.ALL, 5) + self.Bind(wx.EVT_BUTTON, self.OnButton1, b) + + b = wx.Button(self, -1, "Long-running function in GUI thread") + vsizer.Add(b, 0, wx.ALL, 5) + self.Bind(wx.EVT_BUTTON, self.OnButton2, b) + + bdr = wx.BoxSizer() + bdr.Add(vsizer, 0, wx.ALL, 50) + self.SetSizer(bdr) + self.Layout() + + def OnButton1(self, evt): + frame = FrameSimpleDelayed(self, title="Long-running function in separate thread") + frame.setLog(self.log.WriteText) + frame.Show() + + def OnButton2(self, evt): + frame = FrameSimpleDirect(self, title="Long-running function in GUI thread") + frame.setLog(self.log.WriteText) + frame.Show() + + +#--------------------------------------------------------------------------- + + +def runTest(frame, nb, log): + win = TestPanel(nb, log) + return win + + +#--------------------------------------------------------------------------- + + +overview = __doc__ + + +if __name__ == '__main__': + import sys,os + import run + run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) + +