在开始Excel开发之前,需要把架子搭起来。最直接的那就是Excel里面的菜单了,他向用户直观的展现了我们的插件具有哪些功能。菜单出来之后我们就可以实现里面的事件和功能了。Excel菜单有两种形式,一种是Excel 2003及之前的传统菜单样式,一种是Excel 2007及之后的Ribbon菜单。本文首先讲解Excel 2007中菜单的创建,包括使用Visual Studio可视化设计菜单,菜单的RibbonXml配置,然后讲解如何在Excel 2003中创建自定义菜单。最后演示如何使用SharedAddin技术将两者结合起来,即在2003版本中显示原始的菜单样式,在以2003上版本中动态加载Ribbon菜单,从而达到版本的兼容。

一 Excel 的Ribbon菜单及Ribbon Xml文件

要演示菜单的创建,我们首先创建一个VSTO程序,如图在VS中创建一个Excel外接程序:

然后接下来,添加项,添加一个Ribbon菜单:

在创建菜单之前,需要明确我们的插件具有哪些模块。这里为了演示如何创建菜单以及后面的功能点,我们的插件打算做四个功能点。 首先是财经模块,包括从一些开放的财经API如新浪财经API,雅虎API中获取实时或者历史行情数据;地图模块,包括地图显示,地址检索,专题制图等;天气模块,获取天气,天气保表;系统模块,包括登录,帮助,关于模块等。确定好功能点之后,就可以开始创建菜单了。

添加了Ribbon菜单之后,就可以打开ToolBox开始设计了,如下图。下面介绍各个菜单项的功能及设计要点。

基本控件

2.1 Tab控件

首先介绍的是RibbonTab控件,它是所有控件的容器,当我们添加一个Ribbon菜单的时候,VS默认会给我们创建一个RibbonTab控件,一个VSTO项目可以创建多个Tab控件,您需要做的只是从ToolBox中推拽一个Tab至设计界面上即可。Tab控件有一些属性。

其中比较重要的属性为ControlIdType,如果类型是Office的话,他会嵌入到Office内置系统的 Tab页中,而不是默认的新创建一个以Label为名称的Tab页。下图是ControlIdType为Office的效果,如果PositionType属性设置为Default的话,会出现在Office加载项 这个标签页中。

如果将ControlIdType设置为Custom,则在界面上会显示以Label命名的一个新的Tab页,这里将Label改为歪歪插件。效果如下图:

一般的,我们会采用Custom的方式,让我们的插件以独立的Tab页展现出来。

Tab控件还有一个重要的名为Position的属性,他决定了我们的Tab在哪个地方展现,PositionType的默认值为Default,这时,如果ControlIdType为Office,我们的插件会在加载项中显示,如果为Custom,插件会显示在Office里面的最后一个加载项后面,如果有多个加载项,则按顺序往后面排列。PositionType属性还有BeforeOfficeId和AfterOfficeId两值,设置这两个属性时,需要指定OfficeId,Office内置的菜单的ID值,您可以到MSDN上下载。指定好OfficeId后,我们的Tab也会在该内置Tab页里面,前面或者后面显示。一般的,我们保持这个属性为空即可。让我们的插件在Office系统Tab页之后显示。

2.2 Group控件

Group控件的作用是将我们的功能进行分组。回到我们之前的规划,我们的歪歪插件有财经,地图,天气,关于这几大功能。所以我们需要在界面上放置4个Group控件,并对其进行命名。

2.3 Menu控件和SpliterButton控件

在分好类之后,我们需要对每个分类的细小功能进行设计,这里面我们需要放置各种控件,首先我们可能会接触到的就是Menu控件,里面可以包含Button,SplitButton等。我们规划的财经项主要包括,实时行情,历史行情和导入功能。实时行情和历史行情包括从Sina或者Yahoo财经API接口中获取所有股票的实时和历史行情数据,导入功能即是从本地或者网络上导入数据。所以在页面上添加三个Menu控件。

