IRibbonExtensibility callback problem

Z

zen35111

Hi

I have an unmanaged C++ COM addin for Word that and I'm attempting to
add a new tab to the Office 2007 ribbon.

Although my new ribbon tab displays (i.e. GetCustomUI is called and the
xml is OK) , Word reports that the callback for OnAction is 'not found'
when I click any buttons.

My class implements IRibbonExtensibility:

public IDispatchImpl<IRibbonExtensibility, &IID_IRibbonExtensibility,
&LIBID_MyIRibbonExtensibility, /*wMajor =*/ 1, /*wMinor =*/ 0>

and includes

COM_INTERFACE_ENTRY(IRibbonExtensibility)

in the COM map, so I don't see why the callback isn't found.

In the xml, the OnAction entry is onAction="ButtonHandler" and the
corresponding class function is

STDMETHOD(ButtonHandler)(IRibbonControl *RibbonControl)
{
....
}

Having read every article on IRibbonExtensibility on Google and tried
everything within them (and much more besides) I'm now at a dead-end.

Incidentally, I created a c# addin with the same xml and that works
fine, but I would have the same issues as above when creating a shim,
so this is'nt a solution.

So, if there is anyone on the planet that knows anything at all about
this, I would be very grateful for any help or advice.

Regards

Steve H
 
P

Patrick Schmid [MVP]

Your callback is wrong:
C++: HRESULT OnAction([in] IRibbonControl *pControl)

From this document:
http://msdn2.microsoft.com/en-us/library/aa722523.aspx

Patrick Schmid [OneNote MVP]
--------------
http://pschmid.net
***
Office 2007 RTM Issues: http://pschmid.net/blog/2006/11/13/80
Office 2007 Beta 2 Technical Refresh (B2TR):
http://pschmid.net/blog/2006/09/18/43
***
Customize Office 2007: http://pschmid.net/office2007/customize
OneNote 2007: http://pschmid.net/office2007/onenote
***
Subscribe to my Office 2007 blog: http://pschmid.net/blog/feed
 
Z

zen35111

Hi Patrick

Thanks very much for your reply. After some further thought it turned
out that I made the mistake of adding the callback as a member of of
IRibbonExtensibility rather than my class - the confusion arose, in
part, from misinterpretation of
http://blogs.msdn.com/mshneer/archive/2006/05/31/doclevelribbons.aspx.
From the fact that others have made similar mistakes, and that many
people with existing C++ -based COM addins for Office will probably
want to use the Ribbon interface, it would be helpful for Microsoft to
publish a definitive article on extending the Ribbon using C++ / ATL.

Best Regards

Steve H
Your callback is wrong:
C++: HRESULT OnAction([in] IRibbonControl *pControl)

From this document:
http://msdn2.microsoft.com/en-us/library/aa722523.aspx

Patrick Schmid [OneNote MVP]
 
Z

zen35111

Hi

Sorry about the delay in replying - I don't view this NG regularly.

Yes, I did declare my own LIBID, mainly because I didn't want to import
mso.dll for Office 12, to make compatibility with earlier versions of
Office easier.


The idl to generate the tlb looks like this:

//import "oaidl.idl";
//import "ocidl.idl";

[
uuid(716C8EF9-79BC-4113-9117-47BF2E02F089),
version(1.0),
helpstring("s2sIRibbonExtensibility Object Library"),
]

library s2sIRibbonExtensibility
{
importlib("stdole2.tlb");

// Forward declare all types defined in this typelib
interface IRibbonControl;
interface IRibbonExtensibility;

// ============== IRibbonControl
[
odl,
uuid(000C0395-0000-0000-C000-000000000046),
helpcontext(0x00046500),
dual,
oleautomation
]

interface IRibbonControl : IDispatch {
[id(0x00000001), propget, helpcontext(0x00046501)]
HRESULT Id([out, retval] BSTR* Id);
[id(0x00000002), propget, helpcontext(0x00046502)]
HRESULT Context([out, retval] IDispatch** Context);
[id(0x00000003), propget, helpcontext(0x00046503)]
HRESULT Tag([out, retval] BSTR* Tag);
};

// ============== IRibbonExtensibility
[
odl,
uuid(000C0396-0000-0000-C000-000000000046),
helpcontext(0x000468e8),
dual,
oleautomation
]

interface IRibbonExtensibility : IDispatch {
[id(0x00000001), helpcontext(0x000468e9)]
HRESULT GetCustomUI(
[in] BSTR RibbonID,
[out, retval] BSTR* RibbonXml);
};

};


