17

Click here to load reader

Programming Against Excel Interop With .NET Framework

Embed Size (px)

Citation preview

Page 1: Programming Against Excel Interop With .NET Framework

1

Programming against Excel Interop with .NET Framework by Zhao Chuxin

Excel Macro is using Excel Visual Basic for Applications (VBA), which indeed is a great tool for Excel automation. However when come to much larger amount of data, complicated data structure and frequent changes in requirements, a programming language with a poor IDE may not be your first choice.

Here we are going to discuss how to achieve automation with .NET Framework by programming to Excel API (Excel Interop). In this demo project I am going to use C#. Actually you can use any language support in .NET Framework such as Visual Basic.

Software Setup 1. Microsoft Office Excel 2010 (Excel) 2. Microsoft Visual Studio 2010 (VS)

Let’s CODE! In my demo I am going to use a bit GUI programming with WPF, so some basic understanding in events-driven programming will be nice but not necessary (thanks to code auto completion feature in VS). Please notice that I only have limited exception handling in this demo.

Create a New Project First I need to create a new C# WPF Application with .NET Framework 4.

Page 2: Programming Against Excel Interop With .NET Framework

2

Once the project is created, you will have a UI window which is the MainWindow. In the Solution Explorer you should have something like below.

Some simple editing in the UI so we can work on.

XAML ( ) is used to render UI. We will not change this code anymore.

Page 3: Programming Against Excel Interop With .NET Framework

3

Code-behind ( ) is used to handle events and run other business logic. I collapse the using section in below. We will add more code into this class.

Now we need Excel Interop into our reference,

Right click on , select “Add Reference…”, select .NET tab, now look through this list and select Micorosft.Office.Interop.Excel,

namespace myExcelTryout { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void selectFile_button_Click(object sender, RoutedEventArgs e) { } private void run_button_Click(object sender, RoutedEventArgs e) { } } }

<Window x:Class="myExcelTryout.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="500" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="3*"/> <RowDefinition Height="1*"/> </Grid.RowDefinitions> <TextBox Grid.Row="0" Margin="5" Name="output_textBox" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"/> <Grid Grid.Row="1"> <Button Content="Select Files" Height="23" HorizontalAlignment="Left" Margin="23,13,0,0" VerticalAlignment="Top" Width="75" Name="selectFile_button" Click="selectFile_button_Click" /> <Button Content="Run" Height="23" HorizontalAlignment="Left" Margin="124,13,0,0" VerticalAlignment="Top" Width="75" Name="run_button" Click="run_button_Click"/> </Grid> </Grid> </Window>

Page 4: Programming Against Excel Interop With .NET Framework

4

Then click Ok. You will see the new reference in your Solution Explorer:

For a good practice, I am going to add one more package called ExcelOps with one class with the name ExcelOp.

Before any more coding, we need to understand how this Excel Interop works.

Page 5: Programming Against Excel Interop With .NET Framework

5

Simple Code on UI Before programming to Excel, first we need some code for testing and show us what we get from our code, something like Console.WriteLine. Because we are working on a GUI project, which mean console will not work.

Below code will update the content of output_textBox.Test, and always scroll to the end of this TextBox.

