简体   繁体   中英

How to format rows and columns in clipboard to paste like coming from Excel?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM