]>
Commit | Line | Data |
---|---|---|
8bbd1bbf RD |
1 | """ |
2 | This demonstrates a simple use of delayedresult: get/compute | |
3 | something that takes a long time, without hanging the GUI while this | |
4 | is taking place. | |
5 | ||
6 | The top button runs a small GUI that uses wx.lib.delayedresult.startWorker | |
7 | to wrap a long-running function into a separate thread. Just click | |
8 | Get, and move the slider, and click Get and Abort a few times, and | |
9 | observe that GUI responds. The key functions to look for in the code | |
10 | are startWorker() and __handleResult(). | |
11 | ||
12 | The second button runs the same GUI, but without delayedresult. Click | |
13 | Get: now the get/compute is taking place in main thread, so the GUI | |
14 | does not respond to user actions until worker function returns, it's | |
15 | not even possible to Abort. | |
16 | """ | |
17 | ||
18 | import wx | |
19 | from wx.lib.delayedresult import startWorker | |
20 | ||
21 | class FrameSimpleDelayedGlade(wx.Frame): | |
22 | def __init__(self, *args, **kwds): | |
23 | # begin wxGlade: FrameSimpleDelayed.__init__ | |
24 | kwds["style"] = wx.DEFAULT_FRAME_STYLE | |
25 | wx.Frame.__init__(self, *args, **kwds) | |
26 | self.checkboxUseDelayed = wx.CheckBox(self, -1, "Use delayedresult") | |
27 | self.buttonGet = wx.Button(self, -1, "Get") | |
28 | self.buttonAbort = wx.Button(self, -1, "Abort") | |
29 | self.slider = wx.Slider(self, -1, 0, 0, 10, size=(100,-1), style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS) | |
30 | self.textCtrlResult = wx.TextCtrl(self, -1, "", style=wx.TE_READONLY) | |
31 | ||
32 | self.__set_properties() | |
33 | self.__do_layout() | |
34 | ||
35 | self.Bind(wx.EVT_BUTTON, self.handleGet, self.buttonGet) | |
36 | self.Bind(wx.EVT_BUTTON, self.handleAbort, self.buttonAbort) | |
37 | # end wxGlade | |
38 | ||
39 | def __set_properties(self): | |
40 | # begin wxGlade: FrameSimpleDelayed.__set_properties | |
41 | self.SetTitle("Simple Examle of Delayed Result") | |
42 | self.checkboxUseDelayed.SetValue(1) | |
43 | self.checkboxUseDelayed.Enable(False) | |
44 | self.buttonAbort.Enable(False) | |
45 | # end wxGlade | |
46 | ||
47 | def __do_layout(self): | |
48 | # begin wxGlade: FrameSimpleDelayed.__do_layout | |
49 | sizerFrame = wx.BoxSizer(wx.VERTICAL) | |
50 | sizerGetResult = wx.BoxSizer(wx.HORIZONTAL) | |
51 | sizerUseDelayed = wx.BoxSizer(wx.HORIZONTAL) | |
52 | sizerUseDelayed.Add(self.checkboxUseDelayed, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 5) | |
53 | sizerFrame.Add(sizerUseDelayed, 1, wx.EXPAND, 0) | |
54 | sizerGetResult.Add(self.buttonGet, 0, wx.ADJUST_MINSIZE, 0) | |
55 | sizerGetResult.Add(self.buttonAbort, 0, wx.ADJUST_MINSIZE, 0) | |
56 | sizerGetResult.Add(self.slider, 0, wx.ADJUST_MINSIZE, 0) | |
57 | sizerGetResult.Add(self.textCtrlResult, 0, wx.ADJUST_MINSIZE, 0) | |
58 | sizerFrame.Add(sizerGetResult, 1, wx.ALL|wx.EXPAND, 5) | |
59 | self.SetAutoLayout(True) | |
60 | self.SetSizer(sizerFrame) | |
61 | sizerFrame.Fit(self) | |
62 | sizerFrame.SetSizeHints(self) | |
63 | self.Layout() | |
64 | # end wxGlade | |
65 | ||
66 | ||
67 | class FrameSimpleDelayed(FrameSimpleDelayedGlade): | |
68 | """This demos simplistic use of delayedresult module.""" | |
69 | ||
70 | def __init__(self, *args, **kwargs): | |
71 | self.jobID = 1 | |
72 | FrameSimpleDelayedGlade.__init__(self, *args, **kwargs) | |
73 | self.Bind(wx.EVT_CLOSE, self.handleClose) | |
74 | ||
75 | def setLog(self, log): | |
76 | self.log = log | |
77 | ||
78 | def handleClose(self, event): | |
79 | """Only needed because in demo, closing the window does not kill the | |
80 | app, so worker thread continues and sends result to dead frame; normally | |
81 | your app would exit so this would not happen.""" | |
82 | if self.buttonAbort.IsEnabled(): | |
83 | self.Hide() | |
84 | import time | |
85 | time.sleep(5) | |
86 | self.Destroy() | |
87 | ||
88 | def handleGet(self, event): | |
89 | """Compute result in separate thread, doesn't affect GUI response.""" | |
90 | self.buttonGet.Enable(False) | |
91 | self.buttonAbort.Enable(True) | |
92 | ||
93 | self.log( "Starting job %s in producer thread: GUI remains responsive" % self.jobID ) | |
94 | startWorker(self.__handleResult, self.__resultCreator, | |
95 | wargs=(self.jobID,), jobID=self.jobID) | |
96 | ||
97 | def __resultCreator(self, jobID): | |
98 | """Pretend to be a complex worker function or something that takes | |
99 | long time to run due to network access etc. GUI will freeze if this | |
100 | method is not called in separate thread.""" | |
101 | import time | |
102 | time.sleep(5) | |
103 | return jobID | |
104 | ||
105 | def handleAbort(self, event): | |
106 | """Abort actually just means 'ignore the result when it gets to | |
107 | handler, it is no longer relevant'. We just increase the job ID, | |
108 | this will let handler know that the result has been cancelled.""" | |
109 | self.log( "Aborting result for job %s" % self.jobID ) | |
110 | self.buttonGet.Enable(True) | |
111 | self.buttonAbort.Enable(False) | |
112 | self.jobID += 1 | |
113 | ||
114 | def __handleResult(self, delayedResult): | |
115 | # See if we still want the result for last job started | |
116 | jobID = delayedResult.getJobID() | |
117 | if jobID != self.jobID: | |
118 | self.log( "Got obsolete result for job %s, ignored" % jobID ) | |
119 | return | |
120 | ||
121 | # we do, get result: | |
122 | try: | |
123 | result = delayedResult.get() | |
124 | except Exception, exc: | |
125 | self.log( "Result for job %s raised exception: %s" % (jobID, exc) ) | |
126 | self.jobID += 1 | |
127 | return | |
128 | ||
129 | # output result | |
130 | self.log( "Got result for job %s: %s" % (jobID, result) ) | |
131 | self.textCtrlResult.SetValue(str(result)) | |
132 | ||
133 | # get ready for next job: | |
134 | self.buttonGet.Enable(True) | |
135 | self.buttonAbort.Enable(False) | |
136 | self.jobID += 1 | |
137 | ||
138 | ||
139 | class FrameSimpleDirect(FrameSimpleDelayedGlade): | |
140 | """This does not use delayedresult so the GUI will freeze while | |
141 | the GET is taking place.""" | |
142 | ||
143 | def __init__(self, *args, **kwargs): | |
144 | self.jobID = 1 | |
145 | FrameSimpleDelayedGlade.__init__(self, *args, **kwargs) | |
146 | self.checkboxUseDelayed.SetValue(False) | |
147 | ||
148 | def setLog(self, log): | |
149 | self.log = log | |
150 | ||
151 | def handleGet(self, event): | |
152 | """Use delayedresult, this will compute | |
153 | result in separate thread, and won't affect GUI response. """ | |
154 | self.buttonGet.Enable(False) | |
155 | self.buttonAbort.Enable(True) | |
156 | ||
157 | self.log( "Doing job %s without delayedresult (same as GUI thread): GUI hangs (for a while)" % self.jobID ) | |
158 | result = self.__resultCreator(self.jobID) | |
159 | self.__handleResult( result ) | |
160 | ||
161 | def __resultCreator(self, jobID): | |
162 | """Pretend to be a complex worker function or something that takes | |
163 | long time to run due to network access etc. GUI will freeze if this | |
164 | method is not called in separate thread.""" | |
165 | import time | |
166 | time.sleep(5) | |
167 | return jobID | |
168 | ||
169 | def handleAbort(self, event): | |
170 | """can never be called""" | |
171 | pass | |
172 | ||
173 | def __handleResult(self, result): | |
174 | # output result | |
175 | self.log( "Got result for job %s: %s" % (self.jobID, result) ) | |
176 | self.textCtrlResult.SetValue(str(result)) | |
177 | ||
178 | # get ready for next job: | |
179 | self.buttonGet.Enable(True) | |
180 | self.buttonAbort.Enable(False) | |
181 | self.jobID += 1 | |
182 | ||
183 | ||
184 | #--------------------------------------------------------------------------- | |
185 | #--------------------------------------------------------------------------- | |
186 | ||
187 | class TestPanel(wx.Panel): | |
188 | def __init__(self, parent, log): | |
189 | self.log = log | |
190 | wx.Panel.__init__(self, parent, -1) | |
191 | ||
192 | vsizer = wx.BoxSizer(wx.VERTICAL) | |
193 | b = wx.Button(self, -1, "Long-running function in separate thread") | |
194 | vsizer.Add(b, 0, wx.ALL, 5) | |
195 | self.Bind(wx.EVT_BUTTON, self.OnButton1, b) | |
196 | ||
197 | b = wx.Button(self, -1, "Long-running function in GUI thread") | |
198 | vsizer.Add(b, 0, wx.ALL, 5) | |
199 | self.Bind(wx.EVT_BUTTON, self.OnButton2, b) | |
200 | ||
201 | bdr = wx.BoxSizer() | |
202 | bdr.Add(vsizer, 0, wx.ALL, 50) | |
203 | self.SetSizer(bdr) | |
204 | self.Layout() | |
205 | ||
206 | def OnButton1(self, evt): | |
207 | frame = FrameSimpleDelayed(self, title="Long-running function in separate thread") | |
208 | frame.setLog(self.log.WriteText) | |
209 | frame.Show() | |
210 | ||
211 | def OnButton2(self, evt): | |
212 | frame = FrameSimpleDirect(self, title="Long-running function in GUI thread") | |
213 | frame.setLog(self.log.WriteText) | |
214 | frame.Show() | |
215 | ||
216 | ||
217 | #--------------------------------------------------------------------------- | |
218 | ||
219 | ||
220 | def runTest(frame, nb, log): | |
221 | win = TestPanel(nb, log) | |
222 | return win | |
223 | ||
224 | ||
225 | #--------------------------------------------------------------------------- | |
226 | ||
227 | ||
228 | overview = __doc__ | |
229 | ||
230 | ||
231 | if __name__ == '__main__': | |
232 | import sys,os | |
233 | import run | |
234 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) | |
235 | ||
236 |