customizing imported excel file from delphi - excel

I write a code to export an excel file from a dbgrid. Here's the following code :
var i, x:integer;
sfile:string;
begin
XlApp:=createoleobject('Excel.Application');
XlBook:=XlApp.WorkBooks.Add;
XlSheet:= XlBook.worksheets.add;
for i:=0 to dbgrid1.fieldcount-1 do begin
xlsheet.cells[1,i+1].value:=dbgrid1.columns[i].title.caption;
end;
zquery1.first;
x:=1;
while not zquery1.eof do begin
xlsheet.cells[x+1,1].value:=x;
for i:=1 to dbgrid1.fieldcount-1 do begin
xlsheet.cells[x+1, i+1].value:=dbgrid1.fields[i].text;
end;
zquery1.next;
inc(x);
end;
xlapp.visible:=true;
end;
it works just fine, but the table is so messy.
How can I customize this excel file so that each column give the correct width value according to each data?
thanks.

An Excel Range has an AutoFit method that will do this for you in a single call, and doesn't require you to experiment to figure out what width you need to assign to each column.
Here's an example (tested in Delphi 2007) that creates a two-dimensional variant array, populates it with sample text that is too wide to fit a default Excel cell, assigns that text to an Excel range, and autowidths all cells within that range to the proper width for the text (similar to what happens when you double-click the divider between columns in Excel). You should be able to easily adapt both this way to autowidth the text (and the much faster way of transferring data into Excel via automation) to work with your code.
For demo purposes, I've put the code into a TButton.OnClick event handler.
uses
ComObj, ActiveX;
procedure TForm3.Button1Click(Sender: TObject);
var
xls, wb, Range: OLEVariant;
arrData: Variant;
RowCount, ColCount, i, j: Integer;
begin
{create variant array where we'll copy our data}
RowCount := 10;
ColCount := 10;
arrData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
{fill array}
for i := 1 to RowCount do
for j := 1 to ColCount do
arrData[i, j] := Format('This is test text #%d-%d', [i, j]);
{initialize an instance of Excel}
xls := CreateOLEObject('Excel.Application');
{create workbook}
wb := xls.Workbooks.Add;
{retrieve a range where data must be placed}
Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
wb.WorkSheets[1].Cells[RowCount, ColCount]];
{ copy data from the array into an Excel Range, and then use AutoFit to size them }
Range.Value := arrData;
Range.Columns.AutoFit;
{show Excel with our data}
xls.Visible := True;
end;

You can set the width of each column using Columns[i].ColumnWidth.
var
ColRange: Variant;
ColRange := xlsheet.Columns;
ColRange.Columns[1].ColumnWidth := 12;

Related

Excel - How to find location (i.e. what columns) of specific PivotFields in a PivotTable?

I am building a routine that automates creating a PivotTable. All is working well...I add my rows, columns, filters and values. As a last step, I want to format the values. I cannot find any property that shows me what "value entry/pivotfield" ends up in which column. Lets assume that I have 2 Value PivotFields...
Pivot1 := fmyPivotTable.AddDataField( 'Revenue', 'Revenue', xlSum );
Pivot2 := fmyPivotTable.AddDataField( 'Locations', 'Locations', xlCount );
I am looking for something that will tell me that Pivot1 data is in columns C, E and G while Pivot2 data is in D,F and H. The closest I could find is labelrange and position, but neither of these is really what I need. I am writing this in Delphi, but if someone knows how to do this in VBA, I can convert.
ADDITIONAL INFO.
The code I am using looks similar to this. (I had to cut out some irrelevant stuff). What I am looking for is a way to determine what columns will hold the data from v1 and v2.
var
// Filter Fields
f1, f2: OleVariant;
// Row Fields
r1, r2 : OleVariant;
// Column Fields
c1: OleVariant;
// Value Fields
v1, v2: OleVariant;
PivotStartAddr: String;
OrigDataRange: ExcelRange;
myPivotCache: PivotCache;
myPivotTable: PivotTable;
RawSheet, PivotSheet1 : _Worksheet;
begin
RawSheet := oExcel.ActiveSheet as _Worksheet;
OrigDataRange := RawSheet.Range['A1', 'BA35285'];
// Create a Pivot Cache
myPivotCache := oExcel.ActiveWorkbook.PivotCaches.Create(xlDatabase,OrigDataRange,xlPivotTableVersion15);
// Create a New Sheet
oExcel.Worksheets.Add(EmptyParam, RawSheet, 1, xlWorksheet, LOCALE_USER_DEFAULT);
PivotSheet1 := oExcel.ActiveSheet as _Worksheet;
PivotSheet1.Name := 'Pivot Sheet';
// Create a Pivot table within the PivotCache
PivotStartAddr := 'R4C1';
myPivotTable := myPivotCache.CreatePivotTable(PivotStartAddr, 'PivotTable56', EmptyParam,xlPivotTableVersion15);
// Add Filter Fields
f1 := myPivotTable.PivotFields('OVI_MAJOR');
f1.Caption := 'Industry';
f1.Orientation := xlPageField;
f1.Position :=1 ;
f2 := myPivotTable.PivotFields('APPS_ORG_TYPE');
f2.Caption := 'Apps';
f2.Orientation := xlPageField;
f2.Position :=2 ;
// Add Rows
r1 := myPivotTable.PivotFields('PARENT_ACCOUNT_NAME');
r1.Caption := 'l Name';
r1.Orientation := xlRowField;
r1.Position := 1;
r2 := myPivotTable.PivotFields('ACCOUNT');
r2.Caption := 'Account';
r2.Orientation := xlRowField;
r2.Position := 2;
// Add Columns
c1 := myPivotTable.PivotFields('CLOUD_MARKETING_SEGMENT');
c1.Caption := 'Mkt Segment';
c1.Orientation := xlColumnField;
c1.Position := 1;
v1 := myPivotTable.AddDataField('SIC', 'SIC', xlCount);
v2 := myPivotTable.AddDataField('REVENUE', 'Revenue', xlSum);
end;

