浅谈C/C++ 开发Excel插件之自定制ribbon菜单

来源:互联网 发布:尼康处理raw软件 编辑:程序博客网 时间:2024/05/17 22:56

要实现ribbon菜单只需实现IRibbonExtensibility接口,此接口在程序库Microsoft Office 12.0 Object Library<2.4> 中找到(名称的版本号会随Office安装版本的不同而略有区别)。(方法跟上一节添加接口的流程一样这里就不在啰嗦了)。

1.实现接口。将继承声明中的&LIBID_Office改为&__uuidof(__Office),在stdafx.h中对命名空间和方法重命名以避免冲突。如下所示:

 

#import "C:\Program Files (x86)\Common Files\DESIGNER\MSADDNDR.DLL" auto_rename auto_search raw_interfaces_only rename_namespace("AddinDesign")#import "C:\Program Files (x86)\Common Files\Microsoft Shared\OFFICE12\MSO.DLL" auto_rename auto_search raw_interfaces_only rename_namespace("Office")  rename("RGB","MsoRGB") rename("SearchPath","MsoSearchPath")using namespace AddinDesign;using namespace Office;

这里跟MSDN上一样 只是改下office的路径。

2、添加ribbon描述xml。可用MSDN中的例子修改。

因为C++开发没有设计的可视化界面,这里有一个方法是创建一个C# excel插件项目,然后对ribbon菜单按照设计设计,完成后选择导出xml,将文件拷贝到工程中,稍加修改即可。选择资源视图-》添加-》资源-》导入-》选择该xml文件路径.

文件内容大致为:

<?xml version="1.0" encoding="utf-16BE"?>
<customUI onLoad="RibbonLoad" xmlns="http://schemas.microsoft.com/office/2006/01/customui" loadImage="GetImage">
  <ribbon>
    <tabs>
      <!--getImage="ribbon_GetImage"-->
      <tab id="YYtab" label="新华财经">
        <group id="grpFinancial" label="财经">
          <splitButton id="splitButton1" size="large" >
            <button id="btnQuote" getEnabled="EnableRibbonUi" label="实时行情&#13;" onAction="GeneralButton_Click" image="205"/>
            <menu id="splitButton1__mnu" getEnabled="EnableRibbonUi">
              <menuSeparator id="separator1" title="行情函数" />
              <button id="btnQuoteFunction" onAction="GeneralButton_Click" keytip="R" label="实时行情" image="205" />
              <menuSeparator id="separator2" title="实时行情" />
              <button id="btnQuoteYahoo" onAction="GeneralButton_Click" description="获取实时行情" label="Yahoo实时" image="205" />
              <button id="btnQuoteSina" onAction="GeneralButton_Click" label="Sina实时" image="SinaQuote_64.png" />
            </menu>
          </splitButton>
          <menu id="MenuHisQuote" label="历史行情&#13;" size="large" image="205" itemSize="large" getEnabled="EnableRibbonUi">
            <menuSeparator id="separator3" title="行情函数" />
            <button id="btnQuoteHistFunction" onAction="GeneralButton_Click" label="历史行情"/>
            <menuSeparator id="separator4" title="历史行情" />
            <button id="btnQuoteHisYahoo" onAction="GeneralButton_Click" label="Yahoo历史" />
            <button id="btnQuoteHisSina" onAction="GeneralButton_Click" label="Sina历史" />
          </menu>
          <menu id="MenuImport" label="导入&#13;" size="large"  description="Google地图">
            <button id="btnImportFromLocal" onAction="GeneralButton_Click" label="本地导入"/>
            <button id="btnImportFromWeb" onAction="GeneralButton_Click" label="网络导入"/>
          </menu>
        </group>
        <group id="grpMap" label="地图">
          <button id="btnGoogleMap" onAction="HistoryButtonClicked" label="Google地图&#13;" size="large" description="Google地图"/>
          <button id="btnGeoLocation" onAction="GeneralButton_Click" label="地理定位&#13;" size="large" />
        </group>
        <group id="grpWeather" label="天气">
          <button id="btnWeatherFunction" onAction="GeneralButton_Click" label="天气函数" size="large" />
          <button id="btnWeatherReport" onAction="GeneralButton_Click" label="天气报表" size="large" />
        </group>
        <group id="grpAbout" label="关于">
          <button id="btnLogin" onAction="GeneralButton_Click" label="登录" size="large" />
          <button id="btnHelp" onAction="GeneralButton_Click" label="帮助" />
          <button id="btnAbout" onAction="GeneralButton_Click" label="关于" />
          <button id="btnConfig" onAction="GeneralButton_Click" label="设置"/>
        </group>
      </tab>
    </tabs>
  </ribbon>
</customUI>

这里对这个文件稍微做加解释,详细的可到网上查询。exce插件的所有接口都是以回调的方式给开发者,所有这里里面的RibbonLoad,GetImage HistoryButtonClicked getEnabled都是回调的接口,是需要在项目中实现这些接口的。比如STDMETHOD(RibbonLoad)(IDispatch* ppiDispatch);参数ppiDispatch实际是整个ribbon菜单的对象。excel插件会通过GetImage 函数来加载菜单设置的图片。这里面还有很多这样的回调函数,可根据需要实现相应的接口。本例子实现了部分,可仿照实现即可。有疑问可参考:

