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.
onLoad
callback is an instance of win32com.gen_py.Microsoft Office 16.0 Object Library.IRibbonUI
_oleobj_
value is a PyIDispatch
instanceDispatchBaseClass
in win32com.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__
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.
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?
Sigh, looks like the global variable option is the recommended route
...
For example, in your ribbon XML you would add the onLoad action to the customUI element as follows (assuming your code is in a module named your_module.py):<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="your_module.on_load" > <!-- your ribbon definition here --> </customUI>
Then in your module ("your_module.py" is used above) you would add the action function "on_load" that takes the IRibbonUI object. You can then save it as a global variable so that you can call the "Invalidate" method later.
_ribbon = None def on_load(control): global _ribbon _ribbon = control # Later when you want to invalidate a control you would call _ribbon.InvalidateControl(control_id)