]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/demo/DelayedResult.py
Added wx.lib.delayedresult from Oliver Schoenborn.
[wxWidgets.git] / wxPython / demo / DelayedResult.py
diff --git a/wxPython/demo/DelayedResult.py b/wxPython/demo/DelayedResult.py
new file mode 100644 (file)
index 0000000..f72fab5
--- /dev/null
@@ -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:])
+
+