Menu控件几个比较重要的属性,一个是ControlSize,它确定了控件的大小,一般地,如果功能较重要,或者是有比较明显的分类用途,使用RibbonControlSizeLarge。然后需要设置菜单的图标,可以指定自定义的图片,也可以使用默认的Office内置的菜单的图片,如果要使用内置的图片,需要设置OfficeImageId,具体内置Id及图片可以查看该网址。一般地,我们会为我们的菜单设计图标,您只需要指定其Image属性即可。

SplitButton控件和Menu控件类似,它可以包含Button,Seperator控件,不同的是,SplitButton控件本身自己可以响应Click事件,通常在Menu中如果需要将该菜单项中常用的功能设置为默认的,那么可以使用SplitButton控件,将最常用的功能设置到该控件的Click事件上来。

2.4 Button,Seperator控件

Button控件是最基础的响应单击事件的UI控件。点开Menu的下拉图标,然后向里面添加Button按钮即可,可以设置按钮的Image属性。在设计按钮的时候,可能我们需要对其进行分组,这时候,使用Seperator控件是一种比较好的选择,直接在ToolBox中拖动一个Seperator控件到界面上想分割的地方即可。默认情况下Seperator控件是一条竖线,但是当设置Seperator控件的Title属性时,他可以以文本的形式来进行分割,这和其他系统中的Seperator控件不一样。

同理,按照规划,我们将所有的菜单设计好,并注册其Click事件。这里只讲解了这几个基本的菜单项控件,更多的控件您可能以自己往界面上拖拽试试看,利用这些内置的控件,您可以设计出和Office内置的Ribbon菜单媲美的自定义菜单界面来。

RibbonXml

在Office中Ribbon菜单时可以通过RibbonXML进行配置,也就是说,上面的可视化界面设计其实是为我们提供了编辑RibbonXML的设计时支持。其实我们也可以直接创建一个XML文件进行设计,然后在代码中进行加载,同样能够实现这样的功能。在有些情况下,比如我们创建SharedAddin程序时,根本没有设计时支持。所以了解RibbonXML对于创建可兼容多版本Excel菜单系统显得尤为重要。

要创建RibbonXML最好的做法是对着新建的可视化菜单,然后右键->将功能区导出到XML。然后项目会自动创建Ribbon.xml和Ribbon.cs文件,其中Ribbon.xml是布局文件,Ribbon.cs是事件处理代码。

打开Ribbon.xml可以看到如下代码,可以看到这个UI界面的展现即是使用了该XML文件来进行渲染的,其中在XML中还可以声明一些事件,后面会讲。

现在,如何在我们的应用程序中加载该RibbonXML并渲染出Ribbon菜单呢?首先,我们将之前的添加的可视化设计的Ribbon菜单YYMenu.cs排除到项目外。然后转到ThisAddIn.cs中,覆写Office.IRibbonExtensibility 接口的CreateRibbonExtensibilityObject方法,实例化自动生成的Ribbon类,然后返回。

private Ribbon customerRibbon;

protected override Office.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
    customerRibbon = new Ribbon();
    return customerRibbon;
}

运行程序,即可看到如下效果:

可以看到图片不见了,这是因为在导出功能区为XML的时候,VS没有帮我们处理图片,所以需要我们自己来添加。在RibbonXML文档中, customUI节点下有loadImage事件,Button有getImage事件和image属性。customUI节点的loadImage方法和子节点的image属性一起使用,image属性作为loadImage方法的参数。

loadImage方法如下:

public Image LoadImage(string imageName)
{
    Assembly assembly = Assembly.GetExecutingAssembly();
    //String[] all =assembly.GetManifestResourceNames();//GetResourceName
    Stream stream = assembly.GetManifestResourceStream("YYAddIn.Resources." + imageName);
    return Image.FromStream(stream);
}

其中,imageName即为Button控件的image属性要设置的图片名称。资源文件图片,要设置为嵌入的资源。所有的按钮的单击事件,我们使用GeneralButton_Click事件来处理。运行程序,我们又看到了之前采用设计器时编辑的菜单时的界面了。

