Recent

Author Topic: [Solved] Clipboard Management in Lazarus  (Read 4665 times)

antekgla

  • New Member
  • *
  • Posts: 22
[Solved] Clipboard Management in Lazarus
« on: June 27, 2017, 03:09:15 am »
Hi,
I am trying to read the Clipboard in my Lazarus application and I'm in trouble.

First: my goal is read if a file(s)/folder(s) gets copy in another app and read the file/folder name(s) in my app.
The copy IS NOT a file name or folder name. It is the file o folder itself (by example in Windows Explorer, select a file and Crtl+C). Meaning what the Format of Clipboard would by CF_HDROP.

I find a procedure what work fantastic in Delphi but sadly not in Lazarus. The code is this:

Code: Pascal  [Select][+][-]
  1. procedure Sto_PasteFilenamesFromClipboard(Filenames: TStringlist);
  2. var
  3.   hDropHandle: HDROP;
  4.   szBuffer: PChar;
  5.   iCount, iIndex: Integer;
  6.   iLength: Integer;
  7. begin// check entry conditions
  8.   if (Filenames = nil) then
  9.     Exit;
  10.   Filenames.Clear;// lock clipboard
  11.   Clipboard.Open;
  12.   try// does clipboard contain filenames?
  13.     if (Clipboard.HasFormat(CF_HDROP)) then
  14.     begin// get drop handle from the clipboard
  15.       hDropHandle := Clipboard.GetAsHandle(CF_HDROP);// enumerate filenames
  16.       iCount := DragQueryFile(hDropHandle, $FFFFFFFF, nil, 0);
  17.       for iIndex := 0 to iCount - 1 do
  18.       begin// get length of filename
  19.         iLength := DragQueryFile(hDropHandle, iIndex, nil, 0);
  20.         // allocate the memory, the #0 is not included in "iLength"
  21.         szBuffer := StrAlloc(iLength + 1);
  22.         try// get filename
  23.           DragQueryFile(hDropHandle, iIndex, szBuffer, iLength + 1);
  24.           Filenames.Add(szBuffer);
  25.         finally// free the memory
  26.           StrDispose(szBuffer);
  27.         end;
  28.       end;
  29.     end;
  30.   finally// unlock clipboard
  31.     Clipboard.Close;
  32.   end;
  33. end;
  34.  

The first problem I find is:
Code: Pascal  [Select][+][-]
  1. hDropHandle := Clipboard.GetAsHandle(CF_HDROP)

because this method GetAsHandle not exist in Lazarus.
Not problem, I see the code in Delphi of GetsAsHandle and it is simple:

Code: Pascal  [Select][+][-]
  1. function TClipboard.GetAsHandle(Format: Word): THandle;
  2. begin
  3.   Open;
  4.   try
  5.     Result := GetClipboardData(Format);
  6.   finally
  7.     Close;
  8.   end;
  9. end;

so I replace with:
Code: Pascal  [Select][+][-]
  1. hDropHandle := GetClipboardData(CF_HDROP);

The problem is what that NEVER give the Handle of the Clipboard, always return 0.

Another problem I see debugging the code is this line:
Code: Pascal  [Select][+][-]
  1. if (Clipboard.HasFormat(CF_HDROP)) then

In Delphi if I copy a file(s) in Explorer the sentence is TRUE because the Clipboard has the format CF_HDROP, but I do the SAME in Lazarus and that sentence is always FALSE not matter what copy to the clipboard.

So I am a little lost...
Any help is welcome.
Thanks in advance.
« Last Edit: June 27, 2017, 04:50:45 pm by antekgla »

antekgla

  • New Member
  • *
  • Posts: 22
Re: Clipboard Management in Lazarus
« Reply #1 on: June 27, 2017, 07:40:23 am »
Well, apparently the problem was:

Code: Pascal  [Select][+][-]
  1. Clipboard.Open;
  :o

I comment that line and magically:

Code: Pascal  [Select][+][-]
  1. if (Clipboard.HasFormat(CF_HDROP)) then

works as intended.
The problem however still was:

Code: Pascal  [Select][+][-]
  1. hDropHandle := GetClipboardData(CF_HDROP);

still dont give the handle of the clipboard of the CF_HDROP format...
...but after read the Microsoft description of GetClipboardData there it says what the clipboard as to be open before use the function, so again Clipboard.Open dont work.
In replace OpenClipboard(0) do the trick and the handle was given.

Of course Clipboard.Close not work either, but CloseClipboard do the job.

So if anyone are interested the procedure is like this:

Code: Pascal  [Select][+][-]
  1. procedure Sto_PasteFilenamesFromClipboard(Filenames: TStringlist);
  2. var
  3.   hDropHandle: HDROP;
  4.   szBuffer: PChar;
  5.   iCount, iIndex: Integer;
  6.   iLength: Integer;
  7. begin// check entry conditions
  8.   if (Filenames = nil) then
  9.     Exit;
  10.   Filenames.Clear;
  11.   try// does clipboard contain filenames?
  12. if (Clipboard.HasFormat(CF_HDROP)) then
  13.     begin// get drop handle from the clipboard
  14.       OpenClipboard(0); // lock clipboard
  15.       hDropHandle := GetClipboardData(CF_HDROP);
  16.       iCount := DragQueryFile(hDropHandle, $FFFFFFFF, nil, 0);
  17.       for iIndex := 0 to iCount - 1 do
  18.       begin// get length of filename
  19.         iLength := DragQueryFile(hDropHandle, iIndex, nil, 0);
  20.         // allocate the memory, the #0 is not included in "iLength"
  21.         szBuffer := StrAlloc(iLength + 1);
  22.         try// get filename
  23.           DragQueryFile(hDropHandle, iIndex, szBuffer, iLength + 1);
  24.           Filenames.Add(szBuffer);
  25.         finally// free the memory
  26.           StrDispose(szBuffer);
  27.         end;
  28.       end;
  29.     end;
  30.   finally// unlock clipboard
  31.     CloseClipboard;
  32.   end;
  33. end;  

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1314
    • Lebeau Software
