在开始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. Windows Desktop 调用 WinRT api

    <Reference Include="Windows"> <HintPath>..\..\..\..\..\..\Program Files (x86)\ ...

  2. LinuxMM--Memory Pressure

    Memory pressure定义在操作系统中,用户分配.文件缓存.网卡包缓冲区等等都会消耗内存.一旦出现内存紧张就会导致memory pressure.引发当某个任务需要请求内存时就有可能引发mem ...

  3. How to: Change Sales Rep/Team via Mass Update

    /* from: https://netsuite.custhelp.com/app/answers/detail/a_id/30057/kw/reassign%20sales */ How to c ...

  4. Oracle学习指南

    Oracle学习指南 你走的那天,我决定不落泪,迎着风撑着眼帘用力不眨眼 创建数据库.创建用户.创建表空间.创建表.插入数据..... 1.用系统用户登录,任选系统用户 代码: >>sql ...

  5. 使用django-admin.py 时出错

    我在安装好django后,运行django-admin.py 时出现两处错误: 一.当你在dos命令下输入django-admin.py 时不会运行,而是以记事本的方式打开了. 解决办法:找到你的dj ...

  6. How to Use Lucene.NET with Windows Azure SQL Database

    http://social.technet.microsoft.com/wiki/contents/articles/2367.how-to-use-lucene-net-with-windows-a ...

  7. ubuntu gcc-5 安装

    安装了一个ubuntu 15.10,没有集成vim,很失望,先安装个vim,sudo apt-get install vim. 开始获取g++-5: $ sudo add-apt-repository ...

  8. jetty9 安装部署更改端口号

    1.下载jetty 并解压到指定目录 2.更改默认端口号--在start.d中的http.ini中修改 3.启动服务 在jetty的根目录中输入命令 java -jar start.jar  服务就启 ...

  9. vb---输入模式之文本输入与二进制输入区别

    使用 VB6 MSCOMM 控件 进行二进制收发 发布时间:2012-01-10 12:12:01 技术类别:嵌入式     MSCOMM 控件是用于串口通信的,使用方便.在VB中,这个串口控件缺省是 ...

  10. 使用printf输出各种格式的字符串( 转载)

    1. 原样输出字符串:    printf("%s", str); 2. 输出指定长度的字符串, 超长时不截断, 不足时右对齐:    printf("%Ns" ...