技术开发 频道

详解VSTS与OFFICE的协同开发:Excel篇

  【IT168 专稿】书接上回,在前面两篇文章中,我们结合Office Word和 Office Outlook,分别介绍了如何在Visual Sutton  2010中进行文档级自定义项和应用程序级插件的开发。大家应该都注意到,这两种Office扩展应用几乎都是在宿主程序的某个动作后自动执行,无法跟用户进行交互。

  在第一个文档级自定义项的例子中,如果我们想在文档打开过后,按照需要在合适的位置插入一个请假条,该怎么办呢?同样的在第二个Outlook整理附件的例子中,如果我们想在收取所有邮件后,手动地进行邮件的分拣处理该如何进行呢?这时,我们就需要用到Office开发的第三种情况:Office Ribbon界面插件。

  第一篇:详解VSTS与OFFICE的协同开发:WORD篇

  第二篇:详解VSTS与OFFICE的协同开发:Outlook篇

  通过前面文章的介绍,我们知道在Visual Studio 2010中,Office开发主要有以下三种类型:文档级自定义项,应用程序级插件和Ribbon界面插件。前两种插件应用都是自动加载运行,无法跟用户进行交互,所以其应用场合有限。在这篇文章中,我们将介绍能够跟用户进行交互,从而能够完成更加复杂的Office任务的Ribbon界面插件。

  Ribbon界面插件是在文档级自定义项或者是应用程序级插件的基础上添加Ribbon界面控件而完成的。跟应用程序级插件相类似,Office Ribbon界面插件随着宿主程序的启动而被加载,同时在Office宿主程序的Ribbon界面中添加相应的控件,从而利用这些控件与用户进行交互,实现相应的功能。在Ribbon界面插件中,你可以自定义Office程序的Ribbon UI,可以在功能区以控件形式的组织相关的命令,完成相应的功能。界面插件通过在Office程序的“Add-ons”Ribbon页面中添加相应的控件,使它们易于查找,从而与用户进行界面交互,实现相应的功能。


图1 Ribbon界面插件添加的Ribbon界面

  在本文中,我们以Excel为例,介绍如何利用Ribbon界面插件,获取用户的在界面上的输入输入,然后根据用户的输入对Excel的数据进行计算和处理。在进行具体的开发之前,我们还是先来了解一下Excel对象模型。

  Excel对象模型概述

  由于 Excel 文档中的数据是高度结构化的,因此Excel对象模型也具有层次结构简单明了的特点。我们都知道,Excel可以说是Office套件中最复杂程序了。为了操作Excel,它提供了数百个我们可能需要与之交互的对象,不过我们可以从这些对象中的少数几个开始来掌握Excel对象模型。其中我们最常用的几个类是:

  •Application类
  Application类的对象表示 Excel 应用程序本身。Application 对象公开了大量有关正在运行的应用程序、应用于该实例的选项以及在该实例中打开的当前用户的对象的信息等。

  •Workbook类
  Workbook类表示 Excel 应用程序内的单个工作簿。

  •Worksheet类
  Worksheet 对象是 Worksheets 集合的成员。Worksheet类的许多属性、方法和事件与 Application 或 Workbook 类提供的成员完全相同或相似。

  •Range类
  Range 对象是 Excel 应用程序中最常用的对象。在对 Excel 内的任何范围进行处理之前,我们必须将它表示为 Range 对象,并使用该对象的方法和属性对其进行处理或者获得它的值。Range 对象可以表示一个单元格、一行、一列、包含一个或多个单元格块(可以连续,也可以不连续)的单元格选定范围,甚至多个工作表中的一组单元格。

  在Visual Studio中,VSTO将这些本机对象中的很多对象扩展为可在文档级自定义项中使用的宿主项和宿主控件。这些控件具有额外的功能,包括数据绑定功能和事件等。例如,本机 的Range 对象被扩展为 NamedRange 控件,该控件可与数据绑定并公开事件。

  使用 Excel 完成的很多工作都是围绕着这四个类和它们的成员进行的。Excel对象模型严格遵循着这样的结构层次:Application 对象表示整个应用程序,每个 Workbook 对象都包含 Worksheet 对象的一个集合。而在每个Worksheet中,我们使用Range对象来表示单元格抽象。使用Range对象可以处理单个单元格或者成组的单元格。  

  在使用 Visual Studio 2010 创建新的 Excel 文档级自定义项项目时, Visual Studio会为我们创建ThisWorkbook,Sheet1,Shee2和Sheet3着四个类,分别表示整个工作簿和其中的三个工作表。