Re: Clipboard Management in Lazarus
« Reply #2 on: June 29, 2017, 01:53:42 am »
First: my goal is read if a file(s)/folder(s) gets copy in another app and read the file/folder name(s) in my app.
The copy IS NOT a file name or folder name. It is the file o folder itself (by example in Windows Explorer, select a file and Crtl+C). Meaning what the Format of Clipboard would by CF_HDROP.

No, it is NOT the actual file/folder(s) themselves, it is indeed just their file/folder path strings.  The CF_HDROP format consists of a DROPFILES structure that points to a double null-terminated list of file/folder location strings, nothing more.

By design, a pointer to a DROPFILES structure can be used as-is as an HDROP handle for the DragQuery...() API functions for easy access to the list values.  Or, you could just parse the string list directly.

See Shell Clipboard Formats | CF_HDROP for more details.

The first problem I find is:
Code: Pascal  [Select][+][-]
  1. hDropHandle := Clipboard.GetAsHandle(CF_HDROP)

because this method GetAsHandle not exist in Lazarus.

No, but it does have a GetFormat() method that reads the clipboard data into a TStream.

so I replace with:
Code: Pascal  [Select][+][-]
  1. hDropHandle := GetClipboardData(CF_HDROP);

The problem is what that NEVER give the Handle of the Clipboard, always return 0.

Another problem I see debugging the code is this line:
Code: Pascal  [Select][+][-]
  1. if (Clipboard.HasFormat(CF_HDROP)) then

In Delphi if I copy a file(s) in Explorer the sentence is TRUE because the Clipboard has the format CF_HDROP, but I do the SAME in Lazarus and that sentence is always FALSE not matter what copy to the clipboard.

I can't find FreePascal's source code for TClipboard, but it sounds like TClipboard.Open() is clearing the current clipboard content (by calling EmptyClipboard()).  In Delphi, TClipboard.Open() does not do that.

Well, apparently the problem was:

Code: Pascal  [Select][+][-]
  1. Clipboard.Open;
  :o

I comment that line and magically:

Code: Pascal  [Select][+][-]
  1. if (Clipboard.HasFormat(CF_HDROP)) then

works as intended.

That would fall inline with TClipboard.Open() clearing the clipboard.  The clipboard does not need to be opened before querying/enumerating it for available formats, only for reading/writing format data.

The problem however still was:

Code: Pascal  [Select][+][-]
  1. hDropHandle := GetClipboardData(CF_HDROP);

still dont give the handle of the clipboard of the CF_HDROP format...

That would fall inline with TClipboard.Open() clearing the clipboard.

So if anyone are interested the procedure is like this:

You shouldn't be calling CloseClipboard() unless OpenClipboard() is successful first.  Move your calls to Clipboard.HasFormat() and OpenClipboard() out of your try/finally block:

Code: Pascal  [Select][+][-]
  1. procedure Sto_PasteFilenamesFromClipboard(Filenames: TStrings);
  2. var
  3.   hDropHandle: HDROP;
  4.   szBuffer: PChar;
  5.   iCount, iIndex: Integer;
  6.   iLength: Integer;
  7. begin// check entry conditions
  8.   if (Filenames = nil) then
  9.     Exit;
  10.   Filenames.BeginUpdate;
  11.   try
  12.     Filenames.Clear;
  13.     // does clipboard contain filenames?
  14.     if not Clipboard.HasFormat(CF_HDROP) then
  15.       Exit;
  16.     if not OpenClipboard(0) then // lock clipboard
  17.       Exit; // or raise an exception
  18.     try
  19.       // get drop handle from the clipboard
  20.       hDropHandle := GetClipboardData(CF_HDROP);
  21.       if hDropHandle = 0 then
  22.         Exit; // or raise an exception
  23.       iCount := DragQueryFile(hDropHandle, $FFFFFFFF, nil, 0);
  24.       for iIndex := 0 to iCount - 1 do
  25.       begin
  26.         // get length of filename
  27.         iLength := DragQueryFile(hDropHandle, iIndex, nil, 0);
  28.         // allocate the memory, the #0 is not included in "iLength"
  29.         szBuffer := StrAlloc(iLength + 1);
  30.         try
  31.           // get filename
  32.           if DragQueryFile(hDropHandle, iIndex, szBuffer, iLength + 1) > 0 then
  33.             Filenames.Add(szBuffer);
  34.         finally
  35.           // free the memory
  36.           StrDispose(szBuffer);
  37.         end;
  38.       end;
  39.     finally
  40.       // unlock clipboard
  41.       CloseClipboard;
  42.     end;
  43.   finally
  44.     Filenames.EndUpdate;
  45.   end;
  46. end;
  47.  
« Last Edit: June 29, 2017, 02:37:13 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

 

TinyPortal © 2005-2018