如何使用VS2008 C++/CLI 來自動化操作Excel



寫在前面:

        最近練習寫一個小程序的時候需要編程實現對Excel的操作,由於知識不足,能力有限,只好去百度尋求幫助,可惜沒搜到我現在的水平可以解決的方案。後來使用Microsoft Visual Studio 2010 文檔,終於搜到了這篇文章,一步步的教你實現Excel操作,順着它,很容易就解決掉了這個問題。想想自己學習看英文資料也有段時間了,於是決定嘗試翻譯一下這篇文章與大家分享,順便鍛鍊一下自己的閱讀能力。

原文網址:http://www.codeproject.com/Articles/28083/Use-Visual-Studio-2008-C-CLI-to-Automate-Excel

參考資料:ASP.NET導入、導出EXCEL中的一些問題彙總(結束EXCEL進程)

        閒話不提,書歸正傳.


項目概述


        這篇文章的目的主要是告訴你如何在一個Windows窗體應用程序中使用MC++來操作Excel,因此我不會讓這個例子變得很複雜。項目使用的數據都是虛構的。我們會建立一個.NET Windows窗體應用程序,然後放置一個Button控件去運行Excel並顯示一個具有3個工作表的工作薄。我會教你一個在工作薄中增加和刪除工作表的方法,然後告訴你一個建立並放置柱形圖和折線圖的方法,並說明如何設置它們的數據區域。下圖是此項目在Office 2003下的最終效果圖:



如果你使用Office 2007,它看起來會是這樣(在接下來的例子中我都會使用Office2007):



創建項目


1.我使用VS2008和.NET Framework 2.0。在VS中新建一個項目。我選擇Visual C++ CLR作爲項目類型,使用Windows 窗體應用程序作爲模板。


2.爲了操作Excel,你需要在你的引用中添加一個Office主互操作程序集(PIA).打開你的項目屬性頁,然後點擊"添加新引用"按鈕,你就可以找到這個程序集。在.Net選項卡中,向下滾支直到找到"Microsoft.OfficeInterop.Excel".在我的電腦上有兩個不同版本的程序集,11.0.0.0 和 12.0.0.0,我使用可以支持Office2003和Office2007的11.0.0.0版本。我沒有試過12.0.0.0,對我來說,11.0.0.0已經足夠好了。現在就選擇一個你希望嘗試的,然後點擊"確定"吧。


3.在你打開項目屬性頁時,在左邊的窗口中選中"配置屬性",然後在"公共語言運行時支持"選項中選擇"安全 MSIL 公共語言運行時支持(/clr:safe)"。


4.在你的頭文件(Form1.h)中添加如下代碼:

using namespace Microsoft::Office::Interop::Excel;


5.我在頭文件中也會加上下面一行代碼:

#define Excel   Microsoft::Office::Interop::Excel


這樣一來,在引用Excel方法或屬性時就不用輸入"Microsoft::Office::Interop::Excel"這麼一長串了。
6.爲避免編譯程序在系統應用程序和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;


7.在Form1中添加一個"Button"控件。我把它命名爲 butExcel ,把它的文本屬性設爲"Run Excel".
8.雙擊這個控件以在Form1.h中建立一個事件處理程序:

private: System::Void butExcel_Click(System::Object^ sender, System::EventArgs^ e) {
}

現在,如果你編譯並運行,你將會看到:


接下來我們將建立一個方法去運行Excel,並在 butExcel_Click 事件處理程序中調用這一方法。

運行Excel的代碼


1.在你的項目中添能夠建立並運行一個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”。

2.在butExcel的事件處理程序中調用這個方法:
private: System::Void butExcel_Click(System::Object^ sender, System::EventArgs^ e) {
    RunExcel();
}

3.現在編譯和運行。點擊Run Excel按鈕後,將會啓動Excel並打開一個擁有3個空工作表的工作薄。


刪除,重命名,選擇和添加一個工作表