How can I access a range of rows in Excel?

I am trying to translate this Excel VBA code to Delphi:
ActiveSheet.Rows(r & ":5000").WrapText = True
ActiveSheet.Rows(r & ":5000").AutoFit
However in the Excel2010 unit in Delphi _Worksheet.Rows is an object, not a function or an array object, I also can't find any Items property or similar.
uses
Excel2010;
procedure Test;
var
Sheet: ExcelWorksheet;
R: Integer;
begin
R := 3;
Sheet.Rows[R.ToString + ':5000'].WrapText := True;
// Sheet.Rows.WrapText := True;
end;
The compiler message is:
[dcc32 Error] Unit1.pas(110): E2149 Class does not have a default property
What is the correct translation of the VBA code?
How can I access a certain range of Rows in Excel?
With early binding, selecting A column (rows 3..5000) and using EntireRow, for example like this:
uses Excel2010;
procedure TForm14.Button1Click(Sender: TObject);
var
Excel: ExcelApplication;
Wbook: ExcelWorkbook;
Sheet: ExcelWorksheet;
begin
Excel := CoExcelApplication.Create;
Wbook := Excel.Workbooks.Add(EmptyParam, LOCALE_USER_DEFAULT);
Sheet := Wbook.ActiveSheet as ExcelWorksheet;
Excel.Visible[LOCALE_USER_DEFAULT] := True;
Sheet.Range['A3','A5000'].EntireRow.WrapText := True;
Sheet.Range['A3','A5000'].EntireRow.AutoFit;
//...
end;
The thing is, if you work with Excel from Delphi using early binding (e.g. using CoExcelApplication.Create), you
are working with the raw interfaces Excel exposes, rather than the variants
you get working with late binding (using CreateOleObject('Excel.Application')).
Both methods have their strengths, early binding is best for speed and taking advantage
of Delphi's type-checking and code completion, whereas late binding is useful
for avoiding to have to specify all the arguments of the methods which have
a lot of them.
In Excel_Tlb (or whatever your Excel import unit is called), maybe the best way to think
of the Rows member of the _Worksheet interface is as a function which returns
a dispinterface to an ExcelRange object. The members which return an ExcelRange interface take two arguments, which specify the top left and bottom right cells defining the range. So, one way to do something along the lines you've
asked about is the following:
That's how you get at and use the Item property you were wondering about.
procedure TDefaultForm.TestRange;
var
WB : _Workbook;
vWB,
vRange,
vSheet : OleVariant;
Sheet: _Worksheet;
Range : ExcelRange;
begin
lcid := LOCALE_USER_DEFAULT;
Excel := CoExcelApplication.Create;
Excel.Visible[LOCALE_USER_DEFAULT] := True;
WB := Excel.Workbooks.Add(EmptyParam, LOCALE_USER_DEFAULT);
Sheet := WB.ActiveSheet as ExcelWorksheet;
Range := Sheet.Range['A1', 'B2'];
Range.RowHeight := 33;
Range.Item[1, 1] := 'some text long enough to wrap';
Range.Item[1, 1].WrapText := True;
Range.Item[1, 2] := 'more text long enough to wrap';
Range.Item[2, 2] := 'other text long enough to wrap';
// The following shows how to use the special syntax for passing arguments
// in late-binding
Excel.DisplayAlerts[LOCALE_USER_DEFAULT] := False; // suppresses "Overwrite?" prompt if file already exists
vWB := WB;
// Compare the following with what you would need if you called WB.SaveAs()
vWB.SaveAs(FileName := ExtractFilePath(Application.ExeName) + 'Test.Xlsx');
// some things using late binding
vSheet := Sheet;
vRange := vSheet.Range['c3'];
vRange.Value := 'some value';
vRange := vSheet.Range['d3:e4'];
vRange.Value := 'another value';
// retrieve the ExcelRange object from the vRange variant
Range := IDispatch(vRange) as ExcelRange;
end;
Here is an example of how to use an ExcelRange
var
lcid: Integer;
Range: ExcelRange;
Worksheet: _WorkSheet;
Row: Integer;
begin
lcid := LOCALE_USER_DEFAULT;
Wbk := ExcelApplication1.Workbooks.Open(Filename,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,lcid);
WorkSheet := Wbk.Worksheets.Item['Sheet1'] as _Worksheet;
WorkSheet.Activate(lcid);
Row := 1;
Range := WorkSheet.Range['A'+IntToStr(row), 'F'+IntToStr(row)];
Range.Insert(xlShiftDown, xlFormatFromLeftOrAbove); // shift down and copy
WorkSheet.Cells.Item[row, 6] := edtVatRate.Value;
end;

