Showing posts with label Excel. Show all posts
Showing posts with label Excel. Show all posts

Friday, 1 October 2010

Working with Microsoft Excel (.xlsx) files in Delphi 2010

Due to the improvement of the new format of Microsoft office 2007 files (.docx, .xlsx based on xml) the way we use to access this documents has changed. Instead of using OLE we can use the power of Delphi and the type library Borland (TLB Files).
In this article we'll talk about how to create the TLB files and the code we need to access the .Excel (.xlsx file) of the example I've created.
First of all, we need to import the components via -> Component -> Import a Type Library. Once we are there, we need to select the "Microsoft Excel 12.0 Object Library" and go on with the import. Mark the "Generate component wrappers" check-box and finally add the component to the working unit. This will generate the Excel_TLB.pas.
Once we have integrated this file into our project, we need to go on with importing the second component: Component -> Import a Type Library and then install the "Microsoft Office 12.0 Object Library". This will generate the Office_TLB.pas (this unit is necessary because it's used by Excel_TLB.pas).

1 - Knowing the number and name of the sheets of the Workbook.
The following code will open a excel file and will load all the sheets into a combo box. Even though it represents a small portion of code, the library is quite complicated to use.

procedure TForm1.LoadCombo();
var
    XLSXReader: Excel_TLB.TExcelApplication;
    newWorkbook: Excel_TLB._Workbook;
    objXLS: Excel_TLB.TExcelWorkbook;
    objSheet: Excel_TLB.TExcelWorksheet;
    i: Integer;
begin
    newWorkbook := XLSXReader.Workbooks.Add('C:\book1.xlsx', GetUserDefaultLCID);
    objXLS := Excel_TLB.TExcelWorkbook.Create(nil);
    objXLS.ConnectTo(newWorkbook);
    objSheet := Excel_TLB.TExcelWorksheet.Create(nil);
    for i := 1 to objXLS.Worksheets.count do
    begin
        objSheet.ConnectTo(objXLS.Worksheets[i] as _Worksheet);
        ComboBox1.Items.Add(objSheet.Name);
    end;
    newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID);
    FreeAndNil(objXLS);
    FreeAndNil(objSheet);
end;

2 - Accessing to Excel fields.
The following example will get the objSheet and will get all the data from the sheet and will dump into a dataset to display then the result into a grid.
procedure TForm1.Open(Sender: TObject);
var
    XLSXReader: Excel_TLB.TExcelApplication;
    newWorkbook: Excel_TLB._Workbook;
    objXLS: Excel_TLB.TExcelWorkbook;
    objSheet: Excel_TLB.TExcelWorksheet;
    objWrapper: TExcelWrapper;
begin
    newWorkbook := XLSXReader.Workbooks.Add('C:\book1.xlsx', GetUserDefaultLCID);
    objXLS := Excel_TLB.TExcelWorkbook.Create(nil);
    objXLS.ConnectTo(newWorkbook);
    objSheet := Excel_TLB.TExcelWorksheet.Create(nil);
    objSheet.ConnectTo(objXLS.Worksheets['sheet1'] as _Worksheet);
    objWrapper := TExcelWrapper.Create(objSheet);
    if cdsExcel.Active then
        cdsExcel.Close;
    objWrapper.FillDataSet(cdsExcel);
    cdsExcel.Open;
    cdsExcel.First;
    newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID);
end;

Then the code for getting the data from the sheet:

procedure TExcelWrapper.FillDataSet(var cdsExcel: TClientDataSet);
var
    count: Integer;
    i: Integer;
    bFinished, correct: boolean;
    Value: String;
begin
    if Assigned(FSheet) then
    begin
        try
            cdsExcel.DisableControls;
            count := 0;
            i := 2;
            LastModule := '';
            bFinished := false;
            while not bFinished do
            begin
                Value := FSheet.Cells.Item[i, 1];
                if Value = '' then
                    count := count + 1
                else
                begin
                    count := 0;
                    cdsExcel.Append;
                    cdsExcel.FieldByName('Column1').AsString := FSheet.Cells.Item[i, 1];
                    cdsExcel.FieldByName('Column2').AsString := FSheet.Cells.Item[i, 2];
                    cdsExcel.FieldByName('Column3').AsString := FSheet.Cells.Item[i, 3];
                end;
                if count > 2 then
                    bFinished := true;
                i := i + 1;
            end;
            cdsExcel.Post;
        finally
            cdsExcel.EnableControls;
        end;
    end;
