En las últimas semanas he estado trabajando con C# y una de las cosas que he tenido que hacer fue manejar ficheros Excel. Hay dos formas para trabajar con ellos, usando el interop de Microsoft o tratando el fichero como una base de datos con ADO.NET, el problema es que con lo último no podemos borrar cosas, con lo que nos encontramos con un problema un tanto estrambótico. Así que para hacer lo que yo quería hacer tuve a mi pesar que usar interop.
Lo primero que hay que hacer es añadir la referencia a la librería COM “Microsoft Excel 11.0 Object Library”, que corresponde más o menos al Office 2003. Una vez hemos configurado nuestro proyecto para poder invocar la aplicación Excel desde nuestro programa el código es el siguiente:
// A library to handle excel files in a simple way.
// Copyright (C) 2009 Gorka Suárez García
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see .
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Office.Interop.Excel;
namespace Excel {
/// <summary>
/// This class is used to handle an excel file to write and read from it.
/// Author: Gorka Suárez García
/// </summary>
public class ExcelHandler {
/// <summary>
/// The excel application instance.
/// </summary>
private ApplicationClass app;
/// <summary>
/// The excel book.
/// </summary>
private Workbook book;
/// <summary>
/// The path of the excel file.
/// </summary>
private string path;
/// <summary>
/// Constructs a new ExcelHandler object.
/// </summary>
public ExcelHandler() {
this.app = null;
this.book = null;
this.path = null;
}
/// <summary>
/// Destroys the ExcelHandler object.
/// </summary>
~ExcelHandler() {
if (this.app != null) {
this.app.Quit();
}
}
/// <summary>
/// Opens an excel file.
/// </summary>
/// <param name="path">The file to open.</param>
public void Open(string path) {
this.path = path;
this.app = new ApplicationClass();
this.app.Visible = false;
this.app.ScreenUpdating = false;
this.app.DisplayAlerts = false;
this.book = this.app.Workbooks.Open(this.path, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value);
if (this.book == null)
throw new Exception("Can't open the excel book file.");
}
/// <summary>
/// Writes a value in a cell.
/// </summary>
/// <param name="sheet">The sheet to write.</param>
/// <param name="cell">The cell to write.</param>
/// <param name="value">The value to write.</param>
public void Write(string sheet, string cell, string value) {
Worksheet wsheet = this.getSheet(sheet);
Range range = wsheet.get_Range(cell, cell);
range.Value2 = value;
}
/// <summary>
/// Reads a value from a cell.
/// </summary>
/// <param name="sheet">The sheet to read.</param>
/// <param name="cell">The cell to read.</param>
/// <returns>The value from the cell.</returns>
public string Read(string sheet, string cell) {
Worksheet wsheet = this.getSheet(sheet);
Range range = wsheet.get_Range(cell, cell);
if (range.Value2 != null)
return range.Value2.ToString();
else
return "";
}
/// <summary>
/// Clears the content of the excel book.
/// </summary>
public void Clear() {
Worksheet sheet = null;
for (int i = 1; i <= this.book.Worksheets.Count; i++) {
sheet = (Worksheet)this.book.Worksheets[i];
sheet.Cells.Clear();
}
}
/// <summary>
/// Closes the excel file.
/// </summary>
public void Close() {
this.book.SaveAs(this.path, XlFileFormat.xlWorkbookNormal, Missing.Value, Missing.Value,
false, false, XlSaveAsAccessMode.xlShared, false, false, Missing.Value,
Missing.Value, Missing.Value);
this.book.Close(true, Missing.Value, Missing.Value);
this.app.Quit();
this.app = null;
this.book = null;
this.path = null;
}
/// <summary>
/// Gets all the names of the sheets inside the excel book.
/// </summary>
/// <returns>A list of the sheets names.</returns>
public string[] GetSheetsNames() {
List names = new List();
Worksheet sheet = null;
for (int i = 1; i <= this.book.Worksheets.Count; i++) {
sheet = (Worksheet)this.book.Worksheets[i];
names.Add(sheet.Name);
}
return names.ToArray();
}
/// <summary>
/// Gets a sheet we're looking for.
/// </summary>
/// <param name="name">The name of the sheet.</param>
/// <returns>The sheet we're looking for.</returns>
protected Worksheet getSheet(string name) {
int index = this.getSheetIndex(name);
if (index == 0)
throw new Exception("Invalid sheet name.");
Worksheet sheet = (Worksheet)this.book.Worksheets[index];
return sheet;
}
/// <summary>
/// Gets the index of a sheet we're looking for.
/// </summary>
/// <param name="name">The name of the sheet.</param>
/// <returns>The index of the sheet we're looking for.</returns>
protected int getSheetIndex(string name) {
Worksheet sheet = null;
for (int i = 1; i <= this.book.Worksheets.Count; i++) {
sheet = (Worksheet)this.book.Worksheets[i];
if (sheet.Name == name) return i;
}
return 0;
}
}
}
El objeto de app de tipo ApplicationClass es una instancia del programa Excel, por lo que si vamos al administrador de tareas mientras se ejecuta nuestra aplicación, veremos que “Excel.exe” está ejecutándose. Luego el objeto de tipo Workbook es una instancia del libro contenido en un fichero de tipo excel. Y por último path es simplemente la ruta del fichero, que la guardaremos para salvar los cambios al cerrar el fichero. Así que teniendo esto en cuenta para abrir un documento excel lo haríamos así:
public void Open(string path) {
// Asignamos la ruta del fichero.
this.path = path;
// Instanciamos la aplicación excel.
this.app = new ApplicationClass();
// Indicamos que no será visible,
this.app.Visible = false;
// que no se va a actualizar la pantalla
this.app.ScreenUpdating = false;
// y que no va a mostrar los mensajes de alerta.
this.app.DisplayAlerts = false;
// Abrimos un libro excel pasándole la ruta y el
// resto de valores los dejamos sin asignar.
this.book = this.app.Workbooks.Open(this.path,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value, Missing.Value,
Missing.Value, Missing.Value);
// Si el libro no se ha abierto lanzamos una excepción.
if (this.book == null)
throw new Exception("Can't open the excel book file.");
}
Para poder escribir algo en el fichero, tendremos que indicar la hoja y la celda como cadenas. El valor también será una cadena, sin embargo a efectos internos del fichero, será tratado como un número, un texto o una fórmula si encaja con el formato que excel considera apropiado para esa clase de datos. Por ello podemos asignar a una celda una fórmula siempre que cumpla el formato, para que excel lo detecte.
public void Write(string sheet, string cell,
string value)
{
// Obtenemos la hoja del libro.
Worksheet wsheet = this.getSheet(sheet);
// Obtenemos la celda.
Range range = wsheet.get_Range(cell, cell);
// Asignamos el valor a la celda.
range.Value2 = value;
}
Para obtener una celda tenemos que utilizar una función que nos devuelve un conjunto de celdas determinado por un rango de direcciones. Dentro de esta clase, la propiedad Value2 contiene o bien un array de celdas o una celda simplemente si solo hemos pedido un “conjunto” de una celda. De modo similar para leer tendríamos lo siguiente.
public string Read(string sheet, string cell)
{
// Obtenemos la hoja del libro.
Worksheet wsheet = this.getSheet(sheet);
// Obtenemos la celda.
Range range = wsheet.get_Range(cell, cell);
// Devolvemos el valor de la celda.
if (range.Value2 != null)
return range.Value2.ToString();
else
return "";
}
Hay que tener en cuenta que si una celda no está inicializada o no tiene valor, nos devolvería un null, con lo que en este caso devolvemos una cadena vacía como representación de eso. Ahora que sabemos leer y escribir, lo suyo es saber salvar y cerrar el fichero.
public void Close() {
// Salvamos el contenido del libro.
this.book.SaveAs(this.path,
XlFileFormat.xlWorkbookNormal, Missing.Value,
Missing.Value, false, false,
XlSaveAsAccessMode.xlShared, false, false,
Missing.Value, Missing.Value, Missing.Value);
// Cerramos el libro.
this.book.Close(true, Missing.Value, Missing.Value);
// Y salimos de la aplicación excel.
this.app.Quit();
this.app = null;
this.book = null;
this.path = null;
}
Otras operaciones varias en el código de la clase son:
- Clear: Que borra todas las hojas del fichero.
- GetSheetsNames: Que devuelve un array con los nombres de las hojas del fichero.
- getSheet: Que busca una hoja en base a su nombre y la devuelve.
- getSheetIndex: Que devuelve en qué índice está una hoja en base a su nombre.
Para hacer esas operaciones hay que tener en cuenta cosas como que en Worksheet tenemos la propiedad Cells, que tiene el método Clear() para borrar el contenido de una hoja. Que en Workbook tenemos la propiedad Worksheets que es un array con todas las hojas del libro excel, y como tal array tiene la propiedad Count para saber el número de hojas del libro. Sin embargo dicho array va desde 1 hasta Count incluido. Y por último dentro de Worksheet tenemos la propiedad Name, con el nombre de la hoja.
Y esto es lo más básico para trabajar con fichero Excel usando interop desde .NET, se pueden hacer más cosas pero aun así hay algunos problemillas de esta API, como que no siempre se cierra bien la aplicación o que a veces tarda un buen rato en ser cerrada, o que si salimos de forma abrupta de nuestra aplicación, se puede quedar el programa Excel abierto en el limbo. Pero peor sería hacerlo todo desde cero, eso seguro.