二 Excel 2003下面的菜单系统

创建工程

由于Excel2003及以下版本不支持Ribbon菜单,所以以上的程序在03版本下并不能运行。但是,如果开发企业级应用的话,仍不能忽视广大使用 Office 2003 版本的客户,所以您还需要创建2003下面的菜单系统。下面就介绍如何在Excel2003系统中创建Excel菜单及工具条。

要创建在Excel 03下的插件,我们可以创建Shared Add-in程序,如下图,首先新建一个名为YYSharedAddin的Shared Add-in项目:

然后下一步下一步, 设置编程语言,我们这里选择C#

下一步,设置插件的目标应用程序,可以看到,我们的Shared Add-in程序可以为多个目标应用程序编写同一个插件,这在前一篇文章中介绍SharedAddin和VSTO的差别时已经介绍过了。因为我们主要是编写Excel插件,所以仅勾选Excel即可。

下一步,设置Addin的展现名称和在编程时的名称

创建完成之后,我们可以看到工程项目非常简单,只有一个Connect.cs文件。打开该文件,可以看到其中实现了Extensibility.IDTExtensibility2 接口,所有Office应用程序都是用IDTExtensibility2接口与COM加载项进行通信的,该接口提供了一种通用的初始化机制,并具有在Office应用程序的对象模型中传递数据的能力,因此Com加载项可以与Office应用程序通信,该接口中有5个方法,分别是:

Office在对Com加载项进行实例化时,会创建Connect类,注意我们不能用Connect的构造函数创建类的实例,应该在OnConnection方法中进行初始化操作,比如菜单项的加载,自定义函数的加载,变量的初始化等等;类似的,加载项的关闭不能调用析构函数,而要用OnDisconnection方法,在该方法中需要释放非托管的资源,进行资源清理等一系列操作。下图展示了这5个方法的执行顺序。该图引用了MYM]Brooks同学博文中的图片。

由分析得之,我们对菜单及工具栏的初始化,需要放到OnConnection方法中。

在OnConnection方法中,有一个很重要的参数就是application,我们声明一个类型为

Microsoft.Office.Interop.Excel.Application的applicationObject对象,然后将这个对象保存起来,以备后面创建菜单以及对Excel进行操作时使用。

private Application applicationObject;

/// <summary>
///      Implements the OnConnection method of the IDTExtensibility2 interface.
///      Receives notification that the Add-in is being loaded.
/// </summary>
/// <param term='application'>
///      Root object of the host application.
/// </param>
/// <param term='connectMode'>
///      Describes how the Add-in is being loaded.
/// </param>
/// <param term='addInInst'>
///      Object representing this Add-in.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
    applicationObject = application as Application;
    addInInstance = addInInst as COMAddIn;

    if (applicationObject.Version == "11.0")
    {
        if (menuDesigner == null)
        {
            menuDesigner = new MenuDesigner(applicationObject);
        }
        menuDesigner.AddMenus();
        menuDesigner.AddToolBars();
    }
}

在OnConnection方法中,我们首先判断Excel的版本号,版本号可以通过Version对象获取,如果版本号为11,即为2003版本的Excel,我们需要手动的动态创建菜单和工具条。我们新建了一个名为MenuDesigner的用来创建菜单和工具条的类,在其构造函数中传入了applicationObject对象。

添加菜单

我们把添加菜单放到了MenuDesigner的AddMenus方法中。Excel中的一个菜单项和子菜单其实都是一个MSOffice.CommandBarPopup对象,菜单项里面的菜单按钮是MSOffice.CommandBarButton对象。首先我们定义菜单项以及菜单按钮,这里只列出部分。

//YY插件菜单
MSOffice.CommandBarPopup YYMenu = null;
//实时行情函数菜单按钮
MSOffice.CommandBarButton btnQuoteFunctionMenuCommand = null;
//Sina实时行情函数菜单按钮
MSOffice.CommandBarButton btnQuoteSinaFunctionMenuCommand = null;
//Yahoo实时行情函数菜单按钮
MSOffice.CommandBarButton btnQuoteYahooFunctionMenuCommand = null;

