Link to home
Start Free TrialLog in
Avatar of evansj
evansj

asked on

TDBGrid contents to Clipboard

How do you code a CopyToClipboard function for the TDBGrid Component? I populate a tdbgrid from a query object, and would like to give the users the ability to copy the grid contents to the clipboard for pasting in Excel spreadsheet.
Avatar of Greedy
Greedy

I was playing with it and this is what I came up with as an exceptable way to do it...but I'm not calling it the answer because I know there is a better way I just can't think on it right now.

this requires you to add a editbox componet to the form...set the visible proporty to false...or just create it dynamicaly.

then to get the text to the clip board do a

  Edit1.Text := DBGrid1.SelectedField.AsString;
  Edit1.SelectAll;
  Edit1.Perform(WM_COPY, 0, 0);

you could also handle memo fields by maping the text into a memo if that was the type of field selected

  if DBGrid1.SelectedField is TMemoField then
  begin
    Memo1.Text := DBGrid1.SelectedField.AsString;
    Memo1.SelectAll;
    Memo1.Perform(WM_COPY, 0, 0);
  end
  else
  begin
    Edit1.Text := DBGrid1.SelectedField.AsString;
    Edit1.SelectAll;
    Edit1.Perform(WM_COPY, 0, 0);
  end;

if you had more than one field that you wanted to copy in you could do it by staging the data into one of these components one by one maby seperating them with commas so that it will import into excel nicly.

well hope this kinda helps...in reality this has the potential to be better than what I was thinking on in the first place...

Avatar of evansj

ASKER

How would you select particular rows?
Hi evansj,

I remember of a component that may help you. That is its description:

"T(Adv)Excel is a free DDE component to talk to
Microsoft Excel. DDE is used for maximum data
transfer speed.