end;

3 - Things to take into account.
You need to remember that the library creates an instance of Excel application, and you must close all the opened connections using the Close procedure.

newWorkbook.Close(false, EmptyParam, EmptyParam, GetUserDefaultLCID); 
 

Tuesday, 28 July 2009

Convertir ficheros Excel 2007 a dBase (DBF)

En mi anterior post, indiqué que desde excel 2007 no se podían guardar los cambios de un excel a un fichero dBase (DBF). Pues bien, hay una manera de hacerlo pero con la ayuda de nuestro Access 2007. Primero creamos nuestro fichero Excel y lo guardamos en una ruta conocida.


Luego iniciamos Microsoft Access 2007, y creamos una nueva base de datos en blanco:


Luego creamos nuestra BD y importamos nuestro fichero Excel:

Ahora marcamos la ruta desde donde importaremos el excel que hemos creado:

Seguimos con la importación, y elegimos la hoja que queremos importar:

Una vez importada la tabla, la podemos visualizar dentro de Microsoft Access:

Ahora, ya estamos preparados para generar nuestro fichero dBase (.DBF). Nos situamos encima de la Hoja1 y con el botón derecho hacemos Exportar -> a Archivo de dBase:

Ahora guardamos el fichero en el formato especificado:

Ahora podemos visualizar la tabla desde mi aplicación Thundax DBF Editor:

Espero que os sirva de ayuda.

Tuesday, 14 July 2009

Cargar un excel en un TStringGrid

Hace unos meses os entretuve con Copy-paste de un Excel a un TStringGrid, pues bien, en éste post os muestro la mejora de un código de SwissDelphiCenter (por cierto, muy buen sítio para encontrar pedazos de código muy útiles en delphi) dónde se utiliza un objeto OLE (Object linking and Embedding) para cargar los datos de una hoja de excel y meterlos en un TStringGrid. Para éste ejemplo, crearé una tabla de excel llamada table1.xls y la llenaré con números aleatorios mediante la fórmula ALEATORIO.ENTRE(0;100) que me entregará un número aleatorio entre los valores 0 y 100. Muy útil para generar información aleatoria.
El resultado final es el siguiente:

El problema de trabajar con OLE es que hay que tener en cuenta de que realmente se llama a la aplicación y puede ser que a veces se quede en segundo plano roncando sin que nos demos cuenta y con el fichero abierto. Por eso hay que vigilar y siempre cerrar la aplicación.

Aquí os dejo el código fuente:




uses
ComObj;

type
TForm1 = class(TForm)
StringGrid1: TStringGrid;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

function Load_Excel_To_TStringGrid(StringGrid: TStringGrid; ExcelFile: string): Boolean;
const
xlCellTypeLastCell = $0000000B;
var
ExcelApp, Sheet: OLEVariant;
RangeSheet: Variant;
Row, Col, i, j: Integer;
begin
Result := False;
ExcelApp := CreateOleObject('Excel.Application');
try
ExcelApp.Visible := False;
ExcelApp.Workbooks.Open(ExcelFile);
Sheet := ExcelApp.Workbooks[ExtractFileName(ExcelFile)].WorkSheets[1];
Sheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Activate;
Row := ExcelApp.ActiveCell.Row;
Col := ExcelApp.ActiveCell.Column;
StringGrid.RowCount := Row;
StringGrid.ColCount := Col;
RangeSheet := ExcelApp.Range['A1', ExcelApp.Cells.Item[Row, Col]].Value;
i := 1;
repeat
for j := 1 to Col do
StringGrid.Cells[(j - 1), (i - 1)] := RangeSheet[i, j];
Inc(i, 1);
StringGrid.RowCount := i + 1;
until i > Row;
RangeSheet := Unassigned;
finally
if not VarIsEmpty(ExcelApp) then
begin
ExcelApp.Save;
ExcelApp.Quit;
ExcelApp := Unassigned;
Sheet := Unassigned;
Result := True;
end;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if Load_Excel_To_TStringGrid(StringGrid1, ExtractFilePath(ParamStr(0)) + '\Table1.xls') then
ShowMessage('Table has been loaded!');
end;