在创建菜单时,我们要首先创建YYMenu对象,然后再在该对象上添加菜单项。创建YYMenu菜单的方法如下:

public void AddMenus()
{
    MSOffice.CommandBar menubar = (MSOffice.CommandBar)application.CommandBars.ActiveMenuBar;
    int controlCount = menubar.Controls.Count;
    string menuCaption = "歪歪插件";
    // Add the menu.
    try
    {
        YYMenu = (MSOffice.CommandBarPopup)
            application.CommandBars.ActiveMenuBar.FindControl(
            MSOffice.MsoControlType.msoControlPopup, System.Type.Missing, menuTag, true, true);
    }
    catch { }

    if (YYMenu != null)
    {
        YYMenu.Delete(Type.Missing);
    }
    YYMenu = (MSOffice.CommandBarPopup)menubar.Controls.Add(MSOffice.MsoControlType.msoControlPopup, missing, missing, controlCount, true);
    YYMenu.Tag = menuTag;
    YYMenu.Caption = menuCaption;
    YYMenu.BeginGroup = true;

    LoginGroup();
    FinancialGroup();
    MapServiceGroup();
    WeatherReportGroup();
    AboutGroup();
}

需要注意的是,在创建菜单之前,我们需要判断之前是否已经存在相同Tag值得菜单,如果存在,需要先将之前创建的菜单项删除,然后再重新创建。否则,在用户在Com加载项中显示或者隐藏菜单项时,会重复创建菜单项。创建完主菜单后,我们可以在主菜单上创建子菜单了。这些方法都写到了最后的几个以Group结尾的方法中。现在以创建财务项菜单的FinancalGroup方法为例讲解。

private void FinancialGroup()
{
    //实时行情
    MSOffice.CommandBarPopup realTimeButton = AddPopupButton(YYMenu.Controls, MenuNameEnum.Quote);
    realTimeButton.BeginGroup = true;
    //添加子菜单
    btnQuoteFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.Quote, YYSharedAddin.Properties.Resources.QuoteReal);
    btnQuoteSinaFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.QuoteSina, YYSharedAddin.Properties.Resources.SinaQuote_64);
    btnQuoteSinaFunctionMenuCommand.BeginGroup = true;
    btnQuoteYahooFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.QuoteYahoo, YYSharedAddin.Properties.Resources.Yahoo_Quote);

    //历史行情
    MSOffice.CommandBarPopup historyButton = AddPopupButton(YYMenu.Controls, MenuNameEnum.QuoteHistory);
    realTimeButton.BeginGroup = true;
    //添加子菜单
    btnQuoteHistoryFunctionMenuCommand = AddCommandButton(historyButton.Controls, MenuNameEnum.QuoteHistory, YYSharedAddin.Properties.Resources.QuoteHist);
    btnQuoteHistorySinaFunctionMenuCommand = AddCommandButton(historyButton.Controls, MenuNameEnum.QuoteHistorySina, YYSharedAddin.Properties.Resources.SinaQuote_64);
    btnQuoteHistorySinaFunctionMenuCommand.BeginGroup = true;
    btnQuoteHistoryYahooFunctionMenuCommand = AddCommandButton(historyButton.Controls, MenuNameEnum.QuoteHistoryYahoo, YYSharedAddin.Properties.Resources.Yahoo_HistoryQuote);

    //导出
    MSOffice.CommandBarPopup importButton = AddPopupButton(YYMenu.Controls, MenuNameEnum.Import);
    importButton.BeginGroup = true;
    btnImportFromLocalMenuCommand = AddCommandButton(importButton.Controls, MenuNameEnum.ImportFromLocal, YYSharedAddin.Properties.Resources.ImportFromDisk);
    btnImportFromWebMenuCommand = AddCommandButton(importButton.Controls, MenuNameEnum.ImportFromWeb, YYSharedAddin.Properties.Resources.ImportFromWeb);
}

