|
发表于 2023-10-1 22:21:34
|
显示全部楼层
Ribbon Images in Excel 2007/2010 using a Delphi COM DLL
This problem has been bubbling away for more than a year. I’ve pick it up every now and then tried some fixes, found they didn’t work and put it down again. However last night I found the solution to the whole problem and I want to write it down so others can benefit from this knowledge.
If you review most of the knowledge on Office Ribbon controls, the examples are always in Visual Studio where some things are done in the background for you. With Delphi you need to understand all the nitty gritty to get the solution working.
What I’m going to do is describe the solution in pieces and point out the problems I had to over come and why they were a problem.
First up is to enable your custom Ribbon to ask for images. This is done in two parts. Firstly, by adding a loadImage tag to the customUI element as follows:
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="RibbonLoaded" loadImage="GetImage">
Where GetImage is the name of the call back function in my COM DLL.
Secondly, we add image tags to the button elements of the custom ribbon as follows:
<button id="EidolonAbout" label="About" onAction="RibbonClick" image="EidolonAbout" keytip="A" screentip="Display the About dialogue" supertip="This option displays the about dialogue so that you can get Eidolon's version number for support."/>
Where “EidolonAbout” is the name that will be passed to the GetImage() function.
Before we get to the GetImage() implementation in the COM DLL, lets make sure we have some images to work with. I prefer to have the images created externally with any package (GIMP or the Borland Image Editor) and put them in a windows resource file using a resource script. Below is a portion of the RC script for these images:
...
EidolonNewQueryImage ICON "Images\Toolbar Images\EidolonNewQueryImage.ico"
EidolonNewPivotTableImage ICON "Images\Toolbar Images\EidolonNewPivotTableImage.ico"
EidolonEditQueryPivotImage ICON "Images\Toolbar Images\EidolonEditQueryPivotImage.ico"
EidolonUpdateQueryPivotImage ICON "Images\Toolbar Images\EidolonUpdateQueryPivotImage.ico"
...
If you bind this into your project then it will be checked and compiled with your project. This then allow us to load the images from the DLL and not have to maintain additional external files.
You will notice I’ve used icon files for the images rather than bitmaps. When I tried bitmaps, they didn’t behave the same as in Delphi with the bottom left pixel being used as the transparent colour. There is probably something I’m missing here. As an aside, I’ve added the name “Image” to the end of all the icon files as I have a lot of images in my DLL for different components and so wanted to make sure there were no name clashes.
So now we come to the tricky bit, the implementation of GetImage().
First we need to added a method to the COM DLL’s automation class as follows:
IEidolonAddin = interface(IDispatch)
['{25F65BDF-36E0-4A51-B222-8A86AC4CCCCB}']
Procedure RibbonClick(Const control: IRibbonControl); safecall;
Procedure RibbonLoaded(Const RibbonUI: IRibbonUI); safecall;
function GetImage(Const imageName: WideString): IPictureDisp; safecall;
end;
IEidolonAddinDisp = dispinterface
['{25F65BDF-36E0-4A51-B222-8A86AC4CCCCB}']
Procedure RibbonClick(Const control: IRibbonControl); DispID 201;
Procedure RibbonLoaded(Const RibbonUI: IRibbonUI); DispID 202;
function GetImage(Const imageName: WideString): IPictureDisp; DispID 203;
end;
Now I always thought that this information was synchronised with the RIDL file but for me its not. We’ll come to the RIDL file at the end.
Obviously this then means the method needs to be implemented in the automation class as follows:
TEidolonAddin = Class(TAutoObject, IUnknown, IDispatch, IEidolonAddin,
IDTExtensibility2, IRibbonExtensibility)
...
Public
...
Function GetImage(Const imageName: WideString): IPictureDisp; Safecall;
End;
Function TEidolonAddin.GetImage(Const imageName: WideString): IPictureDisp;
Var
PictureDesc : TPictDesc;
Begin
Result := Nil;
PictureDesc.cbSizeofstruct := SizeOf(PictureDesc);
PictureDesc.picType := PICTYPE_ICON;
PictureDesc.hIcon := LoadIcon(HInstance, PWideChar(imageName + 'Image'));
If PictureDesc.hIcon <> 0 Then
Case OleCreatePictureIndirect(PictureDesc, ActiveX.IPicture, True, Result) Of
E_NOINTERFACE: OutputDebugString('Picture NO INTERFACE');
E_POINTER: OutputDebugString('Picture NO POINTER');
S_OK: OutputDebugString('Picture OK');
Else
OutputDebugString('Picture Unknown');
End;
End;
This method is surprisingly simple. It gets the icon images from the DLL resource and and passes this to the OLECreatePictureIndirect method to create the image to be passed to Excel. You will notice that there is some debugging code in here. I needed to step through this code to understand whether the reason the image was not appearing the Excel was because it was not being created in Delphi properly.
As it happened this code was not the actual problem. The method was called and ran okay but as soon as the execution passed out of the function Excel locked up or AVed depending on whether it was Excel 2007 or 2010. This started me thinking about the definition on the type library element associated with this call.
I wrongly thought that you only had available the items that were in the drop down lists so my definition was as follows:
IPicture* _stdcall GetImage([in] BSTR imageName);
I looked on the internet to see if anyone had posted a solution to this and found a Russian website (Delphi Masters) that had. Unfortunately my Russian is non-existent BUT by methodically looking at the code I found the probably. It was because the above RIDL definition was wrong and it should have been as below.
HRESULT _stdcall GetImage([in] BSTR imageName, [out, retval] IPictureDisp** aImage);
I run the code (with at the time very like hope that it would work) and I tried to click on Excel after stepping through the code to see if it was responding and to my supprise there was a little image next to one of my buttons |
|