The data from Excel are table in nature (we won’t deal with graph in Excel in this tutorial). It will be nice if we can present this data in out output_textBox.Test as well. Below code just prints a 2D-array to a table in our TextBox. This function takes two parameters. One is the string array we are going to print. Another one is an integer. I called it offset as it is the first index value of the array (C# allows an arbitrary starting index. Some people may prefer 1 to 0).

However the data we get in Excel is not in string array but an object array (notice that data from single cell range will be an object). So we need a code to help us convert an object array to a string array. This code is for testing only. We will see the reason later.

private void printTable(string[,] table,int offset) { printToOutupt_textBox("Table length: " + table.Length); int nRows = table.GetLength(0); int nCols = table.GetLength(1); printToOutupt_textBox("Table size: " + nRows + " " + nCols); string outString = "Table Content: \n"; for (int i = 0 + offset; i < nRows + offset; i++) { for (int j = 0 + offset; j < nCols + offset; j++) { if (!String.IsNullOrWhiteSpace(table[i, j])) { outString += table[i, j] + "\t\t"; } else { outString += "<Empty>" + "\t\t"; } } outString += "\n"; } printToOutupt_textBox(outString); }

public void printToOutupt_textBox(string str) { output_textBox.Text += str + "\n"; output_textBox.ScrollToEnd(); }

Page 6: Programming Against Excel Interop With .NET Framework

6

We also need few private attributes:

Also need some code in our events handlers: below code will show an Open File(s) Dialog

private ExcelOp excelOp = new ExcelOp();// carry out excel access from here private string[] filenames = null; // store selected files

private void printTable(object[,] table, int offset) { int nRows = table.GetLength(0); int nCols = table.GetLength(1); string [,] result = new string[nRows, nCols]; for (int i = 0 + offset; i < nRows + offset; i++) { for (int j = 0 + offset; j < nCols + offset; j++) { if (table[i, j] != null) { result[i - offset, j - offset] = table[i, j].ToString(); } else { result[i - offset, j - offset] = null; } } } printTable(result, 0); }

Page 7: Programming Against Excel Interop With .NET Framework

7

We will come back to private void run_button_Click(object sender, RoutedEventArgs e) later.

Operate on Excel Document First we need to create an Excel process which means after this creating part, there will be a actual Excel process running in your system memory. If you go to your Windows Task Manager, you can see your running Excel process just like you double-clicked on your little Excel icon.

private void selectFile_button_Click(object sender, RoutedEventArgs e) { // save selected files in string array filenames printToOutupt_textBox("Select files..."); Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); dlg.Multiselect = true; // Filter files by extension dlg.Filter = "Excel Worksheets|*.xls; *xlsx" + "|All Files|*.*"; // Show open file dialog box Nullable<bool> result = dlg.ShowDialog(); // Process open file dialog box results if (result == true) { // Open document if (filenames != null) { printToOutupt_textBox("Last selection will be cleared..."); } filenames = dlg.FileNames; foreach (string filename in filenames) { printToOutupt_textBox("Select file: " + filename); } printToOutupt_textBox("Number of files have been selected: " + filenames.Length); } else { printToOutupt_textBox("Action Canceled"); } }

Page 8: Programming Against Excel Interop With .NET Framework

8

This means you need have your Windows Office Excel installed on your computer. This also means you need do some serious work to manage your Excel process and program window events.

Once you created Excel process, you can use this process to open your Excel documents (Workbook), then select your Worksheet and range with your data.

Back to our ExcelOp.ExcelOp.cs, we need to add Excel Interop to our using:

Also some private attributes, properties and constructors:

Now we need some methods to perform those actions we need.

private Excel.Application excelApp = new Excel.Application(); private Excel.Workbook excelWorkbook = null; private Excel.Worksheet currentSheet = null; private Excel.Range currentRange = null; private string filename = null; public string Filename { get { return filename; } set { filename = value; } } public ExcelOp() { } public ExcelOp(string filename) { Filename = filename; }

using Excel = Microsoft.Office.Interop.Excel;

Page 9: Programming Against Excel Interop With .NET Framework

9

1. Open selected file 2. Select worksheet 3. Select range 4. Retrieve data 5. Update data 6. Save file 7. Close file

Open Selected File This method is very straightforward,

The alerts in Excel require user interactions which may interrupt the automation process, so normally I would disable it by

In the Workbooks.Open method, only the first parameter is required the rests in fact are optional. You can simply use

Select Worksheet In Excel Interop, few methods are provided to select a particular worksheet. In this example, I only implemented select the active worksheet. You may also implement select by index (starts from 1) and select by name.

Select Range Similar to worksheet, few methods are provided to select range. In my example, I have two functions to implement two different ways to select your range. One is through UsedRange

public void SelectActiveWorkSheet() { currentSheet = excelWorkbook.ActiveSheet; } //public void SelectWorkSheetAt(int sheetIndex) //public void SelectWorkSheetByName(string sheetName)

excelWorkbook = excelApp.Workbooks.Open(Filename);

excelApp.DisplayAlerts = false;

public void DoOpen() { //we don't want to see any alerts from Excel; excelApp.DisplayAlerts = false; excelWorkbook = excelApp.Workbooks.Open(Filename, 0, false, 5, "", "", false, Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false); }

Page 10: Programming Against Excel Interop With .NET Framework

10

property in Worksheet to get the UsedRange. It will return a minimum range which covers all the used range. The second one will work like press “Ctrl + Shift + End” in Excel from an object cell you indicated in parameter list.

Retrieve Data and Update Data Value2 property of Range will return the data of this range in an object array (index starts with 1) if multiple cells are covered by this range or in a single object if single cell is covered by this range. As Value2 is a property, update also can be done by assigning value to it.

To simplify this process in my example, I just return the property.

Save and Close Workbook Very straightforward:

public void SaveWorkBook() { excelWorkbook.Save(); } public void CloseWorkBook() { currentRange = null; currentSheet = null; excelWorkbook.Close(); }

public object[,] CurrentRangeValue2 { get { return currentRange.Value2; } set { currentRange.Value2 = value; } }

public void SelectUsedRange() { currentRange = currentSheet.UsedRange; } public void SelectLocalRangeFrom(string startCell) { // startCell need to be a range name, such as "A1" // just like Ctrl + Shift + End currentRange = currentSheet.get_Range(startCell); string downAddress = currentRange.get_Address( false, false, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing); currentRange = currentSheet.get_Range(startCell, downAddress); }

Page 11: Programming Against Excel Interop With .NET Framework

11

Try on Excel Files

Define Test Scope and Prepare Test Excel File We will open an Excel file, read and print its content, update the 3rd cell in the 1st row with current time, save and close this file.

In that file we are going to define the same content for all three worksheet in this document.

Page 12: Programming Against Excel Interop With .NET Framework

12

Adding Code to run_button_Click

First we need to ensure there is a excelOp and it is not null.

Then we need to check our file (filenames) list is not null.

In the else clause, add below code to define the action on selected files.

if( excelOp == null) { MessageBox.Show("Opp null"); //excelOp = new ExcelOp(); } if (filenames == null) { printToOutupt_textBox("No file has been selected."); } else { // more code here }

Page 13: Programming Against Excel Interop With .NET Framework

13

Now we ready to go.

After run the program, we will see the result for each worksheet:

for (int i = 0; i < filenames.Length; i++) { printToOutupt_textBox("Open file: " + filenames[i]); excelOp.DoOpen(filenames[i]); for (int j = 1; j <= excelOp.SheetCount; j++) { printToOutupt_textBox("Select sheet: " + j); excelOp.SelectWorkSheetAt(j); excelOp.SelectUsedRange(); object[,] data = excelOp.CurrentRangeValue2; printTable(data, 1); data[1, 3] = System.DateTime.Now.ToOADate(); excelOp.CurrentRangeValue2 = data; } printToOutupt_textBox("Save file: " + filenames[i]); excelOp.SaveWorkBook(); printToOutupt_textBox("Close file: " + filenames[i]); excelOp.CloseWorkBook(); }

Page 14: Programming Against Excel Interop With .NET Framework

14

From above result and checking the Excel for update, we can safely say that our code is working. But we also can see some problem. For example the datetime value in Excel in fact is stored as number. To be more specific, it is an OADate. Data conversion is required to handle such data.

Let’s modify the test data a bit.

Page 15: Programming Against Excel Interop With .NET Framework

15

In worksheet 1, we delete last three rows by “Clear All”. In worksheet 2, we delete last three rows with delete button on keyboard. In worksheet 3, we delete last three rows by “delete rows”.

Page 16: Programming Against Excel Interop With .NET Framework

16

They look similar with the first time we run out program to this Excel files in terms of structure. However the data is different:

Page 17: Programming Against Excel Interop With .NET Framework

17

From this result we know that Excel handle these three way of deleting differently. Using the “Delete” button is not enough to make Excel think this cell is empty. Even through the cell value is null, but Worksheet.UsedRange property will still capture those cells as shown in the output screenshot of sheet 2.