1.假如你只需要兩個工作表,那麼你可以通過引用一個工作表所在的次序去刪除這個工作表。注意,工作表的編號是從1而不是0開始的。下面的代碼將會刪除第二個(Sheet2)工作表。
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[2])->Delete();
2.儘管我沒有在這個例子中這樣做,但是你可以參考下面的向工作薄中添加2個工作表的代碼去自由添加一個或多個工作表。
exWb->Worksheets->Add(Type::Missing,Type::Missing,2,Type::Missing);
3.如果你想在若干個工作表中使用特定的一個,你需要把這個工作表設爲活動工作表。你可以參考下面這行把第二個工作表設爲活動的代碼.
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets[2])->Select(Type::Missing);
4.你可以創建一個引用活動工作表的跟蹤句柄的變量,這樣你就能夠很容易的重命名工作表,並把它傳遞給創建圖表的方法。創建工作薄後,第一個工作表就是活動工作表。
Worksheet^  exWs = safe_cast<Worksheet^>(exApp->ActiveSheet);


5.重命名活動工作表:
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;
}


下面是每一步所做的事:
1.這一步創建了一個應用。
2.第一步所創建的應用是空的,裏面沒有工作薄。這一步創建了一個工作薄,裏面包含三個自動添加的工作表。
3.因爲我只計劃使用一個表格,因此我刪除了其它的兩個。首先刪除最後一個,這樣會比較好一些。
4.我會向創建圖表的方法中傳遞一個工作表的引用。因此這裏我創建了一個指向活動活動工作表的引用。當你在一個應用中添加一個工作薄時,第一個工作表,Sheet1,將會是默認的活動工作表。你可以使用下面的代碼來把另一個工作表設爲活動的:
safe_cast<Worksheet^>(exApp->ActiveWorkbook->Sheets->Item[3])->Select(Type::Missing);


這樣第三個工作表就會變爲活動的了。
5.這一行重命名了活動工作表。
6.調用加載數據的方法。
7.調用一個創建柱形圖的方法。我向它傳遞了3個參數,一個是指向圖表和數據所存放的工作表的引用,另外兩個分別是使用的數據起始位置的行和列的編號。
8.調用一個創建拆線圖的方法。相關信息同第7步。
9.所有工作做完後,把應用設爲可見的。

加載數據

        因爲這篇文章討論的是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.我想在顯示噸位數據時保留兩位小數,因此在第一步中,對於整列都使用了數值格式。如果你不想讓整列都格式化,你可以指定一個範圍,比如僅格式化1-10行,你可以用"B1:B10"來替換"B1".假如你不想顯示小數部分,你可以像我在第9步做的那樣,使用"#,##0"做爲格式化字符串.
2.我把整個工作表的列寬設置爲12.如果你想調整一列的寬度,你可以這樣做:

safe_cast<Range^>(ws->Columns["B1",Type::Missing])->EntireColumn->ColumnWidth = 12;


5.這裏我們添加了一個Chart對象,並指定了它的位置和大小。所有參數都是整型的:X座標,Y座標,寬和高。
8.這是一個Chart嚮導方法。你可以在這裏找到它的詳細描述。


如果你現在運行程序並點擊"Run Excel"按鈕,你將會看到下圖內容(使用Office2007,Office2003應該會有相似的結果):


製做一個折線圖


折線圖用來比較計劃運抵的噸位數和七天內實際運抵的噸位數。數據列都有標題,而且在底部有圖例來區分各條線。我將修改線條的顏色和厚度並且重新定義圖例。

下面是相關代碼:

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;
}


如果你編譯和運行,你將會得到下面的圖表:


結束語

小警告:當我使用Office2003時,我發現在我關閉Excel時,有時候EXCEL.EXE仍然活躍在內在中。當我下次打開Excel時,就會有兩個EXCEL.EXE實例運行而且Excel的標題會顯示Book2(或者Book3或者進程運行數量的數字),而不是Book1.除非我從任務管理器中強制強束所有的EXCEL.EXE進程,不然這種情況會一直繼續下去。到目前爲止,我還沒在Office2007中發現這種情況。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章