Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Events in win32com

885 views
Skip to first unread message

Paul Moore

unread,
Dec 19, 2000, 7:49:23 AM12/19/00
to
If I create an object using

obj = win32com.client.Dispatch(progid)

can I see events from the object? In WSH the CreateObject method has
an extra parameter which is the prefix for subroutine names which are
called on an event, something like

ie = WScript.CreateObject("InternetExplorer.Application", "ie_")

Sub ie_OnQuit()
End Sub

While I don't like the "specially named subroutine" approach, I do
need to be able to sink events in Python. Is this possible, or should
I resort to using Python via WSH?

(BTW, assuming I can sink events, will Python handle ByRef event
parameters - for example, a Cancel parameter which is set to True in
the handler to stop the event occurring).

Thanks,
Paul Moore

Paul Moore

unread,
Dec 19, 2000, 11:37:22 AM12/19/00
to
On Tue, 19 Dec 2000 13:49:23 +0100, Paul Moore
<paul....@uk.origin-it.com> wrote:

>If I create an object using
>
>obj = win32com.client.Dispatch(progid)
>
>can I see events from the object?

After scanning Deja, I found an article describing DispatchWithEvents.
The example in the documentation (OnVisible) works fine. But the
OnQuit event doesn't seem to work :-( Basically, the event routine is
never fired. Is this because the event routine has a ByRef "Cancel"
parameter? Or is it something else?

Thanks,
Paul

Alex Martelli

unread,
Dec 19, 2000, 12:11:16 PM12/19/00
to
"Paul Moore" <paul....@uk.origin-it.com> wrote in message
news:Y1g=Op6IfWSoJn1I...@4ax.com...

> If I create an object using
>
> obj = win32com.client.Dispatch(progid)
>
> can I see events from the object?

You can, although using DispatchWithEvents may be easier.

This is a typical example...:

import win32com.client
from win32com.client import gencache
evmod = gencache.EnsureModule('{D240EC61-B6ED-11D4-9E45-0060B0EB1D67}', 0,
1, 0)

class Evhand:
def OnOne(self):
print "eh: One!"
def OnTwo(self):
print "eh: Two!"

class Other(evmod._IEvGenEvents2):
def OnOne(self):
print "oh: One!"
def OnTwo(self):
print "oh: Two!"

ob = win32com.client.DispatchWithEvents("evts.EvGen", Evhand)
oh = Other(ob)
ob.Fire()


where progid 'evts.EvGen' refers to a tiny object I built just
for the purpose of event-handling tests (it generates events
along different sourceinterfaces when you call its Fire method).
This shows we can get events separately from DispatchWithEvents
(and on a different sourceinterface than the default one, if we
know the name of that alternate sourceinterface!).

> In WSH the CreateObject method has
> an extra parameter which is the prefix for subroutine names which are
> called on an event, something like
>
> ie = WScript.CreateObject("InternetExplorer.Application", "ie_")
>
> Sub ie_OnQuit()
> End Sub
>
> While I don't like the "specially named subroutine" approach, I do
> need to be able to sink events in Python. Is this possible, or should
> I resort to using Python via WSH?

It's quite possible to avoid WSH, unless you like the services
it brings to your script.


> (BTW, assuming I can sink events, will Python handle ByRef event
> parameters - for example, a Cancel parameter which is set to True in
> the handler to stop the event occurring).