对于财务大类菜单,其中有三个一级菜单,分别是实时行情,历史行情,导入。创建一级菜单的方法AddPopupButton代码为:

/// <summary>
/// 添加子菜单项
/// </summary>
/// <param name="controls">该字菜单的父菜单容器</param>
/// <param name="menu">菜单名称</param>
/// <returns></returns>
private MSOffice.CommandBarPopup AddPopupButton(MSOffice.CommandBarControls controls, MenuNameEnum menu)
{
    String tag = menu.ToString();
    String caption = String.Empty;
    Menus.menus.TryGetValue(tag, out caption);
    MSOffice.CommandBarPopup command = null;
    try
    {
        command = controls[caption] as MSOffice.CommandBarPopup;
    }
    catch { }
    if (command == null)
    {
        command = controls.Add(MSOffice.MsoControlType.msoControlPopup, Type.Missing, Type.Missing, Type.Missing, Type.Missing) as MSOffice.CommandBarPopup;
        command.Caption = caption;
    }
    return command;
}

其中第一个参数为最大的根节点菜单YYMenu对象的所有Controls容器。所以创建第一个实时行情一级菜单的方法为:

//实时行情
MSOffice.CommandBarPopup realTimeButton = AddPopupButton(YYMenu.Controls, MenuNameEnum.Quote);
realTimeButton.BeginGroup = true;

得到realTimeButton这个一级菜单之后,我们将其BeginGroup属性设置为true表示在之前添加一个Seperator控件(一条横线或者竖线)。有了这个realTimeButton一级菜单之后,我们可以在该对象上创建三个二级菜单按钮项。

//添加子菜单
btnQuoteFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.Quote, YYSharedAddin.Properties.Resources.QuoteReal);
btnQuoteSinaFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.QuoteSina, YYSharedAddin.Properties.Resources.SinaQuote_64);
btnQuoteSinaFunctionMenuCommand.BeginGroup = true;
btnQuoteYahooFunctionMenuCommand = AddCommandButton(realTimeButton.Controls, MenuNameEnum.QuoteYahoo, YYSharedAddin.Properties.Resources.Yahoo_Quote);

创建子菜单按钮的方法封装到了AddCommandButton方法中,方法第一个参数为第一级子菜单的所有控件的容器类。

private MSOffice.CommandBarButton AddCommandButton(MSOffice.CommandBarControls controls, MenuNameEnum menu, System.Drawing.Image icon)
{
    string tempName = menu.ToString();
    String tag = String.Format("{0}|{1}", menu, DateTime.Now.ToBinary());
    String caption = String.Empty;
    Menus.menus.TryGetValue(tempName, out caption);
    MSOffice.CommandBarButton command = null;
    try
    {
        command = controls[caption] as MSOffice.CommandBarButton;
        command.Tag = tag;
        command.Click += new MSOffice._CommandBarButtonEvents_ClickEventHandler(command_Click);
    }
    catch { }
    if (command == null)
    {
        command = controls.Add(MSOffice.MsoControlType.msoControlButton, Type.Missing, Type.Missing, Type.Missing, Type.Missing) as MSOffice.CommandBarButton;
        command.Style = MSOffice.MsoButtonStyle.msoButtonIconAndCaption;
        command.Caption = caption;
        command.Tag = tag;
        command.Picture = ImageConverterHelper.ImageToPictureDisp(icon);
        command.Click += new MSOffice._CommandBarButtonEvents_ClickEventHandler(command_Click);
    }
    return command;
}

