使用 Visual Studio 2008 C++/CLI 自动化 Excel
本文将向您展示我是如何在一个 Windows 窗体应用程序中使用 C++/CLI 让 Excel 工作的
引言和免责声明
几年前,我使用 Microsoft 的 Visual Studio (VS) 2003 为本网站撰写了一篇文章,题为“如何使用托管 C++ 自动化 Excel”。
自那时起,微软发布了 VS 2005 和 VS 2008。VS 2003 与 VS2005/VS2008 之间存在一些显著差异。首先,微软现在将 VS2003 中的“托管”代码称为 VS2005 和 VS2008 中的“C++/CLI”代码。此外,托管代码和 C++/CLI 代码之间还存在一些显著(至少对我而言)的语法更改。其中一个主要变化是星号符号 (*) 不再在 C++/CLI 代码中使用。取而代之的是使用插入符号 (^) 或“hat”来表示“跟踪句柄”,它在很多方面都像一个指针。我不会尝试深入探讨它们的工作原理(这不是本文的目的,而且,我也不够聪明,无法向您清楚地解释)。其他语法更改包括隐式装箱和直接索引的能力。这两个更改消除了许多笨重的代码。
VS 2008 附带 Visual Studio Tools for Office。我尚未仔细研究此功能,但它似乎针对 .NET Framework 3.5。我支持的所有机器都使用 .NET Framework 2.0,因此我仍然坚持我从早期版本的 VS 迁移过来的方式。有关自动化 Excel 的大量信息可以在以下网址找到:msdn - Excel 任务
我建议您研究该网站。您在那里找不到很多 (如果根本没有的话) C++ 示例,但是如果您对 C# 或 VB 有些模糊的了解,您也许可以将 C# 或 VB 代码解释为 C++。
我将尝试遵循我之前文章中使用的相同结构,以演示如何自动化 Excel。主要区别在于此处使用的代码将符合 C++/CLI 标准,并将运行于 VS2008(可能也包括 VS2005)。在我目前的工作中,我负责开发和维护 17 个 Windows 窗体应用程序,除少数例外,所有这些应用程序都用纯 C++/CLI 编写。我的观点是,我可以在 C++/CLI 中完成大部分在原生代码中可以完成的事情。例外情况是我偶尔会使用一些原生库函数,但我尚未发现完全等效的 C++/CLI 函数。
作为最后的免责声明,我将重复我在上一篇文章中关于我的代码的说法。它有效。它可能不是最有效、最优雅,甚至不是最合乎逻辑的。当我将项目从 VS2003 转换到 VS2005,然后再转换到 VS2008 时,我没有花时间进行大量的挖掘和研究。但我得出的代码适用于 VS 2008 C++/CLI。对于所有可能认为我的代码丑陋低效的“纯粹主义者”,我提前表示歉意,我很乐意接受任何改进建议,只要您已经测试过它们并且它们有效!
项目概述
本文的目的是向您展示我是如何在 Windows 窗体应用程序中使用 MC++ 使 Excel 工作的,因此我不会尝试使这个示例非常复杂。我将使用虚构的数据在虚构的环境中进行演示。我们将创建一个 .NET Windows 窗体应用程序,并在窗体上放置一个按钮,该按钮将导致 Excel 运行并显示一个包含三个工作簿的工作簿。我将向您展示一种删除和添加工作表到工作簿的方法,以及一种创建条形图和折线图并将其及其支持数据放置到工作表上的方法。如果您使用 Office 2003,最终产品将如下图所示:
如果您使用 Office 2007,它将是这样的(我将在所有剩余示例中使用 Office2007)
项目设置
- 我正在使用 Visual Studio 2008 并针对 .NET Framework 2.0。在 Visual Studio 中启动一个新项目。我选择的项目类型是 Visual C++ CLR,模板是 Windows 窗体应用程序。
- 您需要为 Excel 添加一个 Office 主要互操作程序集 (PIA) 到您的引用中。您可以通过调出项目属性窗口并单击“添加新引用”按钮找到它。在“.Net”选项卡上,向下滚动到“Microsoft.Office.Interop.Excel”。我的机器上有两个程序集,11.0.0.0 和 12.0.0.0。我使用 11.0.0.0,它与 Office 2003 和 Office 2007 兼容。我没有尝试过 12.0.0.0,但是 11.0.0.0 对我来说是有效的,这对我来说已经足够了。所以选择您想要尝试的那个并单击“确定”。
- 在属性窗口打开时,在左侧窗口中选择“配置属性”,然后选择“安全 MSIL 公共语言运行时支持 (/clr:safe)”作为“公共语言运行时支持”选项。
- 将以下行添加到您的头文件 (Form1.h)
using namespace Microsoft::Office::Interop::Excel;
- 我还在头文件中添加了一行
#define Excel Microsoft::Office::Interop::Excel
这避免了在我引用 Excel 方法或属性时输入“”Microsoft::Office::Interop::Excel”。
- 为了避免编译器混淆 System 应用程序和 Excel 应用程序,您还需要将 *Automate_Excel.cpp* 中 main 方法中的行从
// Create the main window and run it Application::Run(gcnew Form1()); return 0;
改为
System::Windows::Forms::Application::EnableVisualStyles(); System::Windows::Forms::Application::SetCompatibleTextRenderingDefault(false); // Create the main window and run it System::Windows::Forms::Application::Run(gcnew Form1()); return 0;
- 在 Form1 上放置一个按钮。我将其命名为 `butExcel`,并将 Text 属性设置为“运行 Excel”。
- 双击按钮以在 *Form1.h* 文件中创建事件处理程序
private: System::Void butExcel_Click(System::Object^ sender, System::EventArgs^ e) { }
如果您此时编译并运行,您将得到
在下一节中,我们将创建一个运行 Excel 的方法,并从 `butExcel_Click` 事件处理程序中调用此方法。
让 Excel 运行的代码
- 向您的项目添加一个方法,该方法将创建并运行一个 Excel 应用程序。
void Form1::RunExcel()
{
//1. Create a new Excel application with 3-sheet Workbook
Excel::Application^ exApp = gcnew Excel::ApplicationClass();
//2. Add a workbook (comes with three Worksheets)
Workbook^ exWb = exApp->Workbooks->Add(Type::Missing);
.
.
.
// Show the Workbook
exApp->Visible = true;
}
注意:如果您没有在上面的步骤 5 中将我告诉您的 `#define` 行添加到您的头文件中,您将不得不输入“`Microsoft::Office::Interop::Excel::Application`”而不是“`Excel::Application`”。
- 在 `butExcel` 事件处理程序中调用此方法
private: System::Void butExcel_Click(System::Object^ sender, System::EventArgs^ e) {
RunExcel();
}
- 现在编译并运行。点击“运行 Excel”按钮应该会启动 Excel,并打开一个包含三个空工作表的 Excel 工作簿。
注意:使用 Excel 是我遇到广泛使用 `Type::Missing` 参数的唯一地方。它会导致被调用的方法使用默认参数值。您可能可以通过使用对象浏览器和/或访问我在此文章开头提到的 Microsoft 网站来了解更多信息。
删除、重命名、选择和添加工作表
- 假设您的工作簿中只需要两个工作表。您可以通过引用其在 Sheets 集合中的顺序来删除工作表。工作表编号从一 (1) 开始,而不是零 (0)。此行将删除第二个(Sheet2)工作表
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[2])->Delete();
- 尽管我在此示例中没有这样做,但您可以使用以下代码行添加一个或多个工作表,该代码行将向工作簿添加两个 (2) 个工作表。
exWb->Worksheets->Add(Type::Missing,Type::Missing,2,Type::Missing);
- 如果您有多个工作表,并且想要处理其中一个特定的工作表,您需要将您想要处理的工作表设置为活动工作表。您可以使用此代码行将第二个工作表设置为活动工作表。
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[2])->Select(Type::Missing);
- 您可以为活动工作表的跟踪句柄创建一个变量引用,以便轻松地重命名它并将其传递给将创建图表的方法。活动工作表是创建工作簿后的第一个工作表(我在下面所示的 `RunExcel()` 方法中执行此操作)。
Worksheet^ exWs = safe_cast<Worksheet^>(exApp->ActiveSheet);
- 要重命名活动工作表,请执行以下操作:
exWs->Name = "Charts";
控制方法
我使用 `RunExcel()` 方法来设置和控制 Excel 中发生的一切。这是该方法的全部内容
void Form1::RunExcel() { //1. Create a new Excel application Excel::Application^ exApp = gcnew Excel::ApplicationClass(); //2. Add a workbook (comes with three Worksheets) Workbook^ exWb = exApp->Workbooks->Add(Type::Missing); //3. Delete the last two worksheets safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[3])->Delete(); safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[2])->Delete(); //4. Create a variable for the active Worksheet's tracking handle // (first Worksheet is the default active one) Worksheet^ exWs = safe_cast<Worksheet^>(exApp->ActiveSheet); //5. Rename the active worksheet exWs->Name = "Charts"; //6. Load the data LoadData(); //7. Make the bar chart MakeBarChart(exWs, 2, 1); //8. Make a line chart MakeLineChart(exWs, 2, 8); // Show the Workbook exApp->Visible = true; }
以下是上述步骤中发生的事情
- 此步骤创建应用程序。如果您没有为 Excel 使用定义语句,则必须使用完整路径 (`Microsoft::Office::Interop::Excel`) 来引用 Excel
- 在步骤 1 中创建的应用程序是空的;其中没有工作簿。此步骤添加了一个包含三个工作表的工作簿(自动添加三个)。
- 由于我只打算使用一张表,所以我删除了最后两张。先删除最后一张,这样效果更好。
- 我将把工作表的引用(跟踪句柄)传递给构建图表的方法,所以这里我创建了一个对活动工作表的引用。当您将工作簿添加到应用程序时,第一个工作表 Sheet1 是默认的活动工作表。您可以使用以下方式激活另一个工作表:
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets->Item[3])->Select(Type::Missing);
其中数字 3 是集合中的第三个工作表。
- 此行重命名活动工作表。
- 调用加载数据的方法。您需要从某个地方获取数据。在我的实际应用程序中,我从文件中读取数据并将其存储在全局 SortedList 中,直到我处理完它并准备好将其放到我的工作表中。您可以将数据存储在 SortedList、Hashtable、Array 或其他数据结构中,直到您需要它,或者您可以在将数据读入应用程序时将其放到工作表中,这取决于哪种方式最适合您。您还可以使用 SQL 命令从数据库服务器获取数据。对于此示例,我使用 `LoadData()` 将一些虚假数据放入三个全局 SortedList 中。
- 这是一个调用构建条形图的方法。我向它传递一个对工作表的引用,我希望图表及其数据放置在该工作表上,我还传递了我希望开始放置数据的行号和列号。
- 这调用了构建折线图的方法。传递的信息与步骤 7 相同。
- 所有工作完成后,您必须使应用程序可见。
加载数据
由于本文涉及 Excel,我将在一个名为 `LoadData()` 的方法中伪造数据。我将端口名称和收到的材料吨位放入一个 `SortedList` 中,我将用它来生成条形图。我还构建了两个 `SortedList`,用于折线图,一个用于预计吨位,一个用于实际吨位。不管怎样,这是代码
void Form1::LoadData()
{
slTonsRcvd = gcnew SortedList(); //Global, declared in Form1.h
slByDayNYProjected = gcnew SortedList(); //Global, declared in Form1.h
slByDayNYActual = gcnew SortedList(); //Global, declared in Form1.h
slTonsRcvd->Add("New York", 46.826);
slTonsRcvd->Add("New Jersey", 21.865);
slTonsRcvd->Add("Boston", 4.8);
slTonsRcvd->Add("Los Angles", 30.87);
slTonsRcvd->Add("Portland", 16.4876);
slByDayNYProjected->Add(1, 2.0);
slByDayNYProjected->Add(2, 11.5);
slByDayNYProjected->Add(3, 7.5);
slByDayNYProjected->Add(4, 5);
slByDayNYProjected->Add(5, 10);
slByDayNYProjected->Add(6, 6.5);
slByDayNYProjected->Add(7, .5);
slByDayNYActual->Add(1, 2.3);
slByDayNYActual->Add(2, 12.345);
slByDayNYActual->Add(3, 8.331);
slByDayNYActual->Add(4, 5.702);
slByDayNYActual->Add(5, 10.45);
slByDayNYActual->Add(6, 6.718);
slByDayNYActual->Add(7, .98);
}
制作条形图
这就是我想要生成的条形图,大小合适,数据来源,以及在工作表上的位置。该图表显示了运往各个港口的虚构货物吨位。
我希望数据在工作表的前两列中,并且希望图表紧邻数据。我希望“吨位”列格式化为小数点后两位,但在图表上显示为整数。我希望图表有标题,并且 X 轴和 Y 轴都有标题。
这是我用来生成该图表的方法,解释在代码后面
void Form1::MakeBarChart(Worksheet ^ws, int row, int col)
{
int xPos = (col+2)*48; //Col width 48 points. Chart starts in 3rd col
int yPos = row*9; //Row height = 9, Chart starts in 2nd row
double tons = 0;
String^ port;
//1. Format a Worksheet column to 2 decimal places for chart data
ws->Range["B1", Type::Missing]->EntireColumn->NumberFormat = "#,##0.00";
//2. Set all Worksheet column widths to 12 to fit column titles and data
safe_cast<Range^>(ws->Columns)->ColumnWidth = 12;
//3. Extract Tons Received data from the SortedList and place on the chart
IDictionaryEnumerator^ ide = slTonsRcvd->GetEnumerator();
while (ide->MoveNext()) {
port = ide->Key->ToString();
tons = Convert::ToDouble(ide->Value);
ws->Cells[row, col] = port;
ws->Cells[row, col+1] = tons;
row++;
}
//4. Create a ChartObjects Collection for the Worksheet
ChartObjects^ chObjs = safe_cast<ChartObjects^>(ws->ChartObjects(Type::Missing));
//5. Add a ChartObject to the collection at(x, y, width, height) in points
ChartObject^ chObj = chObjs->Add(xPos, yPos, 300, 300);
//6. Create a chart from the ChartObject
Chart^ ch = chObj->Chart;
//7. Create a Range object & set the data range.
Range^ rn = ws->Range["A2:B6", Type::Missing];
//8. Do the chart using ChartWizard
ch->ChartWizard(rn->CurrentRegion, //Source
Constants::xlColumn, //Gallery
Type::Missing, //Format
XlRowCol::xlColumns, //Plot by
1, //Category Labels
Type::Missing, //Series Labels
false, //Has Legend
"Weekly Tons Received by Port", //Title
"Port", //Category Title (X)
"Tons", //Value Title (Y)
Type::Missing); //Extra Title
//9. Format the x-axis of the Cargo graph
safe_cast<Axis^>(ch->Axes(XlAxisType::xlValue, XlAxisGroup::xlPrimary))-> \
TickLabels->NumberFormat = "#,##0";
}
我使用了一些变量,只是为了让代码更容易(对我而言)处理。所以,让我们一步一步地进行。
- 我希望将我的“吨”数据显示为两位小数。因此,在第一步中,我对整个列进行数字格式设置。如果您不想格式化整个列,可以指定一个范围。例如,要仅格式化第 1 到第 10 行,您可以用“B1:B10”替换“B1”。如果您不想显示任何小数位,可以使用“#,##0”作为您的格式字符串,就像我在步骤 9 中所做的那样。
- 我将整个工作表的列宽设置为 12。如果您想调整单个列的宽度,可以使用
safe_cast<Range^>(ws->Columns["B1", Type::Missing])->EntireColumn->ColumnWidth = 12;
- 在步骤 3 中,我只是遍历一个 SortedList,其中包含端口名称作为键,吨位作为值。我将每个键/值对放入工作表中相应的单元格中。
- 此步骤为工作表创建了一个图表对象集合,但没有对其进行任何操作。接下来会完成此操作。
- 在这里,我们向图表对象集合添加一个图表对象,并指定其位置和大小(以点为单位)。参数是整数:X 位置、Y 位置、宽度和高度。您必须调整这些数字以使图表准确地定位和调整大小到您想要的位置和方式。
- 我创建了一个图表引用变量,可以用于图表向导方法。
- 我发现使用 Range 引用变量在 Chart Wizard 方法中更容易处理。Range 应该只覆盖您的数据所在的单元格。在下一个图表示例中,我将在此范围中包含系列标题。
- 这是图表向导方法。您可以在 Microsoft 网站上的此处找到图表向导方法的描述
- 第一个参数 Source 是图表数据在工作表中的位置。
- Gallery 是一个整数,指定您要绘制的图表类型。从逻辑上讲,它应该是一个 XlChartType 的枚举,但我的对象浏览器没有显示普通条形图的图表类型。所以,我四处寻找,发现条形图的整数值为 3,并且有一个 Excel 常量 `xlColumn`,其值为 3,所以我将其用作值,只是为了提醒我这是一个柱状条形图。您将在折线图示例中看到确实存在一个 `xlLine` 的图表类型。
- 我不确定 Format 参数是如何工作的,微软的解释对我来说不是很清楚。既然我可以通过其默认值在图表上得到我想要的结果,我就使用它。
- 我对 PlotBy 的理解是,它告诉 Excel 您的数据在工作表上的排列方式,是按列排列(像我这样)还是按行排列。XlRowCol 是一个枚举,可以是 xlRow 或 xlColumn。
- Categorylabels 告诉 Excel 在您指定的范围内查找 X 轴标签的位置。在这里,我告诉它在我在源参数中指定的范围的第一列中查找。
- 系列标签处理图表图例标签,我将在折线图示例中展示一个示例。
- “Has Legend”告诉 Excel 您是否希望显示图例。
- 接下来的三个参数告诉 Excel 您想要什么标题。Title 是图表标题,Category 是 X 轴标题,Value 是 Y 轴标题。
- 我不需要最后一个“额外标题”参数,我也没尝试过使用它。
- 我不想在图表的 Y 轴值上显示小数位。因此,经过大量尝试后,我得出了一行代码,可以将这些值格式化为整数。
如果您此时运行应用程序并单击“运行 Excel”按钮,您应该会得到以下结果(使用 Office 2007,Office 2003 应该会给出类似的结果)
制作折线图
折线图旨在比较在七天时间内预计抵达港口的吨位与实际抵达港口的吨位。数据列有标题,底部有一个图例,用于标识哪条线是哪条线。我将修改线的颜色和粗细,并重新定位图例。
这是生成图表的代码
void Form1::MakeLineChart(Worksheet ^ws, int row, int col)
{
int xPos = (col+5)*48; //Col width 48 points. Chart starts in 3rd col
int yPos = row*9; //Row height = 9, Chart starts in 2nd row
double tonsA = 0; //Actual tons
double tonsP = 0; //Projected tons
String^ day; //Day being plotted
String^ title = "Tons Received at NY port by day";
//1. Format two Worksheet columns to two decimal places for chart data
ws->Range["I1:J1", Type::Missing]->EntireColumn->NumberFormat = "#,##0.00";
//2. Reset the three Worksheet data column widths to better fit data
ws->Range["H1", Type::Missing]->EntireColumn->ColumnWidth = 5;
ws->Range["I1:J1", Type::Missing]->EntireColumn->ColumnWidth = 9;
//3. Put Column titles on the chart – two are Legend titles
ws->Cells[row, col] = "Day";
ws->Cells[row, col+1] = "Projected";
ws->Cells[row, col+2] = "Actual";
//4. Extract the data from two SortedLists and put it on the chart
IDictionaryEnumerator^ ide = slByDayNYProjected->GetEnumerator();
while (ide->MoveNext()) {
//Day and projected tons form one SortedList
day = ide->Key->ToString();
tonsP = Convert::ToDouble(ide->Value);
ws->Cells[row+1, col] = day;
ws->Cells[row+1, col+1] = tonsP;
//Use key to get actual tons form the other SortedList
tonsA = Convert::ToDouble(slByDayNYActual[ide->Key]);
ws->Cells[row+1, col+2] = tonsA;
row++;
}
//5. Create a ChartObject Collection for the Worksheet
ChartObjects^ chObjs = safe_cast<ChartObjects^>(ws->ChartObjects(Type::Missing));
//6. Add the ChartObject to the collection at(x, y, width, height) in points
// Width = 350 to prevent title from wrapping
ChartObject^ chObj = chObjs->Add(xPos, yPos, 350, 300);
//7. Create a chart from the ChartObject
Chart^ ch = chObj->Chart;
//8. Create a Range object & set the data range.
Range^ rn = ws->Range["I2:J9", Type::Missing];
//9. Do the chart
ch->ChartWizard(rn->CurrentRegion, //Source
XlChartType::xlLine, //Gallery
Type::Missing, //Format
XlRowCol::xlColumns, //Plot by
1, //Category Labels
1, //Series Labels
true, //Has Legend
title, //Title
"Day", //Category Title
"Tons", //Value Title
Type::Missing); //Extra Title
//10. Tell it the chart type again - initially comes up as a "lineMarked" type
ch->ChartType = safe_cast<XlChartType>(XlChartType::xlLine);
//11. Position the Chart Legend from the side to the bottom
ch->Legend->Position = XlLegendPosition::xlLegendPositionBottom;
//12. Format the Y-axis numbers to integers
safe_cast<Axis^>(ch->Axes(XlAxisType::xlValue, \
XlAxisGroup::xlPrimary))->TickLabels->NumberFormat = "#,##0";
//13. Make the lines thick
safe_cast<Series^>(ch->SeriesCollection(1))->Border->Weight = \
XlBorderWeight::xlThick;
safe_cast<Series^>(ch->SeriesCollection(2))->Border->Weight = \
XlBorderWeight::xlThick;
//14. Change the line colors
safe_cast<Series^>(ch->SeriesCollection(1))->Border->ColorIndex = 3;
safe_cast<Series^>(ch->SeriesCollection(2))->Border->ColorIndex = 32;
}
如果编译并运行,您将得到以下 Excel 图表
我更改并添加了一些变量以适应数据并处理用于标题的长字符串。以下是分步解释
- 这里我将两列数字格式化为显示两位小数。
- 我缩小了三列数据列的列宽,以减少图表之间的距离(只是因为我想要这样做)。J
- 此图表有列标题和图例。吨位数据上方的第二列标题将作为图例标题。请参见步骤 8 和 9。
- 在这里,我遍历一个 SortedList,其中以日期作为键,以预测吨位作为值。我使用这个 `SortedList` 中的键从第二个 SortedList 中获取实际吨位值,该 SortedList 也以日期作为键。我将数据放入相应的工作表单元格中。再次强调,可能存在更高效或更巧妙的方法来完成此操作,但这种方法对我来说有效。
- 与条形图相同。
- 与条形图相同,只是 X 位置已更改,将图表向右移动,并且图表宽度已加宽,以防止图表标题换行。
- 与条形图相同。
- 请注意,范围包括列标题。这是因为我希望将它们用于图例。
- 图表向导
- Source 参数是包含标题行的数据源。
- 这次有一个 XlChartType 枚举可以用于 `Gallery` 参数。不幸的是,它没有产生它应该产生的线型。它生成的是带标记的线而不是普通线。因此,在步骤 10 中再次设置了 ChartType,这次它奏效了。在我看来,微软在这个领域需要做一些工作!
- Plot By 和 Category Labels 与条形图中的相同
- Series Labels 是我们告诉 Excel 使用数据列的第一行作为该特定列中数据对应的图例标题的位置。
- 因为我们需要一个图例,所以我们将 Has Legend 值设置为 true。
- 我用一个变量表示图表标题。其余参数与条形图中的相同。
- 这里是我再次设置图表类型的地方。我不知道为什么要这样做,也不知道为什么它会起作用。有人知道吗?
- 图例的默认位置在图表的右侧。我希望它在底部,这就是它移动的方式。可以使用对象浏览器找到 `XlLegendPosition` 枚举。
- 再次,我希望 Y 轴数字显示为整数。
- 此步骤调整线条粗细。`XlBorderWeight` 枚举也可以在对象浏览器中找到。
- 最后,我希望线条的颜色是纯红色和蓝色,所以我设置了颜色。为了获取要使用的值,我打开 Excel 并创建了一个带图例的图表(对象浏览器对此没有帮助)。然后启动宏录制器,双击图例以调出“格式化图例”对话框。我从图例的颜色表中选择了所需的颜色,关闭对话框并停止录制。然后我在宏编辑器中打开宏,并使用了其中显示的 VB .ColorIndex 值。我尝试使用 RGB 值,但遇到了运行时异常。
结论
一句忠告!当我使用 Office 2003 时,我发现有时关闭 Excel 后,*EXCEL.EXE* 仍然存在于内存中。下次我打开或启动 Excel 时,会有两个 *EXCEL.EXE* 实例在运行,Excel 的标题栏显示 Book2(或 Book3 或运行的进程数),而不是 Book1。这种情况一直持续到我使用 Windows 任务管理器杀死所有正在运行的 *EXCEL.EXE* 进程,方法是选择“进程”选项卡窗口中显示的每个 *EXCEL.EXE* 并单击“结束进程”按钮。到目前为止,我还没有在 Office 2007 中看到这种行为。
由于语法更改,从 VS2003 中的托管代码到 VS 2005 和 VS2008 中的 C++/CLI 的转换有点痛苦。但是,对我来说,最终的代码更清晰、更容易阅读、理解和维护。现在,如果微软能在所有提供 C# 和 VB 示例的地方也提供 C++/CLI 示例,生活会更轻松!