I'm working with a bunch of grids and those grids don't support copying rows and columns with a table layout like Excel does.
We need to be able to copy some rows and columns from the grid, and paste them into an Outlook email with decent formatting in properly aligned columns. If you copy from Excel, it does that nicely.
If I copy from the grid, I get tab-delimited data, and that doesn't work. Also the fonts are not monospaced like Courier, so padding the data to the same number of characters also doesn't work.
I'd really like to know how Excel manages to put this extra formatting into the clipboard. I'm using Delphi by the way, but any advice is appreciated.
EDIT : we don't want to go via Excel first...we want to go straight from the grid to the clipboard and then to the email.
Thanks! Bart
When you copy to the clipboard from Excel, many different formats are placed on the clipboard. You need to find one of those formats that you can replicate, and that will give the desired result.
The way I have achieved this in the past is to put HTML on the clipboard. For which you can use this function:
procedure ClipboardError;
begin
raise EMyExceptionClass.Create('Could not complete clipboard operation.');
end;
procedure CheckClipboardHandle(Handle: Windows.HGLOBAL);
begin
if Handle=0 then begin
ClipboardError;
end;
end;
procedure CheckClipboardPtr(Ptr: Pointer);
begin
if not Assigned(Ptr) then begin
ClipboardError;
end;
end;
procedure PutInClipboard(ClipboardFormat: UINT; Buffer: Pointer; Count: Integer);
var
Handle: Windows.HGLOBAL;
Ptr: Pointer;
begin
if Count>0 then begin
Clipboard.Open;
Try
Handle := Windows.GlobalAlloc(GMEM_MOVEABLE, Count);
Try
CheckClipboardHandle(Handle);
Ptr := Windows.GlobalLock(Handle);
CheckClipboardPtr(Ptr);
Move(Buffer^, Ptr^, Count);
Windows.GlobalUnlock(Handle);
Clipboard.SetAsHandle(ClipboardFormat, Handle);
Except
GlobalFree(Handle);
raise;
End;
Finally
Clipboard.Close;
End;
end;
end;
var
HTMLClipboardFormat: UINT;
procedure PutHTMLInClipboard(Strings: TStrings);
var
Data: TStringList;
procedure WriteDescription(const StartOffset, EndOffset: Integer);
begin
while Data.Count<5 do begin
Data.Add('');
end;
Data[0] := 'Version:0.9';
Data[1] := Format('StartHTML:%.8d', [StartOffset]);
Data[2] := Format('EndHTML:%.8d', [EndOffset]);
Data[3] := Format('StartFragment:%.8d', [StartOffset]);
Data[4] := Format('EndFragment:%.8d', [EndOffset]);
end;
var
StartOffset, EndOffset: Integer;
Text: UTF8String;
begin
Data := TStringList.Create;
Try
WriteDescription(0, 0);//write out description stub - will be replaced later
StartOffset := Length(UTF8String(Data.Text));
Data.AddStrings(Strings);
EndOffset := Length(UTF8String(Data.Text))-1;
WriteDescription(StartOffset, EndOffset);//now we know the offsets we can write proper description
Text := Data.Text;
PutInClipBoard(HTMLClipboardFormat, PAnsiChar(Text), Length(Text));
Finally
FreeAndNil(Data);
End;
end;
....
initialization
HTMLClipboardFormat := Windows.RegisterClipboardFormat('HTML Format');
The only thing that remains is to generate the HTML which you pass to that function. That's down to you. I suggest that you use Excel to put HTML on the clipboard and then inspect the HTML that it generates. Use that as a guide for what you need to do.
You can use this function to export your grid contents to Excel, and other applications which support tabular data:
procedure ExportDBGrid(DBGrid: TDBGrid; toExcel: Boolean);
var
bm: TBookmark;
col, row: Integer;
sline: String;
mem: TStringList;
ExcelApp: Variant;
begin
Screen.Cursor := crHourglass;
try
DBGrid.DataSource.DataSet.DisableControls;
bm := DBGrid.DataSource.DataSet.GetBookmark;
DBGrid.DataSource.DataSet.First;
// create the Excel object
if toExcel then
begin
ExcelApp := CreateOleObject('Excel.Application');
ExcelApp.WorkBooks.Add(1); //xlWBatWorkSheet);
ExcelApp.WorkBooks[1].WorkSheets[1].Name := 'Grid Data';
end;
// First we send the data to a memo
// works faster than doing it directly to Excel
mem := TStringList.Create;
try
sline := '';
// add the info for the column names
for col := 0 to DBGrid.FieldCount-1 do
if Assigned(DBGrid.Fields[col]) then
if DBGrid.Fields[col].Visible then
sline := sline + DBGrid.Fields[col].DisplayLabel + #9;
mem.Add(sline);
// get the data into the memo
for row := 0 to DBGrid.DataSource.DataSet.RecordCount-1 do
begin
sline := '';
for col := 0 to DBGrid.FieldCount-1 do
if Assigned(DBGrid.Fields[col]) then
if DBGrid.Fields[col].Visible then
sline := sline + DBGrid.Fields[col].AsString + #9;
mem.Add(sline);
DBGrid.DataSource.DataSet.Next;
end;
// we copy the data to the clipboard
Clipboard.AsText := mem.Text;
finally
mem.Free;
end;
// if needed, send it to Excel
// if not, we already have it in the clipboard
if toExcel then
begin
ExcelApp.Workbooks[1].WorkSheets['Grid Data'].Paste;
ExcelApp.Visible := true;
end;
finally
DBGrid.DataSource.DataSet.GotoBookmark(bm);
DBGrid.DataSource.DataSet.EnableControls;
Screen.Cursor := crDefault;
end;
end;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.