在创建菜单按钮时,我们需要先判断当前的菜单中是否有该菜单项,如果有直接使用。否则创建新的对象。这里有几个地方需要注意,首先是CommandBarButton 的Tag属性,该属性应该加上一个唯一标志,比如当前时间,或者GUID,然后带上该菜单的名称等信息。加唯一标志的目的是每一次在创建菜单时保证是唯一的,否则会出现菜单只会响应一次按钮点击事件等奇怪的问题。其次设置按钮的Style为MSOffice.MsoButtonStyle.msoButtonIconAndCaption 既带图片又有文字的时候,Picture属性为想要显示在按钮前面的图片,该对象是一个stdole.IPictureDisp类型的对象,您需要进行一下转换。其次直接设置图片会使得图片中的背景色不会透明,背景色为Office的默认风格颜色,如果要将背景色透明,需要设置Mask属性,Mask属性也是一张图片。该图片为带显示图片的蒙版,即原始图片中需要显示的地方,用黑色表示,那么其余地方就会透明显示,具体使用方式您可以参看这篇文章,这里为了简化,不做处理。

添加工具条

工具条其实就是一个大的一级菜单,和创建菜单一样,我们将创建工具条的代码封装到了AddToolBars方法中,该方法代码如下:

public void AddToolBars()
{
    try
    {
        YYToolBar = application.CommandBars["YYToolBar"];
    }
    catch { }

    if (YYToolBar == null)
    {
        YYToolBar = application.CommandBars.Add("YYToolBar", MSOffice.MsoBarPosition.msoBarTop, false, true);
    }
    LoginGroup_ToolBar();
    FinancialGroup_ToolBar();
    MapServiceGroup_ToolBar();
    WeatherReportGroup_ToolBar();
    AboutGroup_ToolBar();

    YYToolBar.Visible = true;
}

我们首先需要创建一个大的工具条,和创建菜单类似,在创建工具条之前,我们需要定义好所有的工具条中的按钮,注意,该按钮对象不能和菜单项里面的对象共用,否则会导致事件注册被冲掉的情况。

//歪歪插件工具条
MSOffice.CommandBar YYToolBar;
//实时行情函数工具条
MSOffice.CommandBarButton btnQuoteFunctionToolBarCommand = null;
//Sina实时行情工具条
MSOffice.CommandBarButton btnQuoteSinaFunctionToolBarCommand = null;
//Yahoo实时行情工具条
MSOffice.CommandBarButton btnQuoteYahooFunctionToolBarCommand = null;

创建好YYToolBar对象后,在该对象的基础上创建工具条里面的工具项就和创建字菜单类似了,这里就不再赘述了。

现在我们来看在Excel2003下面的效果。由于我们创建的SharedAddin程序,我们在调试的时候,需要设置启动程序,Visual Studio 给我们设置的默认启动程序是Visual Studio本身。这里我们将默认程序指定为Excel 2003 的可执行文件,如下图:

运行程序,Visual Studio就会启动Excel程序,然后就可以看到我们创建的歪歪菜单和工具条了。

三 全版本兼容

前面介绍了在2003以上版本的Ribbon菜单创建和在2003版本下面的传统菜单项的创建。一个良好的Excel应用程序应该会根据版本的不同而展现不同的菜单形式。如果您用VSTO创建的话,那么可能在03版本上就不能很好的支持,因为03版本不支持Ribbon菜单。所以要想兼容所有的Excel版本,可以创建Shared Add-in工程。第二部分已经讲解了如何在SharedAddin中创建传统菜单的方法,要兼容03以上版本,我们只需要在SharedAddin中加载第一部分创建好的RibbonXML即可。

要让SharedAddin能在03以上版本中渲染Ribbon菜单,我们需要让Connect类实现Office.IRibbonExtensibility接口。由于之前生成的Ribbon.cs类已经实现了该接口,所以最简单的方法是:将之前创建好的Ribbon.xml及Ribbon.cs拷贝到SharedAddin工程项目中来。并将Ribbon.xml设置为嵌入的资源。将Ribbon.cs 的命名空间改为和Connect.cs一致的命名空间,然后利用Partial关键字,将Ribbon类名称改为partial Connect类。如下:

[ComVisible(true)]
public partial class Connect : Office.IRibbonExtensibility
{
    private Office.IRibbonUI ribbon;
    #region IRibbonExtensibility 成员

    public string GetCustomUI(string ribbonID)
    {
        return GetResourceText("YYSharedAddin.RibbonMenu.Ribbon.xml");
    }
    #endregion