图2 Visual Studio创建的类

  我们可以使用全局类 Globals 从 ThisWorkbook、Sheet1、Sheet2 或 Sheet3 类的外部分别访问各个类。例如,我们可以在Ribbon界面类中对Excel表格进行赋值:

private void button1_Click(object sender, EventArgs e)
{
    
if (Globals.Sheet1 != null)
    {
        Globals.Sheet1.namedRange1.Value2
= this.textBox1.Text;
    }
}

  这样,通过全局类Globals,我们可以在任意位置访问到我们需要的工作簿等Excel文的的内容。

  创建Excel Workbook项目

  在了解了Excel对象模型之后,我们就可以开始Ribbon界面插件的创建了。

  首先启动Visual Studio 2010,创建一个新的Visual C#类型的项目。在“新建项目”对话框中,我们选择“Excel 2007 Workbook”作为项目模板,然后输入项目名称为“MyExcelRibbon”,点击“确定”按钮关闭“新建项目”对话框,Visual Studio将为我们创建一个基于Excel的文档级自定义项目。我们将在这个项目的基础上,添加Ribbon界面。


图3 创建Excel 2007 Workbook项目

  添加Ribbon界面

  我们刚刚创建的Excel文档级自定义项项目并不包含Ribbon界面。为了使用Ribbon界面,我们在其基础上添加Ribbon界面控件。首先,我们添加两个活动面板作为Ribbon界面的侧面板。在“解决方案浏览器”中,我们在当前项目“MyExcelRibbon”上点击右键,在弹出的上下文菜单中我们选择“Add”->”New Item”,

图4 添加Ribbon界面元素

  这时我们会得到一个新建项对话框,在其中我们选择“ActionsPaneControl”(活动面板)作为新建项的类型并将它添加到项目中。重复这一过程,我们为项目添加两个活动面板。


图5 添加活动面板

  有了活动面板后,我们就可以在Excel的Ribbon界面的“Add-On”面板中,添加相应的控件,调用显示这两个活动面板了。同样的,通过项目右键上下文菜单的“Add”->“New Item”添加新的Ribbon界面,在“新建项”对话框中,我们选择“Ribbon (Visual Designer)”作为新建项的类型,并将它命名为MyRibbon。

  点击“Add”按钮关闭对话框,Visual Studio会为我们添加一个Ribbon界面,同时会为我们添加一个名为MyRibbon的类来对这个界面进行管理。这时,我们就可以在Visual Studio的Ribbon界面设计器中对Ribbon界面进行编辑了。根据我们的需要,我们在Ribbon界面上添加两个普通按钮,分别用于两个活动面板的显示,然后再添加一个开关按钮,控制活动面板的显示和隐藏。在设计器中,编辑Ribbon界面如下:
 


图6 在设计器中编辑Ribbon界面

  控制活动面板的显示和隐藏

  完成Ribbon界面的编辑后,我们就可以实现面板上各个按钮控件的动作,控制两个活动面板的显示和隐藏。
首先,我们在MyRibbon类中添加两个活动面板类的对象所为类的属性。

public partial class MyRibbon : OfficeRibbon
{
        ActionsPaneControl1 actionsPane1
= new ActionsPaneControl1();
        ActionsPaneControl2 actionsPane2
= new ActionsPaneControl2();
///
}

  然后,在MyRibbon类的构造函数中,添加界面上按钮控件的事件响应函数:
public MyRibbon()
{
            InitializeComponent();

            
this.button1.Click += new System.EventHandler
                
<Microsoft.Office.Tools.Ribbon.RibbonControlEventArgs>
                    (
this.button1_Click);
            
this.button2.Click += new System.EventHandler
                
<Microsoft.Office.Tools.Ribbon.RibbonControlEventArgs>
                    (
this.button2_Click);
            
this.toggleButton1.Click += new System.EventHandler
                
<Microsoft.Office.Tools.Ribbon.RibbonControlEventArgs>
                    (
this.toggleButton1_Click);
            
this.Load += new System.EventHandler
                
<Microsoft.Office.Tools.Ribbon.RibbonUIEventArgs>
                    (
this.MyRibbon_Load);
}

  大家可以看到在这段代码中,我们同时也对Ribbon界面加载的事件进行了处理,所以在这里我们将MyRibbon_Load()函数实现如下。在这段代码中,我们将两个活动面板添加为当前工作簿的活动面板,并且默认状态下不显示这些面板。

