摘要:探究托管 Office 解决方案的代码,该代码显示了可用于 Microsoft Office 系统命令栏的所有按钮表面。了解如何在托管代码中使用命令栏对象模型,同时使解决方案可以跨版本兼容。最终的解决方案显示了所有可用的 Office 命令栏按钮表面和 faceIDs(一个对开发自定义命令栏非常有用的工具)。
本页内容
简介
作为 Microsoft ® Office 开发人员,最困难的事情之一就是使用命令栏和命令栏按钮。从简单到复杂的解决方案可能都要求在小按钮上使用常规文字作为标题,但还有一些解决方案使用吸引人的独立小图标或带有文字的小图标来标识特征。此处的图 1 显示了一个包含所有三种方式的示例。
图 1. 按钮可以具有图像、文字或二者兼有
使用按钮功能图标创建命令栏时,有数千种可能的图标可供选择。尝试找出合适的命令栏表面的最常见做法是用宏编写一个过程,该过程使用循环结构依次通过编号,并将它们指定为测试按钮的 Faceid 属性,以查看生成图标的外观。
本文的解决方案建立在这一相同的概念上,但是添加了更为优雅的外观(如图 2 所示)。而且,它还可以在不同应用程序的不同 Microsoft Office 版本中使用,例如 Microsoft Office PowerPoint ® 2003、Microsoft Office Word 2003 或 Microsoft Office Excel 2003。
图 2. 外接程序显示按钮表面并在工具提示文本中提供表面 ID
创建外接程序
外接程序是 Microsoft .NET 程序集,它通过 COM 互操作性来使用 Office 对象模型。在本例中,.NET 程序集是以 C# 编写的,但是如果需要,也可以将其轻松地转换为另一种与 .NET 兼容的语言。要创建项目,请遵循以下步骤来启动和使用 Extensibility Wizard:
1.
在 Microsoft Visual Studio ® .NET 中,创建一个项目,并在 New Project 对话框中选择 Extensibility Projects。
2.
在该对话框的 Templates 窗口中,单击 Shared Add-in,如图 3 所示。
图 3. 在 Visual Studio .NET 中选择“Shared Add-in”项目类型
3.
为项目指定一个名称(例如 ButtonFaces),再指定项目位置,然后单击 OK。
4.
在下一步中(图 4),选择要宿主该外接程序的应用程序。单击 Microsoft Word 和 Microsoft Excel,然后单击 Next。
图 4. 使用 Extensibility Wizard 来选择要宿主自定义外接程序的应用程序
5.
该对话框可让您为外接程序键入名称和说明。如图 5 所示进行配置。
图 5. 配置外接程序的名称和说明
6.
在下一个窗口中(图 6),选中两个复选框,以便在宿主应用程序启动时加载该外接程序,并且任何用户都可以使用该外接程序。单击 Next。
图 6. 选择何时加载外接程序以及哪些用户应该访问它
7.
单击 Finish 以完成 Extensibility Wizard 中的步骤。
准备外接程序以供使用
项目设置完后,Visual Studio .NET 会创建一个名为 Connect 的类。该类包含由 Extensibility Wizard 自动生成的方法,用于实现外接程序的接口(IDTExtensibility2 接口)。所需的方法由以下内容开始:
public void OnConnection(object application,
Extensibility.ext_ConnectMode connectMode,
object addInInst, ref System.Array custom)
{
applicationObject = application;
addInInstance = addInInst;
if(connectMode != Extensibility.ext_ConnectMode.ext_cm_Startup)
{
OnStartupComplete(ref custom);
}
}
当外接程序在宿主应用程序中启用时,会激发该方法。对宿主应用程序的引用将作为一个参数传递到该方法。这将允许代码设置指向宿主应用程序的内部变量。代码从此处调用另一个向导生成的方法 OnStartupComplete,以开始准备要使用的外接程序。该方法的代码使用某些变量来访问命令栏,向宿主应用程序中的菜单添加新按钮,以及了解宿主应用程序的类型。这些声明的代码和 OnStartupComplete 方法如下所示:
private Office.CommandBarButton getButtonFaces;
private Office.CommandBars bars;
private Type applicationType;
public void OnStartupComplete(ref System.Array custom)
{
Office.CommandBar bar = null;
string buttonName = "Get Button Faces";
try
{
applicationType = applicationObject.GetType();
bars =
(Office.CommandBars)applicationType.InvokeMember(
"CommandBars", BindingFlags.GetProperty, null,
applicationObject, null);
bar = bars["Tools"];
object missing = Missing.Value;
Office.CommandBarButton button = (Office.CommandBarButton)
bar.FindControl(Office.MsoControlType.msoControlButton,
missing, buttonName, true, true);
if (button == null)
{
getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
Office.MsoControlType.msoControlButton,
missing, missing, missing, missing);
getButtonFaces.Caption = buttonName;
getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
getButtonFaces.Tag = buttonName;
}
else
{
getButtonFaces = button;
}
getButtonFaces.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
getButtonFaces_Click);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
该代码的目的就是向宿主应用程序的 Tools 菜单添加按钮,如图 7 所示。
图 7. 外接程序从宿主应用程序的 Tools 菜单中启动
使用 InvokeMember 方法,代码可获得对宿主应用程序的 CommandBars 集合的引用,然后检索 Tools 命令栏。在使用 .NET Framework 中所谓的反射(System.Reflection namespace 的一部分)时,会使用 InvokeMember 方法。反射可允许代码发现和检查程序集中类型的成员。使用反射,您还可以使用指定的绑定约束并为成员匹配指定的参数列表,来调用这些类型的成员。因为该代码包含对宿主应用程序的类型的引用,所以我们可以使用该类型的 InvokeMember 方法来执行诸如检索宿主应用程序中的 CommandBars 集合之类的操作。
bars = (Office.CommandBars)applicationType.InvokeMember(
"CommandBars", BindingFlags.GetProperty, null,
applicationObject, null);
bar = bars["Tools"];
通过使用 FindControl 方法,代码可以查看是否已经安装了所需的按钮。在 Excel 中,试图添加现有的按钮会引发异常,但是 Word 允许将相同的按钮添加多次。在代码中捕获异常不能同时适用于这两种情况,所以在试图添加按钮之前,最好先检查一下该按钮是否已存在。
bar.FindControl(Office.MsoControlType.msoControlButton,
missing, buttonName, true, true);
如果没有安装该按钮,则代码会将该按钮添加到目标命令栏。
if (button == null)
{
getButtonFaces = (Office.CommandBarButton) bar.Controls.Add(
Office.MsoControlType.msoControlButton,
missing, missing, missing, missing);
getButtonFaces.Caption = buttonName;
getButtonFaces.Style = Office.MsoButtonStyle.msoButtonCaption;
getButtonFaces.Tag = buttonName;
}
否则,会获取一个对现有按钮的引用。
else
{
getButtonFaces = button;
}
在任何一种情况下,均由按钮 getButtonFaces 来启动应用程序的主功能。因为该按钮是构建命令栏(即大部分外接程序)的方法,所以我们需要一个该按钮的事件过程。可以用以下方法添加一个事件过程:
getButtonFaces.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
getButtonFaces_Click);
对接口进行编码
由代码添加到菜单(图 7)的按钮事件处理程序主要用于启动外接程序的主功能。事件处理程序的代码如下所示:
private void getButtonFaces_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
if (buttonFaceBar == null)
{
BuildCommandBar();
SetupNavigationButtons();
AttachButtonFaces(0);
}
}
该事件处理程序代码调用另外三个过程,其中第一个包含外接程序其余部分的大量代码。BuildCommandBar 过程负责构建显示图标的命令栏,以及将对新命令栏的引用存储到 buttonFaceBar 变量中。SetupNavigationButtons 过程将导航按钮放在界面上,以便用户可以按每组一百个的方式来查看按钮表面。AttachButtonFaces 过程实际显示新创建命令栏上的按钮表面。按照执行顺序查看这些过程,BuildCommandBar 代码如下所示:
private const short buttonsInRow = 20;
private const short buttonsWithNavigation = 120;
private Office.CommandBar buttonFaceBar;
public void BuildCommandBar()
{
try
{
buttonFaceBar = bars.Add("Button Faces",
Office.MsoBarPosition.msoBarFloating, false, true);
buttonFaceBar.Protection =
Office.MsoBarProtection.msoBarNoChangeVisible
| Office.MsoBarProtection.msoBarNoResize
| Office.MsoBarProtection.msoBarNoChangeDock;
int appTop = 0;
int appLeft = 0;
string appName = (string)applicationType.InvokeMember(
"Name", BindingFlags.GetProperty, null,
applicationObject, null);
switch (appName)
{
case "Microsoft Excel":
appTop = (int)(double)applicationType.InvokeMember(
"Top", BindingFlags.GetProperty,
null, applicationObject, null);
appLeft = (int)(double)applicationType.InvokeMember(
"Left", BindingFlags.GetProperty,
null, applicationObject, null);
break;
case "Microsoft Word":
appTop = (int)applicationType.InvokeMember(
"Top", BindingFlags.GetProperty,
null, applicationObject, null);
appLeft = (int)applicationType.InvokeMember(
"Left", BindingFlags.GetProperty,
null, applicationObject, null);
break;
default:
break;
}
buttonFaceBar.Top = (int) (appTop + 50);
buttonFaceBar.Left = (int) (appLeft + 50);
Office.CommandBarButton button = null;
for (int i = 1; i <= buttonsWithNavigation; i++)
{
object missing = Missing.Value;
button = (Office.CommandBarButton)
buttonFaceBar.Controls.Add(
Office.MsoControlType.msoControlButton, 1,
missing, missing, missing);
button.FaceId = 1;
}
GetHighestFaceIds(
(Office.CommandBarButton)buttonFaceBar.Controls[1]);
buttonFaceBar.Visible = true;
buttonFaceBar.Width = button.Width * buttonsInRow +6;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
该过程的主要流程是,首先将新命令栏添加到宿主应用程序的 CommandBars 集合。
buttonFaceBar = bars.Add("Button Faces",
Office.MsoBarPosition.msoBarFloating, false, true);
buttonFaceBar.Protection =
Office.MsoBarProtection.msoBarNoChangeVisible
| Office.MsoBarProtection.msoBarNoResize
| Office.MsoBarProtection.msoBarNoChangeDock;
CommandBars 集合的 Add 方法接受四个参数。第一个是目标命令栏的名称。第二个是该命令栏的位置,在本例中是浮动命令栏。第三个带有 False 值,它指定新命令栏不应用新命令栏替代活动菜单栏。最后一个参数告诉宿主应用程序,命令栏在应用程序关闭时不应被删除。
添加新命令栏之后,代码将设置 Top 和 Left 属性偏移量,以便将新创建的命令栏定位在相对于宿主应用程序的 Top 和 Left 属性的特定位置上。命令栏本身包括一组按钮,确切地说是 120 个按钮,这些按钮在用户查看成千上万个按钮表面时被重复使用。在请求每个新的按钮表面组时,新的按钮图像会被复制到现有按钮中。即使有 120 个按钮之多,但实际上只使用一行按钮来进行导航,所以用户可以按每组一百个的方式来查看按钮表面。界面按钮包括用于导航的按钮和一个取消按钮。
Office.CommandBarButton button = null; for (int i = 1; i <= buttonsWithNavigation; i++) { object missing = Missing.Value; button = (Office.CommandBarButton) buttonFaceBar.Controls.Add( Office.MsoControlType.msoControlButton, 1, missing, missing, missing); button.FaceId = 1; }
在添加新按钮的循环操作中,使用主命令栏的 Controls 属性来添加新的空白按钮。调用 Add 方法来添加和返回新按钮。通过将 msoControlButton 参数传递给该方法,可以指定新按钮应为一个简单的按钮,而非下拉按钮或一长串可能按钮类型中的一种。第二个参数(在这里值为 1)指定将指定类型的空白控件添加到命令栏。然后,将它的 FaceId 属性设为 1,以使其成为空白控件或者其表面没有图像。这与指定控件类型不同,它只规定哪种按钮表面(如果有)应当显示在按钮上。
界面准备完成后,代码将通过调用自定义过程 GetHighestFaceIds 来查找最后一组按钮的起始点。然后,它使命令栏可见,并设置其宽度。该宽度由这些按钮的宽度乘以按钮的数量得出。此外,还要将一个任意数添加到该计算中。这个数字(下例中的 6)可能不适合所有的屏幕分辨率或风格。当然,应当进行完全测试。也可以设计一个更能区分上下文的计算。
GetHighestFaceIds(
(Office.CommandBarButton)buttonFaceBar.Controls[1]);
buttonFaceBar.Visible = true;
buttonFaceBar.Width = button.Width * buttonsInRow +6;
为了找到最后一组按钮的起始点,GetHighestFaceIds 过程以 1000 的增量来依次通过每个编号,最多达到不合情理的高限(一千万),尽管该代码足够长,并且在它到达那个编号时尚未结束。设置 FaceId 属性 (testButton.FaceId = i) 会在数字过大时引发错误。在引发异常时,代码会在最终识别最后一组按钮表面的起始点之前,以较小的增量依次通过编号。在退出之前,代码会继续寻找最后一个可用的表面 ID,并将其存储在 finalID 变量中。GetHighestFaceIds 过程将当前 Office 应用程序版本中开始某个组的按钮所能具有的最大表面 ID 值存储在 highestStartID 变量中。
private int highestStartID;
private int finalID;
public void GetHighestFaceIds(Office.CommandBarButton testButton)
{
int increment = 1000;
int loopStart = 1000;
for (int i = loopStart; i <= 10000000; i += increment)
{
try
{
testButton.FaceId = i;
}
catch (Exception)
{
if (increment == 1000)
{
i -= 1000 + 100;
increment = 100;
}
else if (increment == 100)
{
highestStartID = i - buttonsInGroup;
i -= 100 + 1;
increment = 1;
}
else
{
finalID = i - 1;
break;
}
}
}
}
通过调用 BuildCommandBar 准备好命令栏之后,getButtonFaces_Click 过程(在用户决定使用来自宿主应用程序 Tools 菜单的外接程序时引发的事件过程)就可以设置导航按钮,然后开始用可见的图像填充其按钮。设置导航按钮是在 SetUpNavigationButtons 过程中完成的。虽然设置过程有点冗长,但代码却非常简单,只是添加具有它们自己固定的按钮表面和相应事件处理程序的导航按钮。第一个按钮(适当地命名为 firstButton)包括一个与其他按钮明显不同的属性设置,即,它的 BeginGroup 属性设为 True。这可确保该按钮位于命令栏上所有其他按钮的前面。以下代码摘录还包含了适合于每个导航按钮的按钮表面的值。此处,使用的是现有按钮表面的值。该代码还包括了一个设置 firstButton 导航按钮 ID 的常量 (firstNavigationButton),该导航按钮是底行的第七个按钮。因为有一百个按钮,所以该 ID 必须是 100 加上 7。
private Office.CommandBarButton firstButton;
private Office.CommandBarButton bigPreviousButton;
private Office.CommandBarButton previousButton;
private Office.CommandBarButton nextButton;
private Office.CommandBarButton bigNextButton;
private Office.CommandBarButton lastButton;
private Office.CommandBarButton cancelButton;
private const short firstNavigationButton = 107;
private const short firstFace = 154;
private const short bigPreviousFace = 41;
private const short previousFace = 155;
private const short nextFace = 156;
private const short bigNextFace = 39;
private const short lastFace = 157;
private const short cancelFace = 478;
public void SetupNavigationButtons()
{
try
{
firstButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton];
firstButton.BeginGroup = true;
firstButton.FaceId = firstFace;
firstButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
firstHundred_Click);
firstButton.TooltipText = "First 100";
bigPreviousButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+1];
bigPreviousButton.FaceId = bigPreviousFace;
bigPreviousButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
bigPreviousHundred_Click);
bigPreviousButton.TooltipText = "Previous 1000";
previousButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+2];
previousButton.FaceId = previousFace;
previousButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
previousHundred_Click);
previousButton.TooltipText = "Previous 100";
nextButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+3];
nextButton.FaceId = nextFace;
nextButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
nextHundred_Click);
nextButton.TooltipText = "Next 100";
bigNextButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+4];
bigNextButton.FaceId = bigNextFace;
bigNextButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
bigNextHundred_Click);
bigNextButton.TooltipText = "Next 1000";
lastButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+5];
lastButton.FaceId = lastFace;
lastButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
lastHundred_Click);
lastButton.TooltipText = "Last 100";
cancelButton =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+6];
cancelButton.FaceId = cancelFace;
cancelButton.Click +=
new Office._CommandBarButtonEvents_ClickEventHandler(
cancelFaceBar_Click);
cancelButton.TooltipText = "Close";
Office.CommandBarButton button =
(Office.CommandBarButton)buttonFaceBar.Controls
[firstNavigationButton+7];
button.BeginGroup = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
按钮及其用途的列表如下所示:
按钮名称
事件过程
用途
firstButton
firstHundred_Click
显示前 100 个按钮
bigPreviousButton
bigPreviousHundred_Click
显示当前组之前的 1000 个按钮
nextButton
nextHundred_Click
显示当前组之后的 100 个按钮
previousHundred
previousHundred_Click
显示当前组之前的 100 个按钮
bigNextButton
bigNextHundred_Click
显示当前组之后的 1000 个按钮
lastButton
lastHundred_Click
显示最后 100 个按钮
cancelButton
cancelFaceBar_Click
关闭自定义命令栏
这些事件过程的相应代码如下所示:
private void firstHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
AttachButtonFaces(0);
}
private void bigPreviousHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
if (latestStartID >= buttonsInGroup)
{
AttachButtonFaces(Math.Max(0, latestStartID - 1000));
}
}
private void previousHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
if (latestStartID >= buttonsInGroup)
{
AttachButtonFaces(latestStartID - buttonsInGroup);
}
}
private void nextHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
if (latestStartID < highestStartID)
{
AttachButtonFaces(latestStartID + buttonsInGroup);
}
}
private void bigNextHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
if (latestStartID < highestStartID)
{
AttachButtonFaces(Math.Min(highestStartID, latestStartID + 1000));
}
}
private void lastHundred_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
AttachButtonFaces(highestStartID);
}
private void cancelFaceBar_Click(
Office.CommandBarButton cmdBarbutton, ref bool cancelButton)
{
CloseFaceBar();
}
取消使用自定义命令栏的事件过程会调用一个 CloseFaceBar 过程。该过程可以十分简单地删除自定义命令栏。
private void CloseFaceBar()
{
try
{
buttonFaceBar.Delete();
buttonFaceBar = null;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
AttachButtonFaces 过程使用起始点,并用图像表面从该起始点开始填充按钮。该过程使用两个变量(latestStartID 和 finalID)来记录当前组开头的按钮的 ID。这个 ID 会随着用户在按钮表面组上移动而递增或递减。
private const short buttonsInGroup = 100;
private int latestStartID;
public void AttachButtonFaces(int startID)
{
int i = 1;
latestStartID = startID;
UpdateNavigationButtons();
int relativeFinalID = finalID - latestStartID;
try
{
for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
{
Office.CommandbarButton button =
(Office.CommandBarButton)buttonFaceBar.Controls[i];
int id = startID + i;
button.FaceId = id;
button.TooltipText = id.ToString();
}
buttonFaceBar.Name = String.Format(
"Button Faces ({0}..{1})", startID +1, startID + i - 1);
ClearUnusedFaces(i);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
为了让用户了解显示的是哪一组按钮,应使用具有两个占位符的值的标题串联来设置命令栏的显示名称,一个用于组中的起始 faceID,另一个用于结束 faceID:
buttonFaceBar.Name = String.Format(
"Button Faces ({0}..{1})", startID +1, startID + i - 1);
通过使用整个组的起始点,代码可以依次通过新的按钮表面直至达到组限制 buttonsInGroup。
for (; i <= buttonsInGroup && i <= relativeFinalID; i++)
{
Office.CommandBarButton button =
(Office.CommandBarButton)buttonFaceBar.Controls[i];
int id = startID + i;
button.FaceId = id;
button.TooltipText = id.ToString();
}
通过该方法,AttachButtonFaces 过程执行了两个较为重要的操作。第一,它更新了导航按钮,以便在按钮无法使用时将其禁用。例如,如果用户位于列表末尾,则让用户查看更多按钮表面的按钮应该被禁用。同样,如果用户位于列表开头,则应该不能够向回导航,因此应当禁用相应的按钮。该逻辑是在 UpdateNavigationButtons 过程中处理的。
public void UpdateNavigationButtons()
{
try
{
buttonFaceBar.Controls[firstNavigationButton].Enabled =
latestStartID != 0;
buttonFaceBar.Controls[firstNavigationButton+1].Enabled =
latestStartID >= 1000;
buttonFaceBar.Controls[firstNavigationButton+2].Enabled =
latestStartID != 0;
buttonFaceBar.Controls[firstNavigationButton+3].Enabled =
latestStartID != highestStartID;
buttonFaceBar.Controls[firstNavigationButton+4].Enabled =
latestStartID <= highestStartID - 1000;
buttonFaceBar.Controls[firstNavigationButton+5].Enabled =
latestStartID != highestStartID;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
第二,AttachButtonFaces 过程调用了 ClearUnusedFaces 过程,以便如果按钮表面列表的末尾处有未使用的按钮,则清除这些按钮的任何预先存在的内容。
public void ClearUnusedFaces(int firstUnusedButton)
{
try
{
for (int i = firstUnusedButton; i <= buttonsInGroup; i++)
{
Office.CommandBarButton button =
(Office.CommandBarButton)buttonFaceBar.Controls[i];
button.FaceId = 1;
button.TooltipText = "";
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
对整个应用程序进行编码,除了在从宿主应用程序卸载外接程序时运行的例程。还有一个作为 IDTExtensibility2 接口一部分的内置事件过程,它可让您根据条件运行代码,这取决于触发外接程序卸载的原因。
public void OnDisconnection(
Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
if(disconnectMode !=
Extensibility.ext_DisconnectMode.ext_dm_HostShutdown)
{
OnBeginShutdown(ref custom);
}
applicationObject = null;
}
public void OnBeginShutdown(ref System.Array custom)
{
object missing = Missing.Value ;
getButtonFaces.Delete(missing);
getButtonFaces = null;
}
该代码将检查宿主应用程序是否已关闭,这会导致外接程序被卸载。如果卸载外接程序不是宿主应用程序关闭所导致的结果,那么代码将允许 OnBeginShutdown 过程运行。该事件中的代码只会从宿主应用程序的 Tools 菜单中删除该按钮。
建立和部署外接程序
最后一个步骤是建立和部署外接程序。在使用 Visual Studio .NET 中的 Extensibility Wizard 首次创建整个解决方案时,不但创建了实际的外接程序,还创建了第二个项目并将其添加到解决方案中。这第二个项目就是外接程序本身的安装项目。项目文件(请参见图 8)包括为外接程序检测到的依赖项,一旦建立项目,它就会成为可以启动以安装外接程序的 Microsoft Installer 软件包。
图 8. 总体解决方案包括项目和外接程序的设置文件
要在 Visual Studio .NET 中构建外接程序,请在 Build 菜单上单击 Build Solution。这将创建一个 .NET 程序集,并为 COM 互操作性创建必要的基础结构。生成安装项目的一种方法是,在 Solution Explorer 窗口中右键单击项目,然后单击 Build。这将生成安装项目,并创建 .MSI 文件。要找到生成的 Microsoft Installer 软件包,请查看解决方案和应用程序项目文件的同一目录。在那里,您可以找到 ButtonFacesSetup 目录。在该目录中,您可以看一下已经完成的 build 类型目录,发布或调试,以及查找 .MSI 文件。双击该文件可以启动安装,但这一步在开发计算机上可以不执行,因为它已经注册并可以使用了。要使用外接程序,请启动 Word 或 Excel,并在 Tools 菜单上单击 Get Button Faces。
为其他应用程序添加支持
迄今为止,外接程序只支持 Word 和 Excel。需要记住的是,每个应用程序(但应该是 Microsoft Office 系统的一部分)都可能具有与同一系列中其他应用程序不同的行为和属性。例如,Word 和 Excel 中命令栏支持的轻微差别,就意味着要对代码进行更改以适应它们。同样,为其他应用程序(例如 PowerPoint)添加支持就意味着,您必须在代码的其他位置上添加条件代码。完全了解对象模型和严格的测试,可确保您将这些差异挑选出来,并调整您的代码以开发稳定可靠的外接程序。
至于 Visual Studio 中的项目,实际上只需要进行一项调整,就可以使外接程序在其他应用程序中可用。图 9 显示了外接程序安装项目的 Registry Editor 界面。
图 9. 使用 Registry Editor 在目标计算机上配置注册表设置
安装项目中的 Registry Editor 可让您配置在安装项目运行时,您希望已经在目标计算机上配置好的注册表设置。例如,在 图 9 中,您可看到 Excel、PowerPoint 以及 Word 的注册表项。ButtonFaces.Connect 项具有三个值,其中两个是字符串值,一个是数值,该项可告诉宿主应用程序应该在何时加载外接程序。在本例中,这三个值指定外接程序应该在宿主应用程序启动时加载。在扩展性向导首次运行以创建初始项目时,PowerPoint 项中的外接程序注册表项不存在。这可在稍后手动进行添加,以使外接程序可以用于 PowerPoint。再次运行安装项目可以将该注册表项添加到目标机器的注册表中。此外,您可能需要编写条件代码,以适应宿主应用程序的对象模型中的差异。
小结
在设计用户友好的 Office 应用程序时通常会使用命令栏。然而,在命令栏按钮上也经常使用图像,而不是文本。在为按钮表面创建自定义图像之前,最好先查看一下是否已经存在适合您需要的图像。在将近数千个按钮表面中是存在这种可能性的,总会有一个符合您的应用程序要求。
本文中的代码使得查找按钮表面库以寻找所需按钮表面更为简便。该代码是以 C# 编写的,它创建的外接程序可以用于 Office 2000、Office XP 以及 Office 2003。它对不同 Office 版本的可移植性在某种程度上是可能的,这是因为它并没有对每个版本中提供的按钮表面数量进行假设。这些数量在不同的版本中确实各不相同,但是外接程序代码设计为可智能地查明数量,并相应地进行响应。