    #region 功能区回调
    //在此创建回调方法。有关添加回调方法的详细信息,请在解决方案资源管理器中选择功能区 XML 项,然后按 F1

    public void Ribbon_Load(Office.IRibbonUI ribbonUI)
    {
        this.ribbon = ribbonUI;
    }

    public Image LoadImage(string imageName)
    {
        Assembly assembly = Assembly.GetExecutingAssembly();
        //String[] all =assembly.GetManifestResourceNames();//GetResourceName
        Stream stream = assembly.GetManifestResourceStream("YYSharedAddin.Resources." + imageName);
        return Image.FromStream(stream);
    }

    public void GeneralButton_Click(Office.IRibbonControl control)
    {
        try
        {
            MessageBox.Show("you clicked the button id is " + control.Id);
        }
        catch (Exception ex)
        {

        }
    }
    #endregion

    #region 帮助器

    private static string GetResourceText(string resourceName)
    {
        Assembly asm = Assembly.GetExecutingAssembly();
        string[] resourceNames = asm.GetManifestResourceNames();
        for (int i = 0; i < resourceNames.Length; ++i)
        {
            if (string.Compare(resourceName, resourceNames[i], StringComparison.OrdinalIgnoreCase) == 0)
            {
                using (StreamReader resourceReader = new StreamReader(asm.GetManifestResourceStream(resourceNames[i])))
                {
                    if (resourceReader != null)
                    {
                        return resourceReader.ReadToEnd();
                    }
                }
            }
        }
        return null;
    }

    #endregion
}

我们需要注意的是,要设置好正确的资源名称,您可以通过GetManifestResourceNames 来查看该程序集中的所有的资源名称。

现在,将启动项目设置为2007 或者2010版本的Excel,现在菜单又变成Ribbon风格的了:

将启动项目设置为2003版本的Excel,菜单就变成传统风格的了。

四 结语

本文介绍了Excel中的菜单系统。首先介绍了使用Visual Studio设计时支持的Ribbon菜单的创建,通过拖拉控件及设置属性,可以创建出和Office内置菜单相媲美的自定义菜单。然后介绍了Ribbon菜单的基础Ribbon XML文件,随后讲解了如何在VSTO中手动加载Ribbon菜单。然而Ribbon菜单仅在2003以上版本的Excel中支持。为了解决Excel 2003下菜单创建的问题,本文展示了如何创建Excel Shared Add-in程序,并演示了如何创建传统的菜单项和工具栏。最后为了兼容所有的Excel版本,在SharedAddin中展示了如何加载Ribbon XML,使得我们的Excel插件对于不同的Excel版本,能够展现出不同风格的菜单项。

现在我们的插件架子已经搭好了,下文我会讲解Excel的对象模型,介绍Excel中的几个核心对象,如WorkBook,WorkSheet,Range对象等,这些对象无论是您进行何种类型的Excel开发,都会遇到,这些对象也是您进行Excel开发的重要基础,敬请期待。

本文代码点击此处下载,希望本文对您了解Excel菜单系统有所帮助。