In my addin's class header I have the following relevant entries:

#import ".\2007\IRibbonExtensibility.tlb" named_guids

using namespace Office;
using namespace Word;
using namespace Excel;
using namespace s2sIRibbonExtensibility;

....
....
class ATL_NO_VTABLE Cs2signoa :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Cs2signoa, &CLSID_s2signoa>,
public IDispatchImpl<Is2signoa, &IID_Is2signoa, &LIBID_s2soaLib,
/*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispatchImpl<_IDTExtensibility2, &IID__IDTExtensibility2,
&LIBID_AddInDesignerObjects>,
public IDispatchImpl<IRibbonExtensibility, &IID_IRibbonExtensibility,
&LIBID_s2sIRibbonExtensibility, /*wMajor =*/ 1, /*wMinor =*/ 0>,

....
....

BEGIN_COM_MAP(Cs2signoa)
COM_INTERFACE_ENTRY(Is2signoa)
COM_INTERFACE_ENTRY2(IDispatch, Is2signoa)
COM_INTERFACE_ENTRY(_IDTExtensibility2)
COM_INTERFACE_ENTRY(IRibbonExtensibility)
END_COM_MAP()

....
....

public:
....
// IRibbonExtensibility
STDMETHOD(raw_GetCustomUI)(BSTR RibbonID, BSTR *RibbonXml)
{
HRSRC hr = FindResource (_AtlBaseModule.GetModuleInstance (),
MAKEINTRESOURCE(IDR_XML_DATA1), TEXT("XML_DATA"));
if (hr)
{
HGLOBAL hgres = LoadResource (_AtlBaseModule.GetModuleInstance (),
hr);
if (hgres)
{
LPSTR ptr = (LPSTR)LockResource (hgres);
CComBSTR cbs = ptr;
*RibbonXml = cbs.m_str;
}
}
return S_OK;
}

// for ribbon
STDMETHOD(BtnClick)(IUnknown* pObj);
STDMETHOD(GetImage)(IUnknown* pObj, IPictureDisp ** ppdispImage);


I modified my class's idl to include the following:

interface Is2signoa : IDispatch{
[id(1), helpstring("method BtnClick")] HRESULT BtnClick([in] IUnknown*
pControl);
[id(2), helpstring("method GetImage")] HRESULT GetImage([in] IUnknown*
pControl, [out, retval] IPictureDisp ** ppdispImage);
};

Note that BtnClick and GetImage are in the xml that defines what to
call for the OnAction and GetImage functionality.

The BtnClick function looks like this:

STDMETHODIMP Cs2signoa::BtnClick(IUnknown* pObj)
{
CComQIPtr<IRibbonControl> pRibbonControl;
pRibbonControl = pObj;
_bstr_t bstrID = pRibbonControl->Id;

if (lstrcmp (bstrID, TEXT("Button1")) == 0)
{
// button 1 stuff here
}
... rest omitted for clarity
return S_OK;
}

The GetImage function is complex owing to the *very tedious*
requirement that the button images be in .png format to ensure correct
transparency. The simplest method of handling this is to invoke GDI+,
once the png is laboriously loaded from the application resources

STDMETHODIMP Cs2signoa::GetImage(IUnknown* pObj, IPictureDisp **
ppdispImage)
{
HRESULT hr = S_OK;

CComQIPtr<IRibbonControl> pRibbonControl;
pRibbonControl = pObj;
_bstr_t bstrID = pRibbonControl->Id;

PICTDESC pd;

pd.cbSizeofstruct = sizeof (PICTDESC);
pd.picType = PICTYPE_BITMAP;


GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;

gdiplusStartupInput.DebugEventCallback = NULL;
gdiplusStartupInput.SuppressBackgroundThread = FALSE;
gdiplusStartupInput.SuppressExternalCodecs = FALSE;
gdiplusStartupInput.GdiplusVersion = 1;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);

int idPNG;

if (lstrcmp (bstrID, TEXT("Button1")) == 0)
idPNG = IDR_PNG_DATA1;
... others omitted for clarity

HRSRC hResource = FindResource (_AtlBaseModule.GetResourceInstance (),
MAKEINTRESOURCE(idPNG), TEXT("PNG_DATA"));
if (!hResource)
return hr;

DWORD dwImageSize = SizeofResource (_AtlBaseModule.GetResourceInstance
(), hResource);
const void* pResourceData = LockResource (LoadResource
(_AtlBaseModule.GetResourceInstance (), hResource));
if (!pResourceData)
return hr;

