Embedding MS Word in a wxPython application

Hello wxPython MSW users,

Goal

My ultimate goal is to embed a MS Word editor in a wxPython application (I want Word to appear as one of the controls inside my wx.Frame).

Background

Searching on the web it seems that this is indeed possible using something called Active X and/or COM components. (Pardon my naivete here, and in the rest of this long post: I typically do all my work on Linux and I have very little experience with MSW. So while am fairly experienced with wxWidgets and wxPython I know virtually nothing of MSW API’s, or the portions of wxPython that integrate with MSW API’s)

What I have learned and tried:

1. Launch Word in its own window from Python (this works)

As a first step, I am able to open a MS Word window on its own and send commands via API. For example using:

from time import sleep
import win32com.client as win32
RANGE = range(3, 8)
def word():
    word = win32.gencache.EnsureDispatch('Word.Application')
    doc = word.Documents.Add()
    word.Visible = True
    sleep(1)
    rng = doc.Range(0,0)
    rng.InsertAfter('Hacking Word with Python\r\n\r\n')
    sleep(1)
    for i in RANGE:
        rng.InsertAfter('Line %d\r\n' % i)
        sleep(1)
    rng.InsertAfter("\r\nPython rules!\r\n")
    doc.Close(False)
    word.Application.Quit()
if __name__ == '__main__':
    word()

(I did not write this: I found it on the web).

The program is self evident to me minus the magic win32.gencache.EnsureDispatch('Word.Application') which I assume calls some Windows API to launch the word executable and get a handle to it to which we can make API calls. By the way, where can I find the documentation to that API?, or at least, what is it called? (If I knew the right terminology I could probably find the documentation).

2. Launch iewin example (mostly works minus a recurring MessageTranslation exception)

My next step is to make that window appear inside a wxPython UI instead of on its own. From I have read, the wx.lib.activex module is the preferred way to go about doing this. Is this correct? (There is not much documentation on the wxPython site about doing this and this module in particular. And all the posts I can find are ancient. I am hoping this means this module is really easy to use, not that it doesn’t work :wink: )

To begin with I am trying to run the iewin sample which I found mentioned on the web. It sort of works but it raises lots of errors of this type.

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\site-packages\wx\lib\activex.py", line 155, in MSWTranslateMessage
    res = self.ipao.TranslateAccelerator(msg)
ctypes.ArgumentError: argument 1: TypeError: wrong type

Am I missing something? (In case it helps I am running Windows 10 on a VM inside VirtualBox. The VM has Edge, not Internet Explorer, but I am assuming the API is compatible? (MSW is famous for backwards compatibility). I also do have MS Word 2010 installed in it.

(By the way, I have been looking at the code of activex.py and I notice that, coincidentally, its last modification by Robin, from a couple of years ago, actually involved catching an OSError in this same method MSWTranslateMessage)

I have also found the flashwin and pdfwin examples. They both raise an error and don’t even open a window, but I am not surprised because I don’t think I have flash (is that still a thing?) and I definitely don’t have Adobe Reader installed. Anyhow the code in both looks analogous to that of iewin.

3. Create a control that embeds MS Word (I am stuck)

Next. I have tried to create my own MSWord control that I can use in my UI.

_progID = 'Word.Application'

import wx
import wx.lib.activex

class MSWord(wx.lib.activex.ActiveXCtrl):
    def __init__(self, parent, ID=wx.ID_ANY,
             pos=wx.DefaultPosition, size=wx.DefaultSize,
             style=0):
    	super().__init__(parent, _progID, ID, pos, size, style)

This fails with

2023-02-10 18:44:34 INFO [comtypes.client._code_cache]: Could not import comtypes.gen, trying to create it.
2023-02-10 18:44:34 INFO [comtypes.client._code_cache]: Creating comtypes.gen package failed: [WinError 5] Access is denied: 'C:\\Program Files\\Python310\\lib\\site-packages\\comtypes\\gen'
2023-02-10 18:44:34 INFO [comtypes.client._code_cache]: Created a memory-only package.
2023-02-10 18:44:34 INFO [comtypes.client._code_cache]: Using writeable comtypes cache directory: 'C:\Users\labqa\AppData\Roaming\Python\Python310\comtypes_cache'
Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "Z:\meticy-client\src\meticy\devtools\widgets\msword.py", line 57, in <module>
    TEST_FRAME = MSWordTestFrame(None, title="Meticy MS Word Editor", size=wx.Size(800, 600))
  File "Z:\meticy-client\src\meticy\devtools\widgets\msword.py", line 28, in __init__
    self.viewer = MSWord(self, wx.ID_ANY, wx.DefaultPosition)
  File "Z:\meticy-client\src\meticy\ui\widgets\msword.py", line 30, in __init__
    super().__init__(parent, _progID, ID, pos, size, style)
  File "C:\Program Files\Python310\lib\site-packages\wx\lib\activex.py", line 110, in __init__
    self.ipao = self._ax.QueryInterface(myole4ax.IOleInPlaceActiveObject)
  File "C:\Program Files\Python310\lib\site-packages\comtypes\__init__.py", line 1197, in QueryInterface
    self.__com_QueryInterface(byref(iid), byref(p))
_ctypes.COMError: (-2147467262, 'No such interface supported', (None, None, None, 0, None))

I notice that the iewin example and the others invoke com.components.GetModule passing some magic registry key. Is it necessary? What does it do? Why does the program that opens Word in its own window not need it?

If a key is necessary to embed Word in wxPython, how does one know which key to use? From the pdfwin sample I gather the key may depend on the version of MS Word installed. Is this so? Can the relevant key for the installed version be retrieved programmatically from python?

Thank you in advance for answering my beginner questions and pointing me in the right direction!

and just running Word in a subprocess is not enough :face_with_hand_over_mouth:

I don’t know about activex (Is this going to be EOL?), but you can embed “Microsoft 365” using html2.

import wx
import wx.html2

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.browser = wx.html2.WebView.New(self)

        self.Bind(wx.html2.EVT_WEBVIEW_NAVIGATING,
                  lambda v: self.SetTitle(v.URL))

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None)
    frm.browser.LoadURL("https://www.office.com/?auth=1") # Microsoft 365
    frm.Show()
    app.MainLoop()

Thank you @komoto48g. My customer uses an older, desktop application, version of Word and this won’t work, but it is good to know for the future.

Does anyone know of an example of embedding the older, “desktop”, office applications?

sure :rofl:

The best source of information for these things is probably still “Python Programming On Win32” by Andy Robinson and Mark Hammond.

Thank you @DietmarSchwertberger I found an online reference to the boot at Python-Programming-on-Win32 and it is definitely helpful! I haven’t yet been able to embed word inside wxPython but I am slowly building up my knowledge.