I wanted to know if anyone ones a way that I can export data from a DBGrid to Excel ? I am using Delphi 7 , Excel 2007 and ADO .
Any help will be appreciated.
If you want a fast export of raw data, just export your recordset (ADODataset.recordset) with something like that:
procedure ExportRecordsetToMSExcel(DestName: string; Data: _Recordset);
var
ovExcelApp: OleVariant;
ovExcelWorkbook: OleVariant;
ovWS: OleVariant;
ovRange: OleVariant;
begin
ovExcelApp := CreateOleObject('Excel.Application'); //If Excel isnt installed will raise an exception
try
ovExcelWorkbook := ovExcelApp.WorkBooks.Add;
ovWS := ovExcelWorkbook.Worksheets.Item[1]; // go to first worksheet
ovWS.Activate;
ovWS.Select;
ovRange := ovWS.Range['A1', 'A1']; //go to first cell
ovRange.Resize[Data.RecordCount, Data.Fields.Count];
ovRange.CopyFromRecordset(Data, Data.RecordCount, Data.Fields.Count); //this copy the entire recordset to the selected range in excel
ovWS.SaveAs(DestName, 1, '', '', False, False);
finally
ovExcelWorkbook.Close(SaveChanges := False);
ovWS := Unassigned;
ovExcelWorkbook := Unassigned;
ovExcelApp := Unassigned;
end;
end;
It is working by using Tfilestream component
procedure TForm2.ExportdatatoexcelClick(Sender: TObject);
var
Stream: TFileStream;
i: Integer;
OutLine,f: string;
sTemp,s: string;
begin
Stream := TFileStream.Create('D:\Yogesh Delphi\employee1.csv', fmCreate);
try
s := string(adotable1.Fields[0].FieldName);
for I := 1 to adotable1.FieldCount - 1 do
begin
s:= s+ ',' + string(adotable1.Fields[I].FieldName);
end;
s:= s+ #13#10;
stream.Write(s[1], Length(s) * SizeOf(Char));
{S := '';
for I := 0 to adotable1.FieldCount - 1 do
begin
S := (adotable1.Fields[I].FieldName);
outline := OutLine+S + ' ,';
end; }
while not adotable1.Eof do
begin
// You'll need to add your special handling here where OutLine is built
s:='';
OutLine := '';
for i := 0 to adotable1.FieldCount - 1 do
begin
sTemp := adotable1.Fields[i].AsString;
// Special handling to sTemp here
OutLine := OutLine + sTemp +',';
end;
// Remove final unnecessary ','
SetLength(OutLine, Length(OutLine) - 1);
// Write line to file
Stream.Write(OutLine[1], Length(OutLine) * SizeOf(Char));
// Write line ending
Stream.Write(sLineBreak, Length(sLineBreak));
adotable1.Next;
end;
finally
Stream.Free; // Saves the file
end;
showmessage('Records Successfully Exported.') ;
end;
{Yog}
Related
I have an ADOQuery (TADOQuery, bound to other visual components) with multiple columns (fields), in Delphi. I can export all the data (rows and columns) to an Excel file. I'm using OleVariant, something like ovRange.CopyFromRecordset (Data, Rows, Cols).
How can I export only some columns from an ADOQuery to Excel using Delphi (any version)?
procedure ExportRecordsetToMSExcel(const DestName: string; Data: _Recordset);
var
ovExcelApp: OleVariant;
ovExcelWorkbook: OleVariant;
ovWS: OleVariant;
ovRange: OleVariant;
FileFormat: Integer;
Cols, Rows: Cardinal;
begin
FileFormat := ExcelFileTypeToInt(xlWorkbookDefault);
ovExcelApp := CreateOleObject('Excel.Application'); // If Excel isnt installed will raise an exception
try
ovExcelWorkbook := ovExcelApp.WorkBooks.Add;
ovWS := ovExcelWorkbook.Worksheets.Item[1]; // go to first worksheet
ovWS.Activate;
ovWS.Select;
Rows := Data.RecordCount;
Cols := Data.Fields.Count; // I don't want all of them, just some, maybe the ones that are visible
ovRange := ovWS.Range['A1', 'A1']; // go to first cell
ovRange.Resize[Rows, Cols]; //ovRange.Resize[Data.RecordCount, Data.Fields.Count];
ovRange.CopyFromRecordset(Data, Rows, Cols); // this copy the entire recordset to the selected range in excel
ovWS.SaveAs(DestName, FileFormat, '', '', False, False);
finally
ovExcelWorkbook.Close(SaveChanges := False);
ovWS := Unassigned;
ovExcelWorkbook := Unassigned;
ovExcelApp.Quit;
ovExcelApp := Unassigned;
end;
end;
...
ExportRecordsetToMSExcel('c:\temp\test.xlsx', ADOQuery.Recordset);
Resolved (working solution based on #MartynA and #PeterWolf's answers):
procedure ExportRecordsetToMSExcel(const DestName: string; ADOQuery: TADOQuery; const Fields: array of string); overload;
procedure CopyData( { out } var Values: OleVariant);
var
R, C: Integer;
FieldsNo: array of Integer;
L1, H1, L2, H2: Integer;
V: Variant;
F: TField;
begin
L1 := 0;
H1 := ADOQuery.RecordSet.RecordCount + L1 - 1;
L2 := Low(Fields); // 0
H2 := High(Fields);
SetLength(FieldsNo, Length(Fields));
for C := L2 to H2 do
FieldsNo[C] := ADOQuery.FieldByName(Fields[C]).Index;
Values := VarArrayCreate([L1, H1, L2, H2], varVariant);
for R := L1 to H1 do begin
for C := L2 to H2 do
Values[R, C] := ADOQuery.RecordSet.Fields[FieldsNo[C]].Value;
ADOQuery.RecordSet.MoveNext();
end;
end;
var
ovExcelApp: OleVariant;
ovExcelWorkbook: OleVariant;
ovWS: OleVariant;
ovRange: OleVariant;
Values: OleVariant;
RangeStr: string;
Rows, Cols: Integer;
begin
CopyData(Values);
try
ovExcelApp := CreateOleObject('Excel.Application');
try
ovExcelWorkbook := ovExcelApp.WorkBooks.Add;
ovWS := ovExcelWorkbook.ActiveSheet;
Rows := ADOQuery.RecordSet.RecordCount;
Cols := Length(Fields);
RangeStr := ToRange(1, 1, Rows, Cols); // Ex: 'A1:BE100'
ovRange := ovWS.Range[RangeStr];
ovRange.Value := Values;
ovWS.SaveAs(FileName := DestName);
finally
ovExcelWorkbook.Close(SaveChanges := False);
ovWS := Unassigned;
ovExcelWorkbook := Unassigned;
ovExcelApp.Quit;
ovExcelApp := Unassigned;
end;
finally
VarClear(Values);
end;
end;
Update
I am obliged to Peter Wolf for the suggestion to use Excel's Transpose function to avoid the element by element copying in my initial code. Trying to implement it, I found I ran into a known problem with Transpose, that it throws a "Type mismatch" error if it encounters a Null in the array it is transposing. The updated code below has a work-around to this problem, and also removes a number of lines from the OP's code which seemed to me to be superfluous.
====
You can do what you are asking, without changing the SQL used to retrieve your recordset by using the recordset's GetRows method which is declared in AdoIntf.Pas as
function GetRows(Rows: Integer; Start: OleVariant; Fields: OleVariant): OleVariant; safecall;
This can retrieve the values from one or more named columns from the recordset into a variant array, as documented here: https://learn.microsoft.com/en-us/office/client-developer/access/desktop-database-reference/recordset-getrows-method-dao
A version of your routine modified to use recordset.GetRows might be
procedure ExportRecordsetToMSExcel(const DestName: string; Data: _Recordset);
var
ovExcelApp: OleVariant;
ovExcelWorkbook: OleVariant;
ovWS: OleVariant;
ovRange: OleVariant;
Rows : Integer;
FieldList : Variant;
RSRows : OleVariant;
i : Integer;
Values : OleVariant;
begin
ovExcelApp := CreateOleObject('Excel.Application');
ovExcelApp.Visible := True; // So we can see what's happening
try
ovExcelWorkbook := ovExcelApp.WorkBooks.Add;
ovWS := ovExcelWorkbook.ActiveSheet;
// RecordSet.GetRows (see AdoIntf.Pas) can return one or more fields of the RS to a variant array
FieldList := 'Name';
RSRows := Data.GetRows(Data.RecordCount, '', 'name' );
// The values from the RS 'Name' field are now in the 2nd dimension of RSRows
// The following is a naive way of extracting these values to a Transposable array
Values := VarArrayCreate([VarArrayLowBound(RSRows, 2), VarArrayHighBound(RSRows, 2)], varVariant);
Rows := VarArrayHighBound(RSRows, 2) - VarArrayLowBound(RSRows, 2) + 1;
for i := VarArrayLowBound(RSRows, 2) to VarArrayHighBound(RSRows, 2) do begin
Values[i] := RSRows[0, i];
// Note: the next 2 lines are to avoid the known problem that calling Excel's Transpose
// will generate a "Type mismatch" error when the array bring transposed contains Nullss
if VarIsNull(Values[i]) then
Values[i] := '';
end;
// Now, transpose Values into the destination range (the 'A' column) using Excel's built-in function
ovWS.Range['A1:A' + IntToStr(Rows)] := ovExcelApp.Transpose(Values);
ShowMessage(' here');
finally
ovExcelWorkbook.Close(SaveChanges := False); // Abandon changes to avoid tedium in debugging
ovWS := Unassigned;
ovExcelWorkbook := Unassigned;
ovExcelApp.Quit;
ovExcelApp := Unassigned;
end;
end;
As noted in the code's comments, this extracts the Name column of the Sql table I happened to by using for this answer.
Please note R Hoek's comment about bracketing the call to your bound dataset's Open method by calls to DisableControls and EnableControls, as this will likely have as big an impact on speed as the method you use to import the column(s) into Excel.
I am refactoring an old application to make it a bit more responsive and I have a form that is using devExpress components and it creates a custom grid using the CallbackCustomDrawPreviewCell, the problem is that this function is very slow it takes about 0.09s per call but it is call about 30 to 60 times each time the form is open so the form can take 2.8s to 5.6s to open.
I normally program with C# and Object-C/Swift where we can dispatch a block to be process in the background, but as far as my research go we don't have nothing similar in Delphi, it seems that normally in Delphi a new thread has to be a whole new and independent piece of code. Is my assumptions correct?
If so what is the best type of solution to improve speed in this kind of situation? (I am using Delphi XE)
(in case it helps: I also just bought AQTime to try help me figure out how to improve this but I had no luck so far with it, still need to dig into the manuals a little more. But it did help me find the problem in the speed in this particular callback)
Thanks in advance.
The function is:
procedure TtvdAvaOutageManagementForm.CallbackCustomDrawPreviewCell(Sender: TcxCustomTreeList; ACanvas: TcxCanvas;
AViewInfo: TcxTreeListEditCellViewInfo; var ADone: Boolean);
const
AlignFlag = DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX or DT_END_ELLIPSIS;
cnFontColor = clBlue;
var
AFaultId: variant;
aFault: TtvdFault;
aLocalities, aFaultLocalities: TStringList;
i: integer;
aLocality: string;
Rect: TRect;
size: TSize;
AText: string;
begin
{ colour the preview text blue }
ACanvas.Font.Color := cnFontColor;
AText := AViewInfo.DisplayValue;
aLocalities := TStringList.Create;
aFaultLocalities := TStringList.Create;
try
AFaultId := AViewInfo.Node.Values[FtvdTree.GetColumnByFieldName('FaultId').ItemIndex];
if (not VarIsNull(AFaultId)) then
begin
ACanvas.Brush.Color := COLOR_FAULT;
aFault := FtvdFaults.tvdGetFault(AFaultId);
if Assigned(aFault) then
begin
ACanvas.Brush.Color := aFault.tvdFaultColor;
ACanvas.Brush.Color := aFault.tvdFaultColor;
ACanvas.FillRect(AViewInfo.BoundsRect);
CopyRect(Rect, AViewInfo.BoundsRect);
InflateRect(Rect, -1, -1);
Inc(Rect.Left, FtvdTree.OptionsView.IndicatorWidth);
ACanvas.Font.Color := cnFontColor;
{ if all the localities are in the fault then bold the preview text,
else need to do it manually (i.e. only bold the localities that are
in the fault }
if aFault.tvdAllLocalities then
begin
ACanvas.Font.Style := [fsBold];
ACanvas.DrawTexT(AText, AViewInfo.BoundsRect, AlignFlag);
end
else
begin
CopyRect(Rect, AViewInfo.BoundsRect);
aLocalities.Text := StringReplace(AText, ', ', #13#10, [rfReplaceAll]);
aFaultLocalities.Text := StringReplace(aFault.tvdLocalities, ', ', #13#10, [rfReplaceAll]);
for i := 0 to aLocalities.Count - 1 do
begin
ACanvas.Font.Style := [];
{ draw a comma if this is not the first locality }
if i > 0 then
begin
size := ACanvas.TextExtent(',');
DrawText(ACanvas.Handle, ',', 1, Rect, DT_LEFT or DT_NOPREFIX);
Inc(Rect.Left, size.cx);
end;
aLocality := aLocalities[i];
if aFaultLocalities.IndexOf(aLocality) >= 0 then
begin
ACanvas.Font.Style := [fsBold];
end;
size := ACanvas.TextExtent(aLocality);
if (Rect.Left + size.cx) > Rect.Right then
begin
Rect.Left := AViewInfo.BoundsRect.Left;
Inc(Rect.Top, size.cy);
end;
{ draw the text item }
DrawText(ACanvas.Handle, pchar(aLocality), Length(aLocality), Rect, DT_LEFT or DT_NOPREFIX);
Inc(Rect.Left, size.cx);
end;
end;
ADone := true;
end;
end;
finally
aLocalities.Free;
aFaultLocalities.Free;
end;
end;
If you sum up my comments then it should be more or less this.
Try that at let us know how it worked out for you. Since I don't have a working example it might not be 100% correct.
procedure TtvdAvaOutageManagementForm.CallbackCustomDrawPreviewCell(Sender: TcxCustomTreeList; ACanvas: TcxCanvas;
AViewInfo: TcxTreeListEditCellViewInfo; var ADone: Boolean);
const
AlignFlag = DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or DT_NOPREFIX or DT_END_ELLIPSIS;
cnFontColor = clBlue;
var
AFaultId: variant;
aFault: TtvdFault;
aLocalities, aFaultLocalities: TStringList;
i: integer;
aLocality: string;
Rect: TRect;
size: TSize;
AText: string;
begin
{ colour the preview text blue }
ACanvas.Font.Color := cnFontColor;
AText := AViewInfo.DisplayValue;
aLocalities := TStringList.Create;
aFaultLocalities := TStringList.Create;
try
AFaultId := AViewInfo.Node.Values[FaultIdColumn.ItemIndex];
if not VarIsNull(AFaultId) then
begin
ACanvas.Brush.Color := COLOR_FAULT;
aFault := FtvdFaults.tvdGetFault(AFaultId);
if Assigned(aFault) then
begin
ACanvas.Brush.Color := aFault.tvdFaultColor;
ACanvas.Brush.Color := aFault.tvdFaultColor;
ACanvas.FillRect(AViewInfo.BoundsRect);
CopyRect(Rect, AViewInfo.BoundsRect);
InflateRect(Rect, -1, -1);
Inc(Rect.Left, FtvdTree.OptionsView.IndicatorWidth);
ACanvas.Font.Color := cnFontColor;
{ if all the localities are in the fault then bold the preview text,
else need to do it manually (i.e. only bold the localities that are
in the fault }
if aFault.tvdAllLocalities then
begin
ACanvas.Font.Style := [fsBold];
ACanvas.DrawTexT(AText, AViewInfo.BoundsRect, AlignFlag);
end
else
begin
CopyRect(Rect, AViewInfo.BoundsRect);
aLocalities.CommaText:= AText;
aFaultLocalities.CommaText := aFault.tvdLocalities;
aFaultLocalities.Sorted := True;
for i := 0 to aLocalities.Count - 1 do
begin
ACanvas.Font.Style := [];
{ draw a comma if this is not the first locality }
if i > 0 then
begin
size := ACanvas.TextExtent(',');
DrawText(ACanvas.Handle, ', ', 1, Rect, DT_LEFT or DT_NOPREFIX);
Inc(Rect.Left, size.cx);
end;
aLocality := aLocalities[i];
if aFaultLocalities.IndexOf(aLocality) >= 0 then
begin
ACanvas.Font.Style := [fsBold];
end;
size := ACanvas.TextExtent(aLocality);
if (Rect.Left + size.cx) > Rect.Right then
begin
Rect.Left := AViewInfo.BoundsRect.Left;
Inc(Rect.Top, size.cy);
end;
{ draw the text item }
DrawText(ACanvas.Handle, pchar(aLocality), Length(aLocality), Rect, DT_LEFT or DT_NOPREFIX);
Inc(Rect.Left, size.cx);
end;
end;
ADone := true;
end;
end;
finally
aLocalities.Free;
aFaultLocalities.Free;
end;
end;
I have the following code and I want to extract it into a plain text but I cannot manage to do it.
The code works but what I need is to show it in the ExpandConstant field.
I have tried several ways but no luck so far.
function LoadValueFromXML(const AFileName, APath: string): string;
var
XMLNode: Variant;
XMLDocument: Variant;
begin
Result := '';
XMLDocument := CreateOleObject('Msxml2.DOMDocument.3.0');
try
XMLDocument.async := False;
XMLDocument.load(AFileName);
if (XMLDocument.parseError.errorCode <> 0) then
MsgBox('The XML file could not be parsed. ' +
XMLDocument.parseError.reason, mbError, MB_OK)
else
begin
XMLDocument.setProperty('SelectionLanguage', 'XPath');
XMLNode := XMLDocument.selectSingleNode(APath);
Result := XMLNode.text;
end;
except
MsgBox('An error occured!' + #13#10 + GetExceptionMessage, mbError, MB_OK);
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = CustomPageID then
CustomEdit.Text := LoadValueFromXML('C:\Games\World_of_Tanks_test\WoTLauncher.xml', '//info/patch_info_urls/item');
end;
procedure ClienteWot();
var
StaticText: TNewStaticText;
begin
StaticText := TNewStaticText.Create(WizardForm);
StaticText.Parent := WizardForm.SelectComponentsPage;
StaticText.Left := 425;
StaticText.Top := ScaleY(40);
StaticText.Font.Style := [fsBold];
//StaticText.Font.Color := clRed;
StaticText.Caption := ExpandConstant('Cliente WOT: -->>> Show XML Url <<<---');
end;
If you want to inline a value into a string that can be passed to the ExpandConstant function call, you can use e.g. the Format function:
var
...
URL: string;
begin
...
URL := LoadValueFromXML('C:\MyFile.xml', '//node/subnode');
StaticText.Caption := ExpandConstant(Format('{username} %s', [URL]));
end;
The above pseudocode example reads the XML value and assigns the returned value to the URL variable. Then it evaluates the inner statement of the second line:
Format('{username} %s', [URL])
Which inlines the URL string value into the given string (in place of %s) and produces a string like:
'{username} www.example.com'
And this string is then processed by the ExpandConstant function call (the outer statement) and assigned to the static text caption, which might be e.g.:
'MyUserName www.example.com'
Sorry, I'm very ingnorant and I would like to know why the const lWBATWorkSheet is setted at the number -4167.
Thanks, Jack.
function TForm1.SaveAsExcelFile(AGrid: TStringGrid; ASheetName, AFileName: string): boolean;
const
xlWBATWorksheet = -4167;
var
righe, colonne: Integer;
GridPrevFile: string;
XLApp, Sheet, Data: OLEVariant;
l, s: Integer;
begin
// Prepare Data
Data := VarArrayCreate([1, AGrid.RowCount, 1, AGrid.ColCount], varVariant);
for l := 0 to AGrid.ColCount - 1 do
for s := 0 to AGrid.RowCount - 1 do
Data[s + 1, l + 1] := AGrid.Cells[l, s];
// Create Excel-OLE Object
Result := False;
XLApp := CreateOleObject('Excel.Application');
try
// Hide Excel
XLApp.Visible := False;
// Add new Workbook
XLApp.Workbooks.Add(xlWBatWorkSheet);
Sheet := XLApp.Workbooks[1].WorkSheets[1];
Sheet.Name := ASheetName;
// Fill up the sheet
Sheet.Range[RefToCell(1, 1), RefToCell(AGrid.RowCount,AGrid.ColCount)].Value := Data;
// Save Excel Worksheet
try
XLApp.Workbooks[1].SaveAs(AFileName);
Result := True;
except
ShowMessage('Fatal Error!');
end;
finally
// Quit Excel
if not VarIsEmpty(XLApp) then
begin
XLApp.DisplayAlerts := False;
XLApp.Quit;
XLAPP := Unassigned;
Sheet := Unassigned;
end;
end;
end;
Adding excel2000 or excel97 to the use clause would let you get rid of the need to search and declare the constants on your own.
implementation
uses excel2000;
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
begin
Showmessage(Format('Unsigned %u, Signed %d', [xlWBATWorksheet , xlWBATWorksheet]));
// will return Unsigned 4294963129, Signed -4167
end;
This is an Enumeration. Probably it's this value because it was the next free value in the list.
Here's a nice list with Microsofts enumerations where you will see they started with -4098 and use the values from there on.
http://include.wutils.com/com-dll/constants/constants-Graph.htm
implementation
uses Excel2010;
{$R *.dfm}
I'm trying to copy the content from a TDBGrid to an Excel file using an ADO connection to transfer the data. This works for values that are <= 255 characters but fails for longer strings. What can I do to copy strings that are longer than 255 characters?
Changing the DataType to adLongVarWChar tbl.Columns.Append doesn't work. The ADOQuery gets a varchar field with Size 255 regardsless what I use when I create the table.
procedure DBGridToExcelADO(DBGrid: TDBGrid; FileName: string; SheetName: string);
var
cat : _Catalog;
tbl : _Table;
col : _Column;
i : integer;
ADOConnection : TADOConnection;
ADOConnectionExcel: TADOConnection;
ADOQuery : TADOQuery;
ScrollEvents : TScrollEvents;
SavePlace : TBookmark;
begin
//exporting
ADOConnectionExcel := TADOConnection.Create(nil);
try
ADOConnectionExcel.LoginPrompt := False;
ADOConnectionExcel.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' + FileName + ';Extended Properties=Excel 8.0';
ADOConnectionExcel.Open;
//WorkBook creation (database)
cat := CoCatalog.Create;
cat._Set_ActiveConnection(ADOConnectionExcel.ConnectionObject);
//WorkSheet creation (table)
tbl := CoTable.Create;
tbl.Set_Name(SheetName);
//Columns creation (fields)
DBGrid.DataSource.DataSet.First;
with DBGrid.Columns do
begin
for i := 0 to Count - 1 do
if Items[i].Visible then
begin
col := nil;
col := CoColumn.Create;
with col do
begin
Set_Name(Items[i].Title.Caption);
Set_Type_(adVarWChar);
end;
//add column to table
tbl.Columns.Append(col, adVarWChar, 20);
end;
end;
//add table to database
cat.Tables.Append(tbl);
col := nil;
tbl := nil;
cat := nil;
//exporting
ADOConnection := TADOConnection.Create(nil);
ADOConnection.LoginPrompt := False;
ADOConnection.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0; Data Source=' + FileName + ';Extended Properties=Excel 8.0';
ADOQuery := TADOQuery.Create(nil);
ADOQuery.Connection := ADOConnection;
ADOQuery.SQL.Text := 'Select * from [' + SheetName + '$]';
ADOQuery.Open;
DisableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
SavePlace := DBGrid.DataSource.DataSet.GetBookmark;
try
with DBGrid.DataSource.DataSet do
begin
First;
while not Eof do
begin
ADOQuery.Append;
with DBGrid.Columns do
begin
ADOQuery.Edit;
for i := 0 to Count - 1 do
if Items[i].Visible then
begin
//Fails if Length > 255
ADOQuery.FieldByName(Items[i].Title.Caption).AsString := FieldByName(Items[i].FieldName).AsString;
end;
ADOQuery.Post;
end;
Next;
end;
end;
finally
DBGrid.DataSource.DataSet.GotoBookmark(SavePlace);
DBGrid.DataSource.DataSet.FreeBookmark(SavePlace);
EnableDependencies(DBGrid.DataSource.DataSet, ScrollEvents);
ADOQuery.Close;
ADOConnection.Close;
ADOQuery.Free;
ADOConnection.Free;
end;
finally
if Assigned(ADOConnection) and ADOConnection.Connected then ADOConnectionExcel.Close;
ADOConnectionExcel.Free;
end;
end;
You can use TJvDBGridExcelExport JVCL coming in the Jedi (www.delphi-jedi.org/). I have used this with good results, and this is opensource.