Introduction
The facility called MakeActiveXClass, for creating windows that contain ActiveX objects such as Internet Explorer (IE) and Acrobat Reader makes many apps a joy to build.
In the case of IE, though, the structure and contents of a page cannot be examined until IE signals that it has completely finished displaying the page.
This recipe shows one way in which execution of code that depends on what is in the document being loaded can be suspended until IE indicates that the document is fully available.
What Objects are Involved
Principal wxPython items used: wxFrame and MakeActiveXClass. Principal Python classes: Event and Thread
Process Overview
IE can be made to signal its events simply by adding functions to the class in which MakeActiveXClass is used to instantiate IE, with the appropriate names. For example, in the sample code below, one sees a function called 'OnDocumentComplete' which is called by IE when it has entirely finished loading a document.
It might seem, at this point, that one could arrange to access the loaded document from within OnDocumentComplete. However, there are two problems with this approach:
1. For some reason, exceptions generated within OnDocumentComplete are not processed. Furthermore, this behaviour is 'inherited' by any routines that OnDocumentComplete calls. This loss of exception handling makes debugging much, much more time-consuming.
2. One can arrange to make a routine that examines IE's document wait for a signal from OnDocumentComplete. However, if this routine runs at the end of the wxFrame derivative's init a deadlock occurs because IE cannot finish loading the document without wxPython's having displayed the ActiveX window containing IE.
This recipe displays a way of dealing with these problems. The routine which is to process the IE document is run in a separate thread which waits for a signal from OnDocumentComplete before proceeding, if the document is not available when the process begins.
Here is output from the script.
>pythonw -u interviews.py ... inside doSomethingWithIE creating event inside doSomethingWithIE waiting inside doSomethingWithIE ... inside OnDocumentComplete ... obtaining an item from IE document event set inside OnDocumentComplete item obtained: Name Exception in thread Thread-1: Traceback (most recent call last): File "J:\Python22\lib\threading.py", line 408, in __bootstrap self.run() File "J:\Python22\lib\threading.py", line 396, in run apply(self.__target, self.__args, self.__kwargs) File "interviews.py", line 48, in doSomethingWithIE raise Exception, "... inside doSomethingWithIE" Exception: ... inside doSomethingWithIE >Exit code: 0
Notice particularly that the exception that is deliberately raised within OnDocumentComplete does "not" appear in the output but that the one raised within doSomethingWithIE does. (Big help in debugging, not that I don't thrill to opportunities to find obscure bugs.)
Finally, notice that doSomethingWithIE is indeed able to access IE's document successfully, as evidenced by the line in the output, " item obtained: Name".
When doSomethingWithIE runs it tests whether IE has finished its loading and, if it has not, doSomethingWithIE waits on an event that will be set by OnDocumentComplete when it is called.
Special Concerns
Special care should be used in writing OnDocumentComplete given that exceptions may not be handled properly. It might be best just to keep this routine as small and uncomplicated as possible.
The one aspect of this code that worries me a little is that there is no protection against the situation in which 'self . whenDocComplete', which refers to an Event, might be recreated whilst the other thread is using it.
Code Sample
1 from wxPython.wx import *
2 from win32com.client import Dispatch
3 from win32com.client.gencache import EnsureModule
4 from wxPython.lib.activexwrapper import MakeActiveXClass
5 from threading import Event, Thread
6 from os import getcwd
7
8 pageFilename="%s\\%s" %(getcwd(), "justTable.htm",)
9
10 class InterviewFrame(wxFrame):
11 def __init__(self, parent, id, title):
12 wxFrame.__init__(self, parent, id, title)
13
14 browserModule=EnsureModule("{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}", 0, 1, 1)
15 theClass=MakeActiveXClass(browserModule.WebBrowser, eventObj=self)
16 self.ie=theClass(self, -1)
17
18 self.SetAutoLayout(true)
19
20 lc=wxLayoutConstraints()
21 lc.right.SameAs(self , wxRight)
22 lc.left.SameAs(self, wxLeft)
23 lc.top.SameAs(self, wxTop)
24 lc.bottom.SameAs(self, wxBottom)
25 self.ie.SetConstraints(lc)
26
27 self.whenDocComplete=None
28
29 self.ie.Navigate("file://%s" % pageFilename)
30
31 Thread(target=self.doSomethingWithIE).start()
32
33 def OnDocumentComplete(self, *others):
34 print "... inside OnDocumentComplete"
35 self.whenDocComplete.set()
36 print " event set inside OnDocumentComplete"
37 raise Exception, "... inside OnDocumentComplete"
38
39 def doSomethingWithIE(self):
40 print "... inside doSomethingWithIE"
41 if self.ie.ReadyState!=4:
42 print " creating event inside doSomethingWithIE"
43 self.whenDocComplete=Event()
44 print " waiting inside doSomethingWithIE"
45 self.whenDocComplete.wait()
46 print "... obtaining an item from IE document"
47 print " item obtained: %s" % self.ie.Document.getElementsByTagName("input") [ 0 ].name
48 raise Exception, "... inside doSomethingWithIE"
49
50 class InterviewApp(wxApp):
51 def OnInit(self):
52 frame=InterviewFrame(NULL, -1, 'Handling IE Events')
53 frame.Show(true)
54 self.SetTopWindow(frame)
55 return true
56
57 if __name__=="__main__":
58 app=InterviewApp(0)
59 app.MainLoop()
Comments
In .5.2.x there is a new Navigate method in the wx Window class that clashes with ie.Navigate. The code will not break if we write ie.Navigate2 instead of ie.Navigate, but the problem remains ... danielle@davout.org