private void MyRibbon_Load(object sender, RibbonUIEventArgs e)
{
            
// 添加活动面板
            Globals.ThisWorkbook.ActionsPane.Controls.Add(actionsPane1);
            Globals.ThisWorkbook.ActionsPane.Controls.Add(actionsPane2);
            
// 默认隐藏活动面板
            actionsPane1.Hide();
            actionsPane2.Hide();
            Globals.ThisWorkbook.Application.DisplayDocumentActionTaskPane
= false;
}

  最后,我们将Ribbon界面上各个按钮控件的点击事件处理函数实现如下,完成活动面板的显示和隐藏:

private void button1_Click(object sender, RibbonControlEventArgs e)
  {
            
// 显示侧面板
            Globals.ThisWorkbook.Application.DisplayDocumentActionTaskPane =
                
true;
            
// 控制活动面板的显示
            actionsPane2.Hide();
            actionsPane1.Show();
}

private void button2_Click(object sender, RibbonControlEventArgs e)
{
            Globals.ThisWorkbook.Application.DisplayDocumentActionTaskPane
=
                
true;
            actionsPane1.Hide();
            actionsPane2.Show();
}

private void toggleButton1_Click(object sender,
            RibbonControlEventArgs e)
{
            
// 根据按钮状态,显示或者隐藏侧面板
            if (toggleButton1.Checked == true)
            {
                Globals.ThisWorkbook.Application.
                    DisplayDocumentActionTaskPane
= false;
            }
            
else
            {
                Globals.ThisWorkbook.Application.
                    DisplayDocumentActionTaskPane
= true;
            }
}

  这样,我们就可以通过Ribbon界面上的按钮来控制侧面板的显示和隐藏。


图7 Ribbon界面

  在Ribbon界面中处理数据

  到这里为止,我们已经可以以编程的方式控制Ribbon界面了,接下来我们通过在活动面板中添加控件获得用户的输入,对Excel表格中的数据进行处理。

  假设我们有这样一份考勤记录表,记录了每个员工每天的总工作时间,我们可以在Visual Studio中将这份考勤记录表设计如下:


图8 设计Excel表格

  我们希望能够统计得到某个时间段内,工作时间最长的员工的ID,找到最勤快的人。

  首先,我们编辑活动面板,添加两个Combo Box用于用户输入统计的具体时间段:


图9 编辑活动面板

  为了便于用户输入合法的查询起止时间,我们在插件加载的时候,就将表中所有记录的时间添加到Combo Box中,用户只需要从其中进行选择就可以了。所以,在MyRibbon类的MyRibbon_Load()函数中添加如下代码:

private void MyRibbon_Load(object sender, RibbonUIEventArgs e)
{
            
// …  

            
// 获取日期单元格的内容
            if (!bLoaded) // 判断是否已经加载
            {
                
// 设置已经加载,防止多次添加值
                bLoaded = true;

                
// 循环遍历所有记录日期的单元格
                for (int i = 2; i <= 11; ++i)
                {
                    
// 构造单元格的地址
                    string strCellIndex = "A" + i.ToString();
                    
// 通过全局对象,获取单元格
                    Range rng = Globals.Sheet1.Range[ strCellIndex ];

                    
// 获取单元格的内容
                    string strCell = "";
                    strCell
= rng.Text.ToString();
                    
// 通过调用活动面板类ActionsPaneControl1的AddDate函数,
                    
// 给它控制的Combo Box添加可选的日期值
                    actionsPane1.AddDate(strCell);
                }
            }
        }

  另外,我们将ActionsPaneControl1类的AddDate()函数实现如下,利用这个函数,同时向活动面板上的两个Combo Box添加可选值。

public void AddDate(string strDate)
{
            
// 将日期值添加到Combo Box中
            comboBoxFrom.Items.Add(strDate);
            comboBoxTo.Items.Add(strDate);
}

  这样,控件在加载的时候,就会将表格中所有的日期值添加到活动面板的Combo Box中供用户选择。
对Excel数据进行处理

  当用户在活动面板中通过Combo Box选择了合适的时间段之后,我们就需要根据用户的输入查询这个时间段内工作时间最多的员工ID。

首先,我们需要根据用户的输入决定统计的日期范围,也就是找出需要统计那几行数据。在活动面板查询按钮的点击动作响应函数中,我们根据用户的输入找到需要统计的范围:

