"""
Helper module for loading an ActiveX library.
The module also defines all the `Window Styles`_ and `Extended Window Styles`_ constants.
.. _Window Styles: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
.. _Extended Window Styles: https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
"""
import ctypes
try:
import clr
import System
clr.AddReference('System.Windows.Forms')
import System.Windows.Forms as Forms
except:
clr = None
try:
from comtypes import GUID
from comtypes import IUnknown
from comtypes.client import GetBestInterface
from comtypes.client import GetEvents
except:
GUID = None
CW_USEDEFAULT = 0x80000000
# Window Styles
WS_OVERLAPPED = 0x00000000
WS_POPUP = 0x80000000
WS_CHILD = 0x40000000
WS_MINIMIZE = 0x20000000
WS_VISIBLE = 0x10000000
WS_DISABLED = 0x08000000
WS_CLIPSIBLINGS = 0x04000000
WS_CLIPCHILDREN = 0x02000000
WS_MAXIMIZE = 0x01000000
WS_CAPTION = 0x00C00000
WS_BORDER = 0x00800000
WS_DLGFRAME = 0x00400000
WS_VSCROLL = 0x00200000
WS_HSCROLL = 0x00100000
WS_SYSMENU = 0x00080000
WS_THICKFRAME = 0x00040000
WS_GROUP = 0x00020000
WS_TABSTOP = 0x00010000
WS_MINIMIZEBOX = 0x00020000
WS_MAXIMIZEBOX = 0x00010000
WS_TILED = WS_OVERLAPPED
WS_ICONIC = WS_MINIMIZE
WS_SIZEBOX = WS_THICKFRAME
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU
WS_CHILDWINDOW = WS_CHILD
WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW
# Extended Window Styles
WS_EX_DLGMODALFRAME = 0x00000001
WS_EX_NOPARENTNOTIFY = 0x00000004
WS_EX_TOPMOST = 0x00000008
WS_EX_ACCEPTFILES = 0x00000010
WS_EX_TRANSPARENT = 0x00000020
WS_EX_MDICHILD = 0x00000040
WS_EX_TOOLWINDOW = 0x00000080
WS_EX_WINDOWEDGE = 0x00000100
WS_EX_CLIENTEDGE = 0x00000200
WS_EX_CONTEXTHELP = 0x00000400
WS_EX_RIGHT = 0x00001000
WS_EX_LEFT = 0x00000000
WS_EX_RTLREADING = 0x00002000
WS_EX_LTRREADING = 0x00000000
WS_EX_LEFTSCROLLBAR = 0x00004000
WS_EX_RIGHTSCROLLBAR = 0x00000000
WS_EX_CONTROLPARENT = 0x00010000
WS_EX_STATICEDGE = 0x00020000
WS_EX_APPWINDOW = 0x00040000
WS_EX_LAYERED = 0x00080000
WS_EX_NOINHERITLAYOUT = 0x00100000
WS_EX_NOREDIRECTIONBITMAP = 0x00200000
WS_EX_LAYOUTRTL = 0x00400000
WS_EX_COMPOSITED = 0x02000000
WS_EX_NOACTIVATE = 0x08000000
WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE
WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST
[docs]class Application(Forms.Form):
def __init__(self):
"""Create the main application window to display ActiveX controls.
Creating an application requires pythonnet_ to be installed.
See Form_ for more details.
.. _Form: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.form
.. _pythonnet: https://pypi.org/project/pythonnet/
.. invisible-code-block: pycon
>>> SKIP_IF_NOT_WINDOWS() or SKIP_IF_NO_PYTHONNET()
Examples
--------
>>> from msl.loadlib.activex import Application
>>> app = Application()
>>> app.create_panel()
<System.Windows.Forms.Panel object at ...>
"""
super(Application, self).__init__()
if clr is None:
raise RuntimeError(
'Creating an ActiveX application requires pythonnet to be installed.\n'
'Run: pip install pythonnet'
)
self._event_connections = []
[docs] def handle_events(self, source, sink=None, interface=None):
"""Handle events from an ActiveX object.
Parameters
----------
source
The ActiveX object that emits events.
sink
The object that handles the events. The `sink` must
define methods with the same names as the ActiveX event names.
If not specified then uses the calling application instance
as the `sink`.
interface
The interface to use.
Returns
-------
The connection object.
"""
cxn = GetEvents(source, sink or self, interface=interface)
self._event_connections.append(cxn)
return cxn
[docs] @staticmethod
def create_panel():
"""Create a new `Panel <https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.panel>`_."""
return Forms.Panel()
[docs] @staticmethod
def load(activex_id, parent=None, x=0, y=0, width=0, height=0, style=0, ex_style=0):
"""Load an ActiveX library.
Additional information about the keyword arguments are described by the
CreateWindowExA_ object.
Loading an ActiveX library requires comtypes_ to be installed.
.. _comtypes: https://pypi.org/project/comtypes/
.. _CreateWindowExA: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
.. _System.Windows.Forms: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms
Parameters
----------
activex_id : :class:`str`
The ProgID or CLSID of the ActiveX object.
parent
The parent or owner window of the window being created.
The parent is typically an object from the System.Windows.Forms_
namespace that has a ``Handle`` property.
x : :class:`int`, optional
The initial horizontal position of the window.
y : :class:`int`, optional
The initial vertical position of the window.
width : :class:`int`, optional
The width of the window.
height : :class:`int`, optional
The height of the window.
style : :class:`int`, optional
The style of the window being created. This argument can be a
combination of the `Window Styles`_ constants, e.g.,
``style = WS_CHILD | WS_VISIBLE``.
ex_style : :class:`int`, optional
The extended window style of the window being created. This argument can be a
combination of the `Extended Window Styles`_ constants, e.g.,
``ex_style = WS_EX_APPWINDOW | WS_EX_CONTEXTHELP``.
Returns
-------
The interface pointer to the ActiveX library.
Raises
------
OSError
If the library cannot be loaded.
"""
if GUID is None:
raise OSError(
'Cannot load an ActiveX library because comtypes is not installed.\n'
'Run: pip install comtypes'
)
try:
clsid = GUID.from_progid(activex_id)
except (TypeError, OSError):
clsid = None
if clsid is None:
raise OSError("Cannot find '{}' for libtype='activex'".format(activex_id))
if parent is not None:
try:
parent_handle = parent.Handle.ToInt32()
except AttributeError:
parent_handle = None
if parent_handle is None:
raise OSError('Cannot create a Handle from the parent {}'.format(type(parent)))
else:
parent_handle = None
# calling AtlAxWinInit initializes ATL's control hosting code
# by registering the "AtlAxWin" window class so that this window
# class is available to the CreateWindowExA function
h_result = ctypes.windll.atl.AtlAxWinInit()
if not h_result:
raise OSError('Cannot register the "AtlAxWin" window class')
# create a new window
h_instance = ctypes.windll.kernel32.GetModuleHandleA(None)
hwnd = ctypes.windll.user32.CreateWindowExA(
ex_style, # dwExStyle
b'AtlAxWin', # lpClassName
str(clsid).encode(), # lpWindowName
style, # dwStyle
x, # X
y, # Y
width, # nWidth
height, # nHeight
parent_handle, # hWndParent
None, # hMenu
h_instance, # hInstance
0 # lpParam
)
if hwnd == 0:
raise OSError('CreateWindowExA {}'.format(ctypes.WinError()))
# get the interface to the ActiveX control
unknown = ctypes.POINTER(IUnknown)()
ret = ctypes.windll.atl.AtlAxGetControl(hwnd, ctypes.byref(unknown))
if ret != 0:
raise OSError('AtlAxGetControl {}'.format(ctypes.WinError()))
return GetBestInterface(unknown)