Skip to content
Merged
130 changes: 60 additions & 70 deletions comtypes/test/test_eventinterface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import gc
import time
import unittest as ut
from ctypes import byref
from ctypes import HRESULT, byref
from ctypes.wintypes import MSG

from comtypes import COMMETHOD, GUID, IUnknown
from comtypes.automation import DISPID
from comtypes.client import CreateObject, GetEvents
from comtypes.messageloop import (
PM_REMOVE,
Expand All @@ -10,42 +14,39 @@
TranslateMessage,
)

# FIXME: External test dependencies like this seem bad. Find a different
# built-in win32 API to use.
# The primary goal is to verify how `GetEvents` behaves when the
# `interface` argument is explicitly specified versus when it is omitted,
# using an object that has multiple outgoing event interfaces.


class IPropertyNotifySink(IUnknown):
# https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ipropertynotifysink
_iid_ = GUID("{9BFBBC02-EFF1-101A-84ED-00AA00341D07}")
_methods_ = [
# Called when a property has changed.
COMMETHOD([], HRESULT, "OnChanged", (["in"], DISPID, "dispid")),
# Called when an object wants to know if it's okay to change a property.
COMMETHOD([], HRESULT, "OnRequestEdit", (["in"], DISPID, "dispid")),
]


class EventSink:
def __init__(self):
self._events = []

# some DWebBrowserEvents
def OnVisible(self, this, *args):
# print "OnVisible", args
self._events.append("OnVisible")

def BeforeNavigate(self, this, *args):
# print "BeforeNavigate", args
self._events.append("BeforeNavigate")
# Events from the default dispatch interface
def onreadystatechange(self, this, *args):
self._events.append("onreadystatechange")

def NavigateComplete(self, this, *args):
# print "NavigateComplete", args
self._events.append("NavigateComplete")
def ondataavailable(self, this, *args):
self._events.append("ondataavailable")

# some DWebBrowserEvents2
def BeforeNavigate2(self, this, *args):
# print "BeforeNavigate2", args
self._events.append("BeforeNavigate2")
# Events from `IPropertyNotifySink`
def OnChanged(self, this, *args):
self._events.append("OnChanged")

def NavigateComplete2(self, this, *args):
# print "NavigateComplete2", args
self._events.append("NavigateComplete2")

def DocumentComplete(self, this, *args):
# print "DocumentComplete", args
self._events.append("DocumentComplete")
def OnRequestEdit(self, this, *args):
self._events.append("OnRequestEdit")


def PumpWaitingMessages():
Expand All @@ -55,70 +56,59 @@ def PumpWaitingMessages():
DispatchMessage(byref(msg))


class Test(ut.TestCase):
def tearDown(self):
import gc
class Test_MSXML(ut.TestCase):
def setUp(self):
# We use `Msxml2.DOMDocument` because it is a built-in Windows
# component that supports both a default source interface and the
# `IPropertyNotifySink` connection point.
self.doc = CreateObject("Msxml2.DOMDocument")
self.doc.async_ = True

def tearDown(self):
del self.doc
# Force garbage collection and wait slightly to ensure COM resources
# are released properly between tests.
gc.collect()
import time

time.sleep(2)

@ut.skip(
"External test dependencies like this seem bad. Find a different built-in "
"win32 API to use."
)
def test_default_eventinterface(self):
# Verify that `GetEvents` automatically connects to the default source
# interface (dispatch events like `onreadystatechange`) when no
# interface is explicitly requested.
sink = EventSink()
ie = CreateObject("InternetExplorer.Application")
conn = GetEvents(ie, sink=sink)
ie.Visible = True
ie.Navigate2(URL="http://docs.python.org/", Flags=0)
import time
conn = GetEvents(self.doc, sink)
self.doc.loadXML("<root/>")

for i in range(50):
# Give the message loop time to process incoming events.
for _ in range(50):
PumpWaitingMessages()
time.sleep(0.1)
ie.Visible = False
ie.Quit()

self.assertEqual(
sink._events,
[
"OnVisible",
"BeforeNavigate2",
"NavigateComplete2",
"DocumentComplete",
"OnVisible",
],
)

del ie
# Should receive events from the default dispatch interface, but not
# events from `IPropertyNotifySink`.
self.assertIn("onreadystatechange", sink._events)
self.assertNotIn("OnChanged", sink._events)

del conn

@ut.skip(
"External test dependencies like this seem bad. Find a different built-in "
"win32 API to use."
)
def test_nondefault_eventinterface(self):
# Verify that `GetEvents` can connect to a non-default interface
# (like `IPropertyNotifySink`) when it is explicitly provided.
sink = EventSink()
ie = CreateObject("InternetExplorer.Application")
import comtypes.gen.SHDocVw as mod

conn = GetEvents(ie, sink, interface=mod.DWebBrowserEvents)
conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink)

ie.Visible = True
ie.Navigate2(Flags=0, URL="http://docs.python.org/")
import time
self.doc.loadXML("<root/>")

for i in range(50):
# Give the message loop time to process incoming events.
for _ in range(50):
PumpWaitingMessages()
time.sleep(0.1)
ie.Visible = False
ie.Quit()
# Should receive events from `IPropertyNotifySink`, but not events from
# the default dispatch interface.
self.assertNotIn("onreadystatechange", sink._events)
self.assertIn("OnChanged", sink._events)

self.assertEqual(sink._events, ["BeforeNavigate", "NavigateComplete"])
del ie
del conn


if __name__ == "__main__":
Expand Down