1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
#----------------------------------------------------------------------
# Name: wx.lib.activex
# Purpose: The 3rd (and hopefully final) implementation of an
# ActiveX container for wxPython.
#
# Author: Robin Dunn
#
# Created: 5-June-2008
# RCS-ID: $Id: $
# Copyright: (c) 2008 by Total Control Software
# Licence: wxWindows license
#----------------------------------------------------------------------
"""
This module provides a wx.Window that hosts ActiveX Controls using
just the ctypes and comtypes packages. This provides a light-weight
COM implementation with full dynamic dispatch support.
The only requirements are ctypes (included with Python 2.5 and
available separately for earlier versions of Python) and the comtypes
package, which is available from
http://starship.python.net/crew/theller/comtypes/. Be sure to get at
least version 0.5, which at the time of this writing is only available
from SVN. You can fetch it with easy_install with a command like
this:
easy_install http://svn.python.org/projects/ctypes/trunk/comtypes
"""
import wx
import ctypes as ct
import ctypes.wintypes as wt
import comtypes
import comtypes.client as cc
import comtypes.hresult as hr
import sys, os
if not hasattr(sys, 'frozen'):
f = os.path.join(os.path.dirname(__file__), 'myole4ax.tlb')
cc.GetModule(f)
from comtypes.gen import myole4ax
kernel32 = ct.windll.kernel32
user32 = ct.windll.user32
atl = ct.windll.atl
WS_CHILD = 0x40000000
WS_VISIBLE = 0x10000000
WS_CLIPCHILDREN = 0x2000000
WS_CLIPSIBLINGS = 0x4000000
CW_USEDEFAULT = 0x80000000
WM_KEYDOWN = 256
WM_DESTROY = 2
#------------------------------------------------------------------------------
class ActiveXCtrl(wx.PyAxBaseWindow):
"""
A wx.Window for hosting ActiveX controls. The COM interface of
the ActiveX control is accessible through the ctrl property of
this class, and this class is also set as the event sink for COM
events originating from the ActiveX control. In other words, to
catch the COM events you mearly have to derive from this class and
provide a method with the correct name. See the comtypes package
documentation for more details.
"""
def __init__(self, parent, axID, wxid=-1, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0, name="activeXCtrl"):
"""
All parameters are like those used in normal wx.Windows with
the addition of axID which is a string that is either a ProgID
or a CLSID used to identify the ActiveX control.
"""
pos = wx.Point(*pos) # in case the arg is a tuple
size = wx.Size(*size) # ditto
x = pos.x
y = pos.y
if x == -1: x = CW_USEDEFAULT
if y == -1: y = 20
w = size.width
h = size.height
if w == -1: w = 20
if h == -1: h = 20
# create the control
atl.AtlAxWinInit()
hInstance = kernel32.GetModuleHandleA(None)
hwnd = user32.CreateWindowExA(0, "AtlAxWin", axID,
WS_CHILD | WS_VISIBLE
| WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
x,y, w,h, parent.GetHandle(), None,
hInstance, 0)
assert hwnd != 0
# get the Interface for the Ax control
unknown = ct.POINTER(comtypes.IUnknown)()
res = atl.AtlAxGetControl(hwnd, ct.byref(unknown))
assert res == hr.S_OK
self._ax = cc.GetBestInterface(unknown)
# Fetch the interface for IOleInPlaceActiveObject. We'll use this
# later to call its TranslateAccelerator method so the AX Control can
# deal with things like tab traversal and such within itself.
self.ipao = self._ax.QueryInterface(myole4ax.IOleInPlaceActiveObject)
# Use this object as the event sink for the ActiveX events
self._evt_connections = []
self.AddEventSink(self)
# Turn the window handle into a wx.Window and set this object to be that window
win = wx.PyAxBaseWindow_FromHWND(parent, hwnd)
self.PostCreate(win)
# Set some wx.Window properties
if wxid == wx.ID_ANY:
wxid = wx.Window.NewControlId()
self.SetId(wxid)
self.SetName(name)
self.SetMinSize(size)
self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroyWindow)
def AddEventSink(self, sink, interface=None):
"""
Add a new target to search for method names that match the COM
Event names.
"""
self._evt_connections.append(cc.GetEvents(self._ax, sink, interface))
def GetCtrl(self):
"""Easy access to the COM interface for the ActiveX Control"""
return self._ax
# And an even easier property
ctrl = property(GetCtrl)
def MSWTranslateMessage(self, msg):
# Pass native messages to the IOleInPlaceActiveObject
# interface before wx processes them, so navigation keys and
# accelerators can be dealt with the way that the AXControl
# wants them to be done. MSWTranslateMessage is called before
# wxWidgets handles and eats the navigation keys itself.
res = self.ipao.TranslateAccelerator(msg)
if res == hr.S_OK:
return True
else:
return wx.PyAxBaseWindow.MSWTranslateMessage(self, msg)
# TBD: Are the focus handlers needed?
def OnSetFocus(self, evt):
self.ipao.OnFrameWindowActivate(True)
def OnKillFocus(self, evt):
self.ipao.OnFrameWindowActivate(False)
def OnDestroyWindow(self, evt):
# release our event sinks while the window still exists
self._evt_connections = None
#------------------------------------------------------------------------------
|