If your EnsureModule (or equivalent) has been called, so that
Python *knows* exactly what's happening, then, yes, [out] and
[in,out] parameters will be handled (COM has no 'byref', but
that's how VB maps [in,out] -- [out] is a big problem in VB),
but of course they'll be handled for events as in other COM
Python cases -- that is, they become extra _return values_
of your method.


Alex

Alex Martelli

unread,
Dec 19, 2000, 2:49:13 PM12/19/00
to
"Paul Moore" <paul....@uk.origin-it.com> wrote in message
news:FY0=OlZFLy71DtnK...@4ax.com...

If the event expects to call a method with one parameter (as it
will, if it's [in,out]; but not if it's just [out] -- this is a key COM
level distinction, but VB tends to the former, not supporting the
latter...) then it will silently fail to call a method that takes no
parameters even if the names coincide. (Make sure to return the
value you receive as input as your result, to get [in,out] effects).

Also, you need (in general) for makepy to have been run on that
type library, or something equivalent that has processed the tlib
for Python used and placed it in the cache directory of known
type libraryes (the, I believe, EnsureDispatch method of the
gencache module, for example). Python has to guess if it does
not know the specifics of the type library (just as VB would have
to guess in the same case) and it won't always guess right!-)


Alex

Mark Hammond

unread,
Dec 19, 2000, 8:11:29 PM12/19/00
to
Paul Moore wrote:

> After scanning Deja, I found an article describing DispatchWithEvents.
> The example in the documentation (OnVisible) works fine. But the
> OnQuit event doesn't seem to work :-( Basically, the event routine is
> never fired. Is this because the event routine has a ByRef "Cancel"
> parameter? Or is it something else?

OnQuit works for me. test\testMSOfficeEvents.py traps this.

Mark.

Paul Moore

unread,
Dec 20, 2000, 5:50:22 AM12/20/00
to

Grrr. I'm having problems getting events fired from any of the MS
applications (IE, Excel), either in VBScript or in Python. Looks like
it's something stupid I am doing... We'll ignore that for now...

On the other hand, if I use a 3rd party control (WScriptEx) I get
events quite happily in both VBS and Python. But I tried the "return"
trick to set a ByRef parameter, which is shown in
test\testMSOfficeEvents.py, but it doesn't seem to work. I have

from win32com.client import *

class WexEvents:
def OnTick(self, rem, wake):
print "Tick...", rem, wake
if rem < 5000:
# wake = 1 # doesn't work...
return 1 # Goes into the only ByRef parameter...

wex = DispatchWithEvents("WshToolbox.WScriptEx", WexEvents)

print "Zzzzz"
wex.Sleep(10000,1000)
print "Hello!"

Here, the WScriptEx Sleep method takes a time to sleep and a time
between ticks, and fires an OnTick event on each tick. The OnTick
event takes a "Remaining Time" (ByVal) parameter, and a "Wake Up"
(ByRef) parameter. My understanding of the example is that returning
1, should set the wake parameter to 1, as it is the only ByRef
parameter and the OnTick event is defined as a sub. But it doesn't
seem to work.

Am I doing something wrong, or is this not expected to work?

Paul

Paul Moore

unread,
Dec 20, 2000, 4:07:04 PM12/20/00
to
On Wed, 20 Dec 2000 11:50:22 +0100, Paul Moore
<paul....@uk.origin-it.com> wrote:

>Grrr. I'm having problems getting events fired from any of the MS
>applications (IE, Excel), either in VBScript or in Python. Looks like
>it's something stupid I am doing... We'll ignore that for now...

Got it working (on a different machine). Looks like it's either a
config problem or an intermitent one. Everything looks great now.
Thanks for all the help.

One further question - another way of doing the same thing (in WSH) is
to assign to the "onclick" property of the HTML document in IE.
Something like

ie.document.onclick = function() { WScript.Echo("Click!") }

Is it possible to do anything like this in Python? If not, can I do
the same thing another way? (I have created ie via
DispatchWithEvents(), but the HTML document is a property of this. Can
I rewrap it somehow, say as
doc = DispatchWithEvents(ie.document, DocEvents)
?

Thanks,
Paul

Mark Hammond

unread,
Dec 20, 2000, 11:52:29 PM12/20/00
to
Paul Moore wrote:

> Am I doing something wrong, or is this not expected to work?

It is expected to work, and I can't see what you are doing wrong.

If you can work out a very simple case that fails (ie, suiltable for
inclusion in the test suite) I would look at it, but without knowing the
exact signatures in question, or knowing if Python is finding the sigs
in the makepy file etc, I really have no idea...

Mark.

Mark Hammond

unread,
Dec 20, 2000, 11:59:32 PM12/20/00
to
Paul Moore wrote:

> Is it possible to do anything like this in Python? If not, can I do
> the same thing another way? (I have created ie via
> DispatchWithEvents(), but the HTML document is a property of this. Can
> I rewrap it somehow, say as
> doc = DispatchWithEvents(ie.document, DocEvents)

This is (should be) as simple as instantiating a sub-class of the makepy
generated event class, and passing the document.

eg, something like:

mod = EnsureModule(...)

class MyEvents(mod.IDocumentEvents):
# your methods here....

handler = MyEvents(ie.document)

# handler should start recieving events.

Mark.

Alex Martelli

unread,
Dec 21, 2000, 9:04:46 AM12/21/00
to
"Mark Hammond" <Ma...@ActiveState.com> wrote in message
news:3A418D02...@ActiveState.com...

> Paul Moore wrote:
>
> > Is it possible to do anything like this in Python? If not, can I do
> > the same thing another way? (I have created ie via
> > DispatchWithEvents(), but the HTML document is a property of this. Can
> > I rewrap it somehow, say as
> > doc = DispatchWithEvents(ie.document, DocEvents)
>
> This is (should be) as simple as instantiating a sub-class of the makepy
> generated event class, and passing the document.

Yes, but -- how does this play with the bForDemand flag, which
is really a must when the module is as huge as MSHTML...?


> eg, something like:
>
> mod = EnsureModule(...)
>
> class MyEvents(mod.IDocumentEvents):
> # your methods here....
>
> handler = MyEvents(ie.document)
>
> # handler should start recieving events.

Sure, once I managed to somehow grab the right module it's very
easy; but I think I've not yet grasped how to do that grabbing
in the current 'bForDemand' architecture (indispensable for
really huge type libraries, of course). For example, consider
the following attempt:


import win32com.client, win32com.client.gencache, pythoncom

def EnsureModuleForTypelibOfObject(obj, bForDemand=1):
try:
ti = obj._oleobj_.GetTypeInfo()
clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
return win32com.client.gencache.EnsureModule(
tla[0], tla[1], tla[3], tla[4], bForDemand=bForDemand)
except pythoncom.com_error:
raise TypeError, "This COM object can not automate the makepy
process - please run makepy manually for this object"

ie = win32com.client.gencache.EnsureDispatch("InternetExplorer.Application")
ie.Navigate('c:/index.htm')
ie.Visible=1

html_module = EnsureModuleForTypelibOfObject(ie.Document)

print html_module

class Handler(html_module.HTMLDocumentEvents2):
def __init__(self, obj):
self.seen = 0
html_module.HTMLDocumentEvents2.__init__(self, obj)
def Ononclick(self, *args):
print "clic!", args
self.seen += 1

h = Handler(doc)

while h.seen < 5:
pythoncom.PumpWaitingMessages()


What I see on the output:

D:\PySym>python tev1.py
<module 'win32com.gen_py.3050F1C5-98B5-11CF-BB82-00AA00BDCE0Bx0x4x0' from
'd:\py
thon20\win32com\gen_py\3050F1C5-98B5-11CF-BB82-00AA00BDCE0Bx0x4x0\__init__.p
yc'>

Traceback (most recent call last):
File "tev1.py", line 22, in ?
class Handler(html_module.HTMLDocumentEvents2):
AttributeError: HTMLDocumentEvents2


and indeed, the generated directory:

D:\Python20\win32com\gen_py\3050F1C5-98B5-11CF-BB82-00AA00BDCE0Bx0x4x0

only contains files named __init__ and DispHTMLDocument (.py and .pyc
for each, of course).

Reading the CLSIDToPackageMap in __init__.py, I can see among
the many, many other lines, the relevant one:

'{3050F613-98B5-11CF-BB82-00AA00BDCE0B}' : 'HTMLDocumentEvents2'


But how do I instantiate and call genpy.Generator, or arrange for
it to be instantiated and called, to ensure the HTMLDocumentEvents2.py
gets built in the bForDemand setting...? Is there a canonical/
recommended way...?

I can get the file _built_ by faking, once, a
DispatchWithEvents('htmlfile',SomeJunkClass),
which I'm sure is not the 'clean' way, but that doesn't insert
HTMLDocumentEvents2 into the html_module's object directory,
anyway.

So, what *is* the beautiful way I'm still missing after all
of my study of sources...? Pretty please...?


Alex

Paul Moore

unread,
Dec 21, 2000, 11:40:37 AM12/21/00
to
On Thu, 21 Dec 2000 05:59:32 +0100, Mark Hammond

<Ma...@ActiveState.com> wrote:
>Paul Moore wrote:
>> Can I rewrap it somehow, say as
>> doc = DispatchWithEvents(ie.document, DocEvents)
>
>This is (should be) as simple as instantiating a sub-class of the makepy
>generated event class, and passing the document.
>
>eg, something like:
>
>mod = EnsureModule(...)
>
>class MyEvents(mod.IDocumentEvents):
> # your methods here....
>
>handler = MyEvents(ie.document)
>
># handler should start recieving events.

Wonderful! I'll try this later. I am continually impressed by how
powerful all of this stuff is. It's just a bit daunting knowing how to
find these things out... (But with the prompt and helpful responses
from you, that's not an issue, other than the fact that I start to
become a nuisance :-)

Happy Christmas,
Paul.

Paul Moore

unread,
Dec 21, 2000, 11:42:44 AM12/21/00
to

I'll see what I can do. The problem is that my small test case
currently relies on a non-standard control. I'll see if I can get a
test in IE or Excel or something...

Paul.

Mark Hammond

unread,
Dec 21, 2000, 9:57:50 PM12/21/00
to
Alex Martelli wrote:

> Yes, but -- how does this play with the bForDemand flag, which
> is really a must when the module is as huge as MSHTML...?

Hrm. Good question.

> Sure, once I managed to somehow grab the right module it's very
> easy; but I think I've not yet grasped how to do that grabbing
> in the current 'bForDemand' architecture (indispensable for
> really huge type libraries, of course). For example, consider
> the following attempt:

with bForDemand, there are 2 distinct steps - getting the module and
getting the class. You are correct that the code I outlined wont work
there.

The module is _always_ created at makepy time, and references to all
subordinate items created at that time.

> But how do I instantiate and call genpy.Generator, or arrange for
> it to be instantiated and called, to ensure the HTMLDocumentEvents2.py
> gets built in the bForDemand setting...? Is there a canonical/
> recommended way...?

gencache.GetClassForProgID() and GetClassForCLSIDID - however, the
"EnsureModule" will already need to have been run. There is no
single-step generate-module-for-demand-and-get-a-specific-class.

I would be more than happy for us to step back and look at the
makepy/gencache exposed API - I agree it sucks, as it "just grew" and
was really never designed for direct use by client code - it just
happened that way.

I would be happy for discussion either here, or on the Python COM
developers list at
http://mailman.pythonpros.com/mailman/listinfo/pycom-dev - that is very
quiet tho - not much COM work is going on at the moment.

Mark.

Alex Martelli

unread,
Dec 22, 2000, 5:23:48 AM12/22/00
to
"Mark Hammond" <Ma...@ActiveState.com> wrote in message
news:3A42C1F7...@ActiveState.com...
[snip]

> The module is _always_ created at makepy time, and references to all
> subordinate items created at that time.

You mean that makepy doesn't use bForDemand, or am I misreading
you?


> > But how do I instantiate and call genpy.Generator, or arrange for
> > it to be instantiated and called, to ensure the HTMLDocumentEvents2.py
> > gets built in the bForDemand setting...? Is there a canonical/
> > recommended way...?
>
> gencache.GetClassForProgID() and GetClassForCLSIDID - however, the

But HTMLDocumentEvents2 doesn't have a ProgID -- it's an interface,
not a coclass, much less a creatable one. So how to best find out
the CLSID (IID, really -- but it's still a GUID anyway, so I guess
that would work just as well) from the interface-name, which is
what one normally gets out of the docs? Linear search through the
keys of CLSIDToPackageMap in the module...? That's over 400
entries for MSHTML -- it could be a bit slow. Would it be better
to search for it manually and hardwire it as a constant in one's
Python code?

> "EnsureModule" will already need to have been run. There is no
> single-step generate-module-for-demand-and-get-a-specific-class.

I don't really see this as a problem in this context -- it's
pretty easy to roll up one's own. Anyway, this version works
just fine (and thanks for your advice!):


import sys, win32com.client.gencache, pythoncom

def EnsureModuleForTypelibOfObject(obj, bForDemand=1):
try:
ti = obj._oleobj_.GetTypeInfo()
clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
return win32com.client.gencache.EnsureModule(
tla[0], tla[1], tla[3], tla[4], bForDemand=bForDemand)
except pythoncom.com_error:
raise TypeError, "This COM object can not automate the makepy
process - please run makepy manually for this object"

ie = win32com.client.gencache.EnsureDispatch("InternetExplorer.Application")
ie.Navigate('c:/index.htm')

ie.Visible=-1

html_module = EnsureModuleForTypelibOfObject(ie.Document)

itf = win32com.client.gencache.GetClassForCLSID(
'{3050F613-98B5-11CF-BB82-00AA00BDCE0B}')

class Handler(itf):


def __init__(self, obj):
self.seen = 0

itf.__init__(self, obj)
def Ononclick(self, ev):
self.seen += 1
ev = win32com.client.gencache.EnsureDispatch(ev)
el = ev.srcElement
print 'clic on %s(%s) "%s", at (%d,%d)' % (
el.tagName, el.id, el.innerText,
ev.clientX, ev.clientY)

h = Handler(ie.Document)

while h.seen < 3:
pythoncom.PumpWaitingMessages()

ie.Quit()


The hard-wiring of '{3050F613-98B5-11CF-BB82-00AA00BDCE0B}' is
the only part of this that seems like a doubtful idea -- though
maybe it's actually better than it 'feels', since IID's *are*
'hardwired forever' in COM's architecture. But it sure would
be nice to be able to say 'HTMLDocumentEvents2' (in the context
of the typelibrary in which it is thus named, i.e. the
html_module object in this case). Maybe I'm just worrying
uselessly! The variant...:

html_module = EnsureModuleForTypelibOfObject(ie.Document)

import time
start = time.clock()
itfName = 'HTMLDocumentEvents2'
for guid, name in html_module.CLSIDToPackageMap.items():
if itfName==name:
itfID = guid
break
else:
raise KeyError, "%s not found in %s" % (itfName, html_module)
stend = time.clock()

print "GUID found, time %f" % (stend-start)

itf = win32com.client.gencache.GetClassForCLSID(itfID)


seems to also work just fine, with less than 6 milliseconds
"wasted" looking for the interface's GUID (which happens
to be around the middle of the package map's 400 entries,
so this should be rather typical). So one could just, worst
case, package this up as a name-to-GUID-within-module helper
function, say:

def name2GUID(module, classname):
for guid, name in html_module.CLSIDToPackageMap.items():
if class==name: return guid
return None

and live happily ever after (without real performance-driven
need to actually provide a reverse-mapping data structure).


One thing I really don't understand is why the above script
doesn't work right any more if I comment out the statement:

ev = win32com.client.gencache.EnsureDispatch(ev)

in Ononclick -- the srcElement &c properties of the event
object are then not found any more (the resulting exception
is correctly caught in upper levels, so the script runs
until three clicks have been recorded, but I don't get
the print at each click any more). Shouldn't the "pure
dispatch" ev object be _equivalent_ to the genpy-covered
one which I get through this statement, at least for
such simple stuff as getting object-properties...?


> I would be more than happy for us to step back and look at the
> makepy/gencache exposed API - I agree it sucks, as it "just grew" and
> was really never designed for direct use by client code - it just
> happened that way.

That's the sign of successful software -- it grows and develops
beyond what its creator had originally conceived! And win32com
most definitely IS successful -- it spans the powerful worlds of
COM and Python to deliver truly awesome power... a whole even
greater than the already-impressive sum of its parts.

I also fully agree that some refactoring of the API's exposed
to the application programmer would be great. The HTML/XML
DOM's, and Office applications' object models, stress any
Automation framework's design parameters, being so huge and
rich (the bForDemand innovation was GREAT from this POV!).

Examining typical IE/HTML/XML use cases, we might come up
with some reasonably palatable packaging. The 'meat' IS
all there, after all.


> I would be happy for discussion either here, or on the Python COM
> developers list at
> http://mailman.pythonpros.com/mailman/listinfo/pycom-dev - that is very
> quiet tho - not much COM work is going on at the moment.

I guess pycom-dev would be more appropriate, to avoid boring
all of the c.l.p readers ... I'll wait for you to 'open the
dances' there!-)


Alex

0 new messages