浅谈Excel开发:二 Excel 菜单系统的更多相关文章

  1. 浅谈WebService开发二(同步与异步调用)转

    上文 <http://www.dotnetgeek.cn/xuexiwebservice1.html>已经跟大家说了,如果创建一个webservice和简单的调用,本文将注重webserv ...

  2. 浅谈WebService开发三(动态调用WebService)转

    在前两讲里,我已经向大家演示了如何使用WebService.同步, 异步调用WebService,而在实际开发过程中,可能会有多个WebService接口供你选择,而在程序执行过程中才决定使用哪一个 ...

  3. 浅谈Kotlin(二):基本类型、基本语法、代码风格

    浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 通过上面的文章,在A ...

  4. 浅谈Java代理二:Cglib动态代理-MethodInterceptor

    浅谈Java代理二:Cglib动态代理-MethodInterceptor CGLib动态代理特点: 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生 ...

  5. 浅谈 Android 开发文化

    Hello,亲爱的读者朋友们(希望你们是 Android 开发者,或者正在成为 Androider 的路上-)! 质量从用户反馈很清凉然后我们就只能看 CPU 原来的想法是但是事实上不是这些但是我们可 ...

  6. 浅谈iOS开发中多语言的字符串排序

    一.前言 在iOS开发中,一个经常的场景是利用tableview展示一组数据,以很多首歌曲为例子.为了便于查找,一般会把这些歌曲按照一定的顺序排列,还会加上索引条以便于快速定位. 由于歌曲名可能有数字 ...

  7. "浅谈Android"第一篇:Android系统简介

    近来,看了一本书,名字叫做<第一行代码>,是CSDN一名博主写的,一本Android入门级的书,比较适合新手.看了书之后,有感而发,想来进行Android开发已经有一年多了,但欠缺系统化的 ...

  8. 浅谈iOS开发的协议(protocol)和代理(delegate)

    协议和代理对于一个新手来说确实不讨好理解,也有很多的iOS开发的老手对此是懂非懂的.网上的很多博文只是讲了怎么使用,并没有说的很明白.下面我谈一下我的理解. 1.你要先搞明白,协议和代理为什么会出现, ...

  9. 浅谈Web开发中的定时任务

    曾经做过Windows server下的定时任务的业务,最近又做了一些Linux下使用Crontab做的定时任务的业务,觉得有必要进行一次小结,于是有了如下这篇文章. Windows Server下 ...

  10. 浅谈教你如何掌握Linux系统

    linux能做什么?相信绝大数人都有这样的疑问.可以玩吃鸡吗?可以玩lol吗? 如果你是以娱乐的名义来评价linux的可用性,对不起,linux可能不适合你,因为linux是一个工具,他是教你聪明的, ...

随机推荐

  1. mac 10.11.6,Xcode8下,ruby2.3安装,Cocoapods安装~

    适用环境 mac: 10.11.6 Xcode:8.1 命令执行步骤(安装ruby2.3前准备工作) 查看ruby更新源 gem sources  -L 删除默认官方或者淘宝,新增 https://g ...

  2. 自己对Extjs的Xtemplate的忽略

    之前学习extjs Xtmeplate受一些书籍的误导,说Xtemplate不支持else ,今天仔细看了官网的示例,才恍然大悟,卧槽!不仅支持if-elseif-else结构 连switch都能够支 ...

  3. svn服务器搭建与使用

  4. 通俗易懂的 JSon解析处理

    1.主要用到的类: 主要用到了JavaScriptSerializer类,该类在System.Web.Script.Serialization命名空间(在System.Web.Extensions.d ...

  5. isee - 创建项目 - 1

    1.在本地web目录下创建一个新项目 D:\web> composer create-project laravel/laravel isee --prefer-dist 2.在vhosts.c ...

  6. Angular JS将数据显示为两列(html)

    (数据为Array数组)使用AngularJS中ng-show="{{}}",其将数据按行分为奇数行和偶数行,$even是判断是否为奇数行[如果是则为true,不是则为false] ...

  7. Pointers and Dynamic Allocation of Memory

    METHOD 1: Consider the case where we do not know the number of elements in each row at compile time, ...

  8. asp.net core 使用protobuf

    在一些性能要求很高的应用中,使用protocol buffer序列化,优于Json.而且protocol buffer向后兼容的能力比较好. 由于Asp.net core 采用了全新的MiddleWa ...

  9. 《LINUX内核设计与实现》读书笔记之第五章

    第五章——系统调用 5.1 与内核通信 1.为用户空间提供一种硬件的抽象接口 2.保证系统稳定和安全 3.除异常和陷入,是内核唯一的合法入口. API.POSIX和C库 关于Unix接口设计:提供机制 ...

  10. 带回调函数的js运动框架

    function startMove(obj, json, endFun) { //开始前关闭之前obj上的定时器 clearInterval(obj.timer); //定时器 obj.timer ...