This component pack contains code for EXTREMELY FAST transfer
speed (like 1000+ elements in 150 milliseconds (!) or better.

The high-speed code used in this component is by far the
fastest way (known to me) to push arbitrary data into
Excel and to get data from Excel, some orders of magnitude
faster than OLE automation and a lot easier to use."

You can find it at:

http://sunsite.icm.edu.pl/delphi/ftp/d10free/excel.zip

If you like it, tell me so I can put this comment as an answer.

IHTH,
Itamar


you would have to iterate them one at a time...like this

  Edit1.Text := '';
  For I := 0 to (DBGrid1.FieldCount - 1) do
  begin
    if DBGrid1.Fields[I] is TStringField then
      Edit1.Text := Edit1.Text + ',' + DBGrid1.Fields[I].Value;
    if DBGrid1.Fields[I] is TIntegerField then
      Edit1.Text := Edit1.Text + ',' + IntToStr(DBGrid1.Fields[I].Value);
    if DBGrid1.Fields[I] is TDateField then
      Edit1.Text := Edit1.Text + ',' + FormatDateTime('mm/dd/yyyy', DBGrid1.Fields[I].Value);
  end;


Avatar of evansj

ASKER

Adjusted points to 75
you want to copy it to Excel?
In the DBGrid:Options  set on the MultiSelect and write the following:

procedure TForm1.Button1Click(Sender: TObject);
{Copying data from a grid to the clipboard}
var
  MemoryBlock: pChar;
  I: Word;
  J: Byte;
  BlockSize:LongInt;
  MemoryHandle: THandle;
begin
  Screen.Cursor:=crHourGlass;
  I:=0;

{Calculating the length of the memory block which equals to total length of all
records in the grid + places for tabs and linefeeds}
with DBGrid1 do
begin
    for J:=0 to Columns.Count-1 do
      Inc(I, Columns[J].Field.Size+1);//1 for the tab
    Inc(I);//1 for the carriage return
    BlockSize:=SelectedRows.Count*I+1;//1 for the null
    MemoryHandle:=GlobalAlloc(GHND, BlockSize);
    MemoryBlock:=GlobalLock(MemoryHandle);

{Entering characters from grid to memory block. Tab is Entered between two fields}
  for I:=0 to SelectedRows.Count-1 do
  begin
     DataSource.DataSet.Bookmark:=SelectedRows.Items[I];
     for J:=0 to Columns.Count-1 do
     if Fields[J].Value<>null then
     begin
         StrCat(MemoryBlock,pChar(Fields[J].DisplayText));
          if J<Columns.Count-1 then
            StrCat(MemoryBlock,#9)
          else
            StrCat(MemoryBlock,#13);
      end;
   end;
end;
{Copying memory block to the clipboard and freeing resources}
GlobalUnlock(MemoryHandle);
OpenClipboard(Self.Handle);
EmptyClipboard;
SetClipboardData(CF_TEXT,MemoryHandle);
CloseClipboard;
Screen.Cursor:=crDefault;
end;

Avatar of evansj

ASKER

The DBGrid component does not have a multiselect property.
aaaaa...yeah it does...it's under options. called dgMultiSelect.

to programaticaly include this option do a

DBGrid1.Options := DBGrid1.Options + [dgMultiSelect];


Avatar of evansj

ASKER

To ronit: the code produces access violations. It will copy 2 rows w/garbage to the clipbrd. You may be on to something though, if we can get past the access violations.
thanks to Greedy also.
Avatar of evansj

ASKER

Also if we can't get a good way to copy a dbgrid to clipboard, how would you populate a grid with data from a SQL database (Oracle)? I think the standard grid has clipboard copy built in, or is it the string grid?
Avatar of evansj

ASKER

Adjusted points to 100
My code works perfectly for a long long time. Post your email and I'll send it to you.
What version of delphi are you using?
Avatar of evansj

ASKER

To ronit:
I'm using the client/server version 3.0. My e-mail address is joe_e_evans@notes.seagate.com
I run on a 200MHZ Pentium (non-MMX) with 128MB ram, Seagate Tape drive, 24x CD-ROM,
four Seagate 4-GB hard drives, Matrox Millineum Video Card w/4MB WRAM. May or may not be pertinent.

Thanks :-)
Avatar of evansj

ASKER

to ronit:

I checked the code at home on my other work machine and it works. Resubmit your answer for your deserved points! I would appreciate possible pointers for why my work machine (at work) gets access violations when running the code.
ASKER CERTIFIED SOLUTION
Avatar of ronit051397
ronit051397

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of evansj

ASKER

To ronit:
It bombs out right away. But when you do a paste to Excel you get two rows of data and garbage. What could be causing the access violation.
I have changed the code for your purpose and forgot to handle Null fields, that's why you got the garbage (I assume), so here is the fixed code.

I tested it in Excel 5 & 7 and didn't get any Access Violation. Where do you get this error, in Delphi or Excel?
If in Delphi, on which line in the code?

procedure TForm1.Button1Click(Sender: TObject);
var
  MemoryBlock: pChar;
  I: Word;
  J: Byte;
  BlockSize:LongInt;
  MemoryHandle: THandle;
begin
  Screen.Cursor:=crHourGlass;
  I:=0;
  with DBGrid1 do
  begin
    DataSource.DataSet.DisableControls;
    for J:=0 to Columns.Count-1 do
      Inc(I, Columns[J].Field.Size+1);
    BlockSize:=SelectedRows.Count*I+1;//1 for the null
    MemoryHandle:=GlobalAlloc(GHND, BlockSize);
    MemoryBlock:=GlobalLock(MemoryHandle);

    for I:=0 to SelectedRows.Count-1 do
    begin
      DataSource.DataSet.Bookmark:=SelectedRows.Items[I];
      for J:=0 to Columns.Count-1 do
      begin
        if Fields[J].Value<>null then
          StrCat(MemoryBlock,pChar(Fields[J].DisplayText))
        else
          StrCat(MemoryBlock,' ');
        if J<Columns.Count-1 then
          StrCat(MemoryBlock,#9)
        else
          StrCat(MemoryBlock,#13);
      end;
    end;
    StrCat(MemoryBlock,#0);
    DataSource.DataSet.EnableControls;
  end;
  GlobalUnlock(MemoryHandle);
  OpenClipboard(Self.Handle);
  EmptyClipboard;
  SetClipboardData(CF_TEXT,MemoryHandle);
  CloseClipboard;
  Screen.Cursor:=crDefault;
end;

Avatar of evansj

ASKER

to ronit:
I get the access violations in Delphi when I select the rows and then click my copy clipboard button. Your new code snippet doesn't give me access violations on some things it used to, but when I query the database, I get several rows back, but only 1 and about 5/7 rows get pasted into excel. The version of excel is the one with Office 97.  And now I get access violations on things I didn't use to.
Avatar of evansj

ASKER

to ronit:
I ran the code again and stepped through. It does everyhting fine until it executes the "end;" statement. When it does work it returns two rows with garbage. Part of the garbage points to  the path of my virus scanner.  I ran your test app, and on intrabase no errors, but on Oracle, I got the access violations. Not all the time.
Access Violation: The only thing I can think of is that the buffer (MemoryBlock) allocated is too small for the data selected, therefore try to increase it.
Yes, you probabely get the Access Violation because you have many fields of the INTEGER type, therefore, when allocating the buffer, the length of the actual data (number size) is bigger then the field size, so the buffer is too small to store all the data.

Change the line:    Inc(I, Columns[J].Field.Size+1);

to something like :

if Columns[J].Field.DataType=ftInteger then
   Inc(I,10+1)
else
  Inc(I, Columns[J].Field.Size+1);

Do it for other fields that are not string: FLOAT, WORD etc.
Avatar of evansj

ASKER

to ronit:
thanks for responding. Here is something interesting about this situation. I was looking through my Mastering Delphi 3 book and found a code snippet for copying to clipboard. He used the following:
HMem:=GlobalAlloc(GHND,25 * sizeof(Integer)

So I changed the code to this:
MemoryHandle:=GlobalAlloc(GHND,25 * BlockSize).  It works! Don't know why though. Is it related to your response about integers? The fields are calculated fields that count the average of the sum of days in most of the fields, plus a count of items field. However, they are floats. I have 9 numeric fields and three string fields. I appreciate you sticking with me on this.
Yes, it probably works because you increased the buffer size, either by this way or another. Happy for you. Bye.