// 得到某个时间段内工作时间最多的员工的ID
private void buttonGet_Click(object sender, EventArgs e)
{
            
// 从Combo Box中获得用户输入的日期范围
            string strCellFrom = comboBoxFrom.SelectedItem.ToString();
            
string strCellTo = comboBoxTo.SelectedItem.ToString();
            
// 根据用户输入,查找需要统计的开始行和结束行
            int nFromIndex = FindDateIndex(comboBoxFrom.SelectedItem.ToString());
            
int nToIndex = FindDateIndex(comboBoxTo.SelectedItem.ToString());
//
}

// 根据输入的日期字符串查找所在的行
private int FindDateIndex(string strDate)
{
            
int nCellIndex = 0;
            
string strCellAddress = "";

            Range currentFind
= null;
            Range allCell
= null;
            
// 所中Excel工作表Sheet1中的所有单元格作为查询范围
            allCell = Globals.Sheet1.Range["A1", "F12"];
            
// 在这些单元格中查找字符串strData,也就是用户输入的日期值
            currentFind = allCell.Find( strDate );

            
if (currentFind != null)
            {
                
// 找到字符串,返回字符串所在单元格的行数
                nCellIndex = currentFind.Row;
            }
            
return nCellIndex;

  在这段代码中,我们使用了Range类的Find函数,有过Office开发经验的朋友都知道,这是一个多参数函数,在以往的Office开发中,尽管我们只需要一个参数,由于它不支持可选参数,我们必须把所有参数都补齐,使得整个代码看起来相当的繁琐,例如:

this.Fruits.Find("apples", missing,
                Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart,
                Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext,
false,
                missing, missing);

  现在Visual Studio 2010中有了对可选参数的支持,整个代码简洁多了。

  当我们找到统计的起止范围后,我们就可以计算这个范围内的工作时间的总和。这里,为了展示对Excel的操作,我们使用Excel的求和公式SUM来计算这些单元格的和。

// 得到某个时间段内工作时间最多的员工的ID
private void buttonGet_Click(object sender, EventArgs e)
{
            
//  …

            
double fTotalTime = 0;
            
char cTotalIndex = 'A';
            
// 统计A到J列的总工作时间
            for (char cColIndex = 'B'; cColIndex < 'J'; ++cColIndex)
            {
                
// 构造单元格计算范围
                strCellFrom = cColIndex.ToString() + nFromIndex.ToString();
                strCellTo
= cColIndex.ToString() + nToIndex.ToString();

                
// 计算单元格范围内的总工作时间
                double fWorkTime = CalculateRange( strCellFrom, strCellTo);
              
// 记录较大的工作时间和单元格的列索引值
                if (fWorkTime > fTotalTime)
                {
                    fTotalTime
= fWorkTime;
                    cTotalIndex
= cColIndex;
                }
            }

            
// 得到工作时间最长的ID
             string strMessage;
            
string strID;
            
string strIDCellAddress = cTotalIndex.ToString() + Convert.ToString(1);
            Range IDRange
= Globals.Sheet1.Range[ strIDCellAddress];
            strID
= IDRange.Text.ToString();

            
// 构造输出消息
            strMessage = "" + comboBoxFrom.SelectedItem.ToString()
                
+ "" + comboBoxTo.SelectedItem.ToString() + "\n"
                
+ strID + "已经工作了" + fTotalTime + "小时。";  
            
// 显示消息框
            MessageBox.Show( strMessage );
}

// 计算单元格范围内的总工作时间
private double CalculateRange(string strCellFrom, string strCellTo)
{
            
// 创建NamedRange
            
// 使用一个临时单元格作为结果单元格
            NamedRange retRange =
                Globals.Sheet1.Controls.AddNamedRange(
                Globals.Sheet1.Range[
"A100"],
                
"RetRange" + strCellFrom + strCellTo);

            
// 构造Excel公式
            string strFormula;
            strFormula
= "=SUM(" + strCellFrom + ":" + strCellTo + ")";
            
// 设置结果单元格的公式
            retRange.Formula = strFormula;
            retRange.FormulaHidden
= true;

            
// 执行计算
            retRange.Calculate();

            
// 从结果单元格获取计算结果
            double fRet;
            
string strRet = retRange.Value2.ToString();
            fRet
= Convert.ToDouble( strRet );

            
return fRet;            
}

  这时,当我们在活动面板中选择时期范围,然后点击“查询”按钮,插件就会为我们统计这段时间范围内工作时间最多的员工ID,并以消息的形式报告给我们。


图10 Excel数据处理结果

  至此,我们已经完成了在Visual Studio 2010中开发三种Office插件应用的整个历程。强大的Visual Studio 2010给Office开发带来了新的动力,使得Office开发如虎添翼,成为一个成熟的并广为接受的开发平台。

0
相关文章