I'm trying to invalidate specific controls on an Excel add-in ribbon using Python's win32com
package in order to have their callbacks re-fire.
Reading over Microsoft's Object References and the IRibbonUI documentation all I've seen recommended is to save a reference to the ribbon in its onLoad
callback. This is also how I've accomplished this in the past when coding in VBA. Unfortunately, I'm not sure how to get to that point using python's win32com
package.
I'm using PyXLL as a means to code the add-in using Python and for future distribution as an xll instead of xlam. By using this I'm able to access the ribbon com object in the ribbon onLoad
callback, but not within the onClick
callbacks of the controls included in the ribbon, since the only parameter passed into those functions is the control's com object.
Some relavent debugging info
- parameter passed into the ribbon
onLoad
callback is an instance ofwin32com.gen_py.Microsoft Office 16.0 Object Library.IRibbonUI
- the parameter's
_oleobj_
value is aPyIDispatch
instance - The errors seems to center around the
DispatchBaseClass
inwin32com.client
class DispatchBaseClass:
def __init__(self, oobj=None):
if oobj is None:
oobj = pythoncom.new(self.CLSID)
elif isinstance(oobj, DispatchBaseClass):
try:
oobj = oobj._oleobj_.QueryInterface(
self.CLSID, pythoncom.IID_IDispatch
) # Must be a valid COM instance
except pythoncom.com_error as details:
import winerror
# Some stupid objects fail here, even tho it is _already_ IDispatch!!??
# Eg, Lotus notes.
# So just let it use the existing object if E_NOINTERFACE
if details.hresult != winerror.E_NOINTERFACE:
raise
oobj = oobj._oleobj_
self.__dict__["_oleobj_"] = oobj # so we dont call __setattr__
- My thinking is to save a reference to the actual class, recreate it, set the
CLSID
class variable, then instantiate the new class.
def ribbon_loaded(ribbon):
r_class = ribbon.__class__
# was thinking I could pickle this class for use elsewhere
class RibbonUI(r_class): # also tried type(r_class)
CLSID = ribbon.CLSID
# just checking below that this would work
# if CLSID is not set on the new class above __init__ fails
# but, even when provided __new__ fails with the next line
new_ribbon = RibbonUI()
new_ribbon.InvalidateControl("ribbon_control_1")
Without setting CLSID
on the new class the error is
2022-07-06 20:48:09,294 - INFO : ribbon.CLSID: IID('{000C03A7-0000-0000-C000-000000000046}')
2022-07-06 20:48:09,314 - ERROR : Error calling ribbon function 'components.ribbon.ribbon_loaded'
2022-07-06 20:48:15,364 - ERROR : Traceback (most recent call last):
2022-07-06 20:48:15,364 - ERROR : File "C:\Users\mhill\PycharmProjects\excel-addin\components\ribbon.py", line 46, in ribbon_loaded
2022-07-06 20:48:15,365 - ERROR : new_ribbon = RibbonUI()
2022-07-06 20:48:15,366 - ERROR : File "c:\users\mhill\PyCharmProjects\excel-addin\.venv\lib\site-packages\win32com\client\__init__.py", line 514, in __init__
2022-07-06 20:48:15,366 - ERROR : oobj = pythoncom.new(self.CLSID)
2022-07-06 20:48:15,366 - ERROR : pywintypes.com_error: (-2147221164, 'Class not registered', None, None)
When creating the new class and specifying the the CLSID
class variable the error becomes
2022-07-06 20:48:22,429 - INFO : ribbon.CLSID: IID('{000C03A7-0000-0000-C000-000000000046}')
2022-07-06 20:48:22,429 - ERROR : Error calling ribbon function 'components.ribbon.ribbon_loaded'
2022-07-06 20:48:28,949 - ERROR : Traceback (most recent call last):
2022-07-06 20:48:28,950 - ERROR : File "C:\Users\mhill\PycharmProjects\excel-addin\components\ribbon.py", line 46, in ribbon_loaded
2022-07-06 20:48:28,951 - ERROR : new_ribbon = RibbonUI()
2022-07-06 20:48:28,951 - ERROR : TypeError: type.__new__() takes exactly 3 arguments (0 given)
Related question mentions something about creating an IDTExtensibility2 object, but not quite sure what to do with that.
The question
Apart from mimicking saving a global reference to the ribbon object from within the ribbon's onLoad
callback as Microsoft's documentation suggests, how can I properly save a reference to the IRibbonUI com object when in the ribbon onLoad callback and be able to access it in the controls' onClick
callbacks?