HGLOBAL hBuffer = GlobalAlloc (GMEM_MOVEABLE, dwImageSize);
if (hBuffer)
{
void* pBuffer = GlobalLock (hBuffer);
if (pBuffer)
{
CopyMemory (pBuffer, pResourceData, dwImageSize);

IStream* pStream = NULL;
if :):CreateStreamOnHGlobal (hBuffer, FALSE, &pStream) ==
S_OK)
{
Bitmap *pBitmap = Bitmap::FromStream (pStream);
pStream->Release();

if (pBitmap)
{
pBitmap->GetHBITMAP (0, &pd.bmp.hbitmap);
hr = OleCreatePictureIndirect (&pd, IID_IDispatch, TRUE, (LPVOID
*)ppdispImage);

delete pBitmap;
}
}
GlobalUnlock (pBuffer);
}
GlobalFree (hBuffer);
}

GdiplusShutdown (gdiplusToken);

return hr;
}

Please note that the above code does not include the usual error
handlers and is very much a first attempt, requiring cleaning up,
testing, etc.

Regards

Steve H
 
S

soch

Hi Steve,

Thanks a lot for posting the code.

I was stuck from last two days and this code helped me move forward.

Now, I am able to get the button clicks.

Thanks again for your help.

-Teena

Hi

Sorry about the delay in replying - I don't view this NG regularly.

Yes, I did declare my own LIBID, mainly because I didn't want to import
mso.dll for Office 12, to make compatibility with earlier versions of
Office easier.


The idl to generate the tlb looks like this:

//import "oaidl.idl";
//import "ocidl.idl";

[
uuid(716C8EF9-79BC-4113-9117-47BF2E02F089),
version(1.0),
helpstring("s2sIRibbonExtensibility Object Library"),
]

library s2sIRibbonExtensibility
{
importlib("stdole2.tlb");

// Forward declare all types defined in this typelib
interface IRibbonControl;
interface IRibbonExtensibility;

// ============== IRibbonControl
[
odl,
uuid(000C0395-0000-0000-C000-000000000046),
helpcontext(0x00046500),
dual,
oleautomation
]

interface IRibbonControl : IDispatch {
[id(0x00000001), propget, helpcontext(0x00046501)]
HRESULT Id([out, retval] BSTR* Id);
[id(0x00000002), propget, helpcontext(0x00046502)]
HRESULT Context([out, retval] IDispatch** Context);
[id(0x00000003), propget, helpcontext(0x00046503)]
HRESULT Tag([out, retval] BSTR* Tag);
};

// ============== IRibbonExtensibility
[
odl,
uuid(000C0396-0000-0000-C000-000000000046),
helpcontext(0x000468e8),
dual,
oleautomation
]

interface IRibbonExtensibility : IDispatch {
[id(0x00000001), helpcontext(0x000468e9)]
HRESULT GetCustomUI(
[in] BSTR RibbonID,
[out, retval] BSTR* RibbonXml);
};

};


In my addin's class header I have the following relevant entries:

#import ".\2007\IRibbonExtensibility.tlb" named_guids

using namespace Office;
using namespace Word;
using namespace Excel;
using namespace s2sIRibbonExtensibility;

...
...
class ATL_NO_VTABLE Cs2signoa :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<Cs2signoa, &CLSID_s2signoa>,
public IDispatchImpl<Is2signoa, &IID_Is2signoa, &LIBID_s2soaLib,
/*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispatchImpl<_IDTExtensibility2, &IID__IDTExtensibility2,
&LIBID_AddInDesignerObjects>,
public IDispatchImpl<IRibbonExtensibility, &IID_IRibbonExtensibility,
&LIBID_s2sIRibbonExtensibility, /*wMajor =*/ 1, /*wMinor =*/ 0>,

...
...

BEGIN_COM_MAP(Cs2signoa)
COM_INTERFACE_ENTRY(Is2signoa)
COM_INTERFACE_ENTRY2(IDispatch, Is2signoa)
COM_INTERFACE_ENTRY(_IDTExtensibility2)
COM_INTERFACE_ENTRY(IRibbonExtensibility)
END_COM_MAP()

...
...