https://msdn.microsoft.com/zh-cn/aa338202
https://msdn.microsoft.com/zh-cn/aa722523.aspx#a16c7df5-93f3-4920-baa8-7b7290794c15_AdditionalResources 

3、实现GetCustomUI接口。这个直接用MSDN中的源码即可。
STDMETHOD(GetCustomUI)(BSTR RibbonID, BSTR * RibbonXml)
{
    if(!RibbonXml)
        return E_POINTER;
    *RibbonXml = GetXMLResource(IDR_XML1);
    return S_OK;
}
HRESULT CConnect::HrGetResource(int nId, LPCTSTR lpType, LPVOID* ppvResourceData, DWORD* pdwSizeInBytes)
{
    HMODULE hModule = _AtlBaseModule.GetModuleInstance();
    if (!hModule)
        return E_UNEXPECTED;
    HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(nId), lpType);
    if (!hRsrc)
        return HRESULT_FROM_WIN32(GetLastError());
    HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
    if (!hGlobal)
        return HRESULT_FROM_WIN32(GetLastError());
    *pdwSizeInBytes = SizeofResource(hModule, hRsrc);
    *ppvResourceData = LockResource(hGlobal);
    return S_OK;
}


BSTR CConnect::GetXMLResource(int nId)
{
    LPVOID pResourceData = NULL;
    DWORD dwSizeInBytes = 0;
    HRESULT hr = HrGetResource(nId, _T("XML"), 
        &pResourceData, &dwSizeInBytes);
    if (FAILED(hr))
        return NULL;
    CComBSTR cbstr(dwSizeInBytes, reinterpret_cast<LPCSTR>(pResourceData));
    return cbstr.Detach();
}
4、自定制按钮图片和按钮事件。先将IConnect设为默认的响应接口。对自定义图片,需要在按钮上用image属性标志自定义图片,在根节点上用loadImage回调来加载自定义图片。对按钮事件,需在按钮上用onAction回调。所有回调函数的签名可以参见Customizing the 2007 Office Fluent Ribbon for Developers (Part 3 of 3),在项目的idl文件中加入回调函数接口的声明,并在Connect.h中将其实现


BEGIN_COM_MAP(CConnect)
    COM_INTERFACE_ENTRY(IConnect)
    COM_INTERFACE_ENTRY2(IDispatch, IConnect)
    COM_INTERFACE_ENTRY(_IDTExtensibility2)
    COM_INTERFACE_ENTRY(IRibbonExtensibility)
END_COM_MAP()

interface IConnect : IDispatch{    HRESULT HistoryButtonClicked([in] IDispatch* ribbon);    HRESULT GetImage([in] BSTR *pbstrImageId, [out, retval] IPictureDisp ** ppdispImage);};
STDMETHOD(HistoryButtonClicked)(IDispatch* ribbon);STDMETHOD(GetImage)(BSTR *pbstrImageId,IPictureDisp ** ppdispImage);
按钮事件的具体实现代码就很简单了,这里说一下加载图片的接口的实现。第一个参数表示图片的名称,是image属性的值,第二个参数是将图片的信息输出给Excel。这里可以将图片放入资源中,利用GDI+将其转为Bitmap并通过OleCreatePictureIndirect将其存入ppdispImage。
STDMETHODIMP CConnect::GetImage(BSTR *pbstrImageId,IPictureDisp ** ppdispImage)
{
    int pngId(0);
    try
    {
        pngId = lexical_cast<int>(*pbstrImageId);
    }
    catch(...)
    {
        return E_UNEXPECTED;
    }
    using namespace Gdiplus;
    LPVOID pResourceData = NULL;
    DWORD len = 0;
    HRESULT hr = HrGetResource(pngId,_T("PNG"), &pResourceData, &len);


    BYTE* lpRsrc = reinterpret_cast<BYTE*>(pResourceData);
    if (!lpRsrc)
    {
        return E_UNEXPECTED;
    }


    HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
    BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
    memcpy(pmem,lpRsrc,len);
    GlobalUnlock(m_hMem);
    IStream* pstm;
    CreateStreamOnHGlobal(m_hMem,FALSE,&pstm);


    PICTDESC pic;
    memset(&pic, 0, sizeof pic);
    Bitmap *png = Bitmap::FromStream(pstm);
    HBITMAP hMap = NULL;
    png->GetHBITMAP(Color(),&hMap);
    pic.picType =  PICTYPE_BITMAP;
    pic.bmp.hbitmap = hMap;


    OleCreatePictureIndirect(&pic,IID_IPictureDisp,true,(LPVOID*)ppdispImage);
    return S_OK;
}

因为资源中图片的ID是数字,而xml中image的属性值是字符串,所有将图片的ID以字符串的方式放在image中,在回调中再转为数字以查找资源,方法比较笨,等以后发现更好的方法再做修改。

注:1.我们添加的菜单如果交给excel处理会出现图标跟文字排版不美观的问题,解决这个问题的方法师换行如:就是在文字后面添加 &#13


2.如果出现乱码请修改文件的编码

经过上述步骤 即可完成自定义ribbon菜单的全部过程。


0 0
原创粉丝点击