Delphi - Using VarArrayCreate to loop through an Excel Range of data

Delphi Tokyo - I am using Delphi to do some Excel spreadsheet pre-processing prior to a load operation. I am trying to read an Excel Range into a VarArray, loop through the array to do cleanup (in this case, it is a zip code column. If it is a 4 digit zipcode, prefix a '0'), and then write the VarArray back to the Excel range. Everything compiles, but I get an error message on the first access of the VarArray. The specific error is 'Variant or safe array index out of bounds'. My VarArray starts at row 2 of the excel range. Any idea why I am getting this error?
I have tried to simplify the code as much as possible here....
function PROCESS_ZIP_CODE_5DIGIT_MIN(P1,P2 : String): integer;
var
MyColumnLetter : String;
thisSheet : _Worksheet;
i : Integer;
CellText : String;
arrData: Variant;
myRange : ExcelRange;
RangeStartAddr, RangeEndAddr : String;
begin
MyColumnLetter := 'H';
thisSheet := oExcel.ActiveSheet as _Worksheet;
{create variant array where we'll copy our data}
arrData := VarArrayCreate([2, 500 ], varVariant);
// Get the Range Address
RangeStartAddr := MyColumnLetter + '2';
RangeEndAddr := MyColumnLetter + IntToStr(500);
// Now read the data into the VarArray
myRange := thisSheet.range[RangeStartAddr, RangeEndAddr];
arrData := myRange.Value2;
// Now process the data itself
for i := 2 to 500 do
begin
CellText := arrData[i]; // ERROR ON THIS LINE
if Length(CellText) = 4 then
begin
CellText:= '0' + CellText;
arrData[i] := CellText;
end;
end;
// Now write the VarArray back to the spreadsheet
thisSheet.range[RangeStartAddr, RangeEndAddr].Value2 := myRange;
end;
I'm not going to try to sort out your code, because it has a bunch of errors in it.
Here's a working sample of code to retrieve a range of cells (in this case, H1 through the last populated cell in J) into a variant array and then put that array into a Delphi TStringGrid. While the code uses late binding instead of early binding, it pretty clearly demonstrates the proper use of VarArrayCreate when reading a range from Excel.
var
Excel, Book, Sheet, Range1: OleVariant;
i, j: Integer;
Data: Variant;
const
// Obtained at https://msdn.microsoft.com/en-us/library/office/ff820880.aspx
xlDown = -4121;
begin
Excel := CreateOleObject('Excel.Application');
try
Book := Excel.WorkBooks.Open('E:\TempFiles\Test.xlsx');
Sheet := Book.Worksheets.Item['Sheet1'];
// Get tne range we want to extract, in this case all rows of columns H-J.
// .End(xlDown) finds the last used cell in the indicated column
Range1 := Sheet.Range['H1', Sheet.Range['J1'].End[xlDown]];
Data := Range1.Value;
// Get the number of columns and rows from the array itself. The addition
// of 1 is for the fixed row and column, and to synch up with the Data
// array being 1 based instead of 0
StringGrid1.ColCount := VarArrayHighBound(Data, 2) + 1;
StringGrid1.RowCount := VarArrayHighBound(Data, 1) + 1;
// StringGrid.Cells are accessed in Col, Row order, but the
// array is returned in Row, Col layout. Note the swap in
// i and j below in the subscripts to accomodate that fact.
for i := 1 to StringGrid1.ColCount - 1 do
for j := 1 to StringGrid1.RowCount - 1 do
StringGrid1.Cells[i, j] := Data[j, i];
finally
// Clean up all references so Excel will close cleanly
Range1 := null;
Sheet := null;
Book := null;
Excel.Quit;
Excel := null;
end;

Working with Sheets in DELPHI - ActiveCell.Column returning wrong value

i want to get the number of rows and columns from a excel sheet.
I'm currently using this code:
function TFprofiles.XlsToStringGrid(AGrid: TStringGrid; AXLSFile: string): Boolean;
const
xlCellTypeLastCell = $0000000B;
var
XLApp, Sheet: OLEVariant;
RangeMatrix: Variant;
x, y, k, r: Integer;
begin
Result:=False;
XLApp:=CreateOleObject('Excel.Application');
try
XLApp.Visible:=False;
XLApp.Workbooks.Open(AXLSFile);
Sheet := XLApp.Workbooks[ExtractFileName(AXLSFile)].WorkSheets[1];
Sheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Select;
y := XLApp.ActiveCell.Row;
x := XLApp.ActiveCell.Column;
AGrid.RowCount:=24;
AGrid.ColCount:=x;
RangeMatrix := XLApp.Range['B2', XLApp.Cells.Item[y,x]];
RangeMatrix := RangeMatrix.Value;
(...)
end;
Everything works fine except for this line:
x := XLApp.ActiveCell.Column;;
My worksheet has 2 COLUMNS and ActiveCell.Column returns me 5,9, or any other value except 2!!!
I selected and deleted all the values from other cells/columns
What's possibly the problem?
Thanks!
You define Sheet as the first worksheet in the workbook then decide somewhat arbitrarily whenever to bother using it or not.
Sheet := XLApp.Workbooks[ExtractFileName(AXLSFile)].WorkSheets[1];
y := Sheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Row;
x := Sheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Column;
...
RangeMatrix := Sheet.Range['B2', Sheet.Cells.Item[y,x]];
I've replaced a few occurrences of xlApp with Sheet while trying to avoid the use of .Select. If this isn't perfect, it is because I do not have Delphi but you should get the general idea.
I find that instead of messing around with “…SpecialCells(xlCellTypeLastCell…”.
It is easier to use “UsedRange” like this >>
x := Sheet.UsedRange.Row + Sheet.UsedRange.Rows.Count - 1;
y := Sheet.UsedRange.Column + Sheet.UsedRange.Columns.Count - 1;
Problem with SpecialCells is that you can't use it on a Protected Sheet.

Delphi cxGrid rowcount

I use cxGrid and I want import a excel file to cxgrid. I wrote this code a function.
But, its wrong, because cxGrid dont know RowCount and ColCount.
I would like know, what can I use, what is similar?
Help me!
Thank you!
function Xls_To_cxGrid(AGrid: TcxGrid; AXLSFile: string): Boolean;
const
xlCellTypeLastCell = $0000000B;
var
XLApp, Sheet: OLEVariant;
RangeMatrix: Variant;
x, y, k, r: Integer;
begin
Result := False;
XLApp := CreateOleObject('Excel.Application');
try
XLApp.Visible := False;
XLApp.Workbooks.Open(AXLSFile);
Sheet := XLApp.Workbooks[ExtractFileName(AXLSFile)].WorkSheets[1];
Sheet.Cells.SpecialCells(xlCellTypeLastCell, EmptyParam).Activate;
x := XLApp.ActiveCell.Row;
y := XLApp.ActiveCell.Column;
AGrid.RowCount := x;
AGrid.ColCount := y;
RangeMatrix := XLApp.Range['A1', XLApp.Cells.Item[X, Y]].Value;
k := 1;
repeat
for r := 1 to y do
AGrid.Cells[(r - 1), (k - 1)] := RangeMatrix[K, R];
Inc(k, 1);
AGrid.RowCount := k + 1;
until k > x;
RangeMatrix := Unassigned;
finally
if not VarIsEmpty(XLApp) then
begin
XLApp.Quit;
XLAPP := Unassigned;
Sheet := Unassigned;
Result := True;
end;
end;
end;
The cxGrid is just a container for ChartViews, TableViews, CardViews and BandedTableViews. Being a container, it does not know anything about rows and columns. Because you want to replace a StringGrid I suggest using a (non-DB) TableView.
To get a TableView...
...open the grid customization form (click on "Customize..." in the grid's "Structure Navigator" in the form designer).
Go to the "Views" tab and click "Add View...". Choose "Table" (not "DB Table"!).
On the "Structure" tab right-click on the cxGrid1Level1 symbol, choose "Select View" and the new TableView.
You can now go back to the "Views" tab and delete the DB TableView (cxGrid1DBTableView1).
(See also the DX help topic "Grid Levels" unter the heading "Specifying Associated Grid Views".)
Instead of "RowCount" you would then use YourView.DataController.RecordCount. "ColCount" would be YourView.ColumnCount. You can access cell values with YourView.DataController.Values[...]. To speed up the populating the view I'd surround it with
YourView.DataController.BeginUpdate;
try
YourView.DataController.RecordCount := x;
// ...
finally
YourView.DataController.EndUpdate;
end;

Resources