public:
...
// IRibbonExtensibility
STDMETHOD(raw_GetCustomUI)(BSTR RibbonID, BSTR *RibbonXml)
{
HRSRC hr = FindResource (_AtlBaseModule.GetModuleInstance (),
MAKEINTRESOURCE(IDR_XML_DATA1), TEXT("XML_DATA"));
if (hr)
{
HGLOBAL hgres = LoadResource (_AtlBaseModule.GetModuleInstance (),
hr);
if (hgres)
{
LPSTR ptr = (LPSTR)LockResource (hgres);
CComBSTR cbs = ptr;
*RibbonXml = cbs.m_str;
}
}
return S_OK;
}

// for ribbon
STDMETHOD(BtnClick)(IUnknown* pObj);
STDMETHOD(GetImage)(IUnknown* pObj, IPictureDisp ** ppdispImage);


I modified my class's idl to include the following:

interface Is2signoa : IDispatch{
[id(1), helpstring("method BtnClick")] HRESULT BtnClick([in] IUnknown*
pControl);
[id(2), helpstring("method GetImage")] HRESULT GetImage([in] IUnknown*
pControl, [out, retval] IPictureDisp ** ppdispImage);
};

Note that BtnClick and GetImage are in the xml that defines what to
call for the OnAction and GetImage functionality.

The BtnClick function looks like this:

STDMETHODIMP Cs2signoa::BtnClick(IUnknown* pObj)
{
CComQIPtr<IRibbonControl> pRibbonControl;
pRibbonControl = pObj;
_bstr_t bstrID = pRibbonControl->Id;

if (lstrcmp (bstrID, TEXT("Button1")) == 0)
{
// button 1 stuff here
}
... rest omitted for clarity
return S_OK;
}

The GetImage function is complex owing to the *very tedious*
requirement that the button images be in .png format to ensure correct
transparency. The simplest method of handling this is to invoke GDI+,
once the png is laboriously loaded from the application resources

STDMETHODIMP Cs2signoa::GetImage(IUnknown* pObj, IPictureDisp **
ppdispImage)
{
HRESULT hr = S_OK;

CComQIPtr<IRibbonControl> pRibbonControl;
pRibbonControl = pObj;
_bstr_t bstrID = pRibbonControl->Id;

PICTDESC pd;

pd.cbSizeofstruct = sizeof (PICTDESC);
pd.picType = PICTYPE_BITMAP;


GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;

gdiplusStartupInput.DebugEventCallback = NULL;
gdiplusStartupInput.SuppressBackgroundThread = FALSE;
gdiplusStartupInput.SuppressExternalCodecs = FALSE;
gdiplusStartupInput.GdiplusVersion = 1;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);

int idPNG;

if (lstrcmp (bstrID, TEXT("Button1")) == 0)
idPNG = IDR_PNG_DATA1;
... others omitted for clarity

HRSRC hResource = FindResource (_AtlBaseModule.GetResourceInstance (),
MAKEINTRESOURCE(idPNG), TEXT("PNG_DATA"));
if (!hResource)
return hr;

DWORD dwImageSize = SizeofResource (_AtlBaseModule.GetResourceInstance
(), hResource);
const void* pResourceData = LockResource (LoadResource
(_AtlBaseModule.GetResourceInstance (), hResource));
if (!pResourceData)
return hr;

HGLOBAL hBuffer = GlobalAlloc (GMEM_MOVEABLE, dwImageSize);
if (hBuffer)
{
void* pBuffer = GlobalLock (hBuffer);
if (pBuffer)
{
CopyMemory (pBuffer, pResourceData, dwImageSize);

IStream* pStream = NULL;
if :):CreateStreamOnHGlobal (hBuffer, FALSE, &pStream) ==
S_OK)
{
Bitmap *pBitmap = Bitmap::FromStream (pStream);
pStream->Release();

if (pBitmap)
{
pBitmap->GetHBITMAP (0, &pd.bmp.hbitmap);
hr = OleCreatePictureIndirect (&pd, IID_IDispatch, TRUE, (LPVOID
*)ppdispImage);

delete pBitmap;
}
}
GlobalUnlock (pBuffer);
}
GlobalFree (hBuffer);
}

GdiplusShutdown (gdiplusToken);

return hr;
}

Please note that the above code does not include the usual error
handlers and is very much a first attempt, requiring cleaning up,
testing, etc.

Regards

Steve H
Hi Steve,

I would appreciate if you can post a code snippet how you implemented
IRibbonExtensibility. I have been looking for exactly this but hasn't found a
way yet.

In your code you have "LIBID_MyIRibbonExtensibility", did you declare your
own LIBID?

&LIBID_MyIRibbonExtensibility, /*wMajor =*/ 1, /*wMinor =*/ 0>

Thanks in advance,

TJain
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top