end.







Tuesday, 2 June 2009

Expresiones regulares en Delphi

Las expresiones regulares son cadenas de texto que describen un patrón de búsqueda. Podemos pensar en éstas expresiones como una especie de comodines, como los que utilizamos para listar una serie de ficheros en un directorio desde el intérprete de comandos -> dir *.doc (devolverá la lista de todos los ficheros con la extensión .doc). Pues bien, la expresión regular equivalente para *.doc seria .*\.doc$. Pero podemos llegar a hacer muchas más cosas con las expresiones regulares. Ya hay varios editores de texto como EditPad Pro o PowerGrep que llevan integradas las Regular Expression, aunque son de pago :( . En PowerGrep, podemos ver una expresión regular típica para la búsqueda de una dirección de correo electrónico:

Como podemos ver, la expresión regular \ b [A-Z0-9._ %+-]+@[ A-Z0-9 .-] + \. [AZ] {2,4} \ b buscar direcciones de correo electrónico dentro del texto. Luego con un pequeño cambio com éste: \ ^ [A-Z0-9._ %+-]+@[ A-Z0-9 .-] + \. [AZ] {2,4} \ $ nos puede servir para comprobar si la dirección de correo electrónico se ha entrado correctamente en un campo. Mediante esta expresión regular, podemos comprobar si hemos entrado los datos correctamente, y nos sirve para una multitud de lenguajes como java, perl, PHP, .net, etc.

Con Microsoft Excel, también las podemos utilizar, con la Microsoft VBScript Regular Expressions 5.5. Aquí os pongo un pequeño ejemplo de como utilizarlo, aunque hay muchos ejemplos por la red:

Luego, por ejemplo si tenemos un texto en una de las celdas y queremos extraer las direcciones electrónicas, solo tenemos que hacer:


Ahora, creamos la función que irá dentro de uno de los módulos del Excel (Acordaros de activar las macros):




Public Function extraerreg(s As String) As String
Dim o_RegExp As RegExp
Dim o_Match As Match
Dim o_Matches As MatchCollection
Set o_RegExp = New RegExp
o_RegExp.IgnoreCase = True
o_RegExp.Global = True
o_RegExp.Pattern = "[^,;\s]+@[^,;\s]+"
Set o_Matches = o_RegExp.Execute(s)
' Recorre en la colección
For Each o_Match In o_Matches
extraerreg = extraerreg + o_Match.Value + " "
Next
End
Function


Como podemos ver en el ejemplo, se utiliza la expresión regular "[^,;\s]+@[^,;\s]+" para encontrar una cadena de texto que siga este patrón. Aquí os dejo el ejemplo : libro1.xlsm

En este caso Delphi aún no lo tiene integrado en su core (esperemos que algún día lo hagan). Pero si que lo he visto en muchos de los componentes de terceros como DevExpress. Por ejemplo el componente TcxMaskEdit, permite realizar la entrada de valores mediante Regular Expressions:


En delphi, hay una librería llamada TRegExpr, de la mano de Andrey V. Sorokin. Podemos descargarla y probarla desde el siguiente enlace: RegExp Studio. Una vez descargada la librería, podemos ver varias demos de la utilización sobre las regular expressions:

En este caso, si analizamos el código fuente:




procedure TfmTRegExprClassMain.btnExtractEmailsClick(Sender: TObject);
begin
lbxEMailesExtracted.Items.CommaText := ExtractEmails (memExtractEmails.Text);
end;

// This simple function extracts all emails from input string
// and places list of this emails into result string as CSV (comma separated values)
function ExtractEmails (const AInputString : string) : string;
const
EmailRE = '[_a-zA-Z\d\-\.]+@[_a-zA-Z\d\-]+(\.[_a-zA-Z\d\-]+)+';
var
r : TRegExpr;
begin
Result := '';
r := TRegExpr.Create;
try // ensure memory clean-up
r.Expression := EmailRE;
if r.Exec (AInputString) then
REPEAT
Result := Result + r.Match [0] + ',';
UNTIL not r.ExecNext;
finally r.Free;
end;
end;




Podemos ver como utiliza expresiones regulares para capturar la dirección de correo electrónica. Pues bien, ahora ya podemos extender la librería y empezar a utilizarlas ya que nos ayudan mucho con los patrones de texto.
  • Enlaces de interés:
Expresiones regulares con visual basic.
Comprobador de expresiones regulares en javascript. (Ejemplo).
Introducción expresiones regulares.
Expresiones regulares XHTML.
Evaluar expresiones regulares.

Friday, 22 May 2009

Copy-paste de un Excel a un TStringGrid

Hace días que intento encontrar una forma de copiar del clipboard el contenido de un excel hacia un TStringGrid, y ahora que lo tengo, aquí os muestro como hacerlo, es bastante sencillo, y solo hay que parsear el contenido del clipboard e ir ubicando cada celda en su correspondiente sitio. El ejemplo que os pongo es bastante sencillo, y permite dar una visión más grande de lo que se puede llegar a hacer, en mi caso estaba harto de tratar con TMemo, porque requería de uno más por cada columna que necesitaba, y en este caso con el componente TStringGrid, puedo personalizar las columnas a mi antojo.


En este caso lo que haré, es crear una clase personalizada de TStringGrid -> TCustomGrid, donde incluiré un par de propiedades públicas que me ayuden a parsear el contenido del clipboard. Aquí os dejo el código fuente:




uses
Clipbrd;

type
TTest = class(TForm)
sgTest: TStringGrid;
procedure sgTestKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;

type
TCustomGrid = class(TStringGrid)
const
cRETURN1 = #13;
cRETURN2 = #10;
cTAB = #9;
public
iCol: integer;
iRow: integer;
procedure LoadParam();
procedure ClearCells();
procedure AddValue(Value: string);
end;


procedure TTest.sgTestKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
Value: string;
Str: string;
i: Integer;
Custom: TCustomGrid;
begin
if (Shift = [ssCtrl]) and (Key = 67) then //CONTROL+C (Copiar)
begin
Str := '';
for i := 1 to sgtest.ColCount - 1 do
begin
Str := Str + sgtest.Cells[i, sgtest.Row] + chr(9);
end;
Str := Copy(Str, 1, Length(Str) - 1);
Clipboard.Open;
Clipboard.AsText := Str;
Clipboard.Close;
end
else if (Shift = [ssCtrl]) and (Key = 86) then //CONTROL+V (Pegar)
begin
Clipboard.Open;
if not Clipboard.HasFormat(CF_Text) then
Exit;
Value := Clipboard.AsText;
Clipboard.Close;
Custom := TCustomGrid(sgtest);
Custom.LoadParam;
Custom.ClearCells;
for i := 1 to Length(Value) do
begin
if Copy(Value, i, 1) = Custom.cRETURN1 then
Continue;
if Copy(Value, i, 1) = Custom.cRETURN2 then
begin
Custom.iCol := Custom.Col;
Inc(Custom.iRow);
if i < Length(Value) then
Custom.ClearCells;
Continue;
end;
if Copy(Value, i, 1) = Custom.cTAB then
begin
Inc(Custom.iCol);
if i < Length(Value) then
Custom.ClearCells;
Continue;
end;
Custom.AddValue(Copy(Value, i, 1));
end;
if Custom.RowCount - 1 < Custom.iRow then
Custom.RowCount := Custom.iRow;
if Custom.InplaceEditor = nil then
Exit;
Custom.InplaceEditor.Text := Custom.Cells[Custom.Col, Custom.Row];
Custom.InplaceEditor.SelStart := Length(Custom.Cells[Custom.Col, Custom.Row]);
end;
end;

{ TCustomGrid }

procedure TCustomGrid.AddValue(Value: string);
begin
Self.Cells[Self.iCol, Self.iRow] := Self.Cells[Self.iCol, Self.iRow] + Value;
end;

procedure TCustomGrid.ClearCells;
begin
Self.Cells[Self.iCol, Self.iRow] := '';
end;

procedure TCustomGrid.LoadParam;
begin
self.iCol := self.Col;
self.iRow := self.Row;
end;