3
votes

I want to create windows GUI application using Lazarus that able to drag file from explorer to the TEdit widget and show the file path.

I had read and tried some delphi tutorials, it said that you need to handle the WM_DROPFILES message, but I still can't get it works. So I'm thinking if I should try the simple way first by making application that able to drag file to TForm instead.

So I followed this example, but it doesn't work too.

Here is the full code:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Windows, Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
  ShellAPI;

type

  { TForm1 }

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  protected
      procedure WMDropFiles(var Msg: TMessage); message WM_DROPFILES;
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  DragAcceptFiles(self.Handle, True);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DragAcceptFiles(self.Handle, False);
end;

procedure TForm1.WMDropFiles(var Msg: TMessage);
begin
  ShowMessage('hello');  // never gets called
end;

end. 

The TForm1.FormCreate and TForm1.FormDestroy are working fine but the TForm1.WMDropFiles method never gets called.

Anyone know the solution? Could be the Lazarus/Free-Pascal windows library behavior differs from Delphi's ?

FYI, I'm using lazarus-1.6.0-fpc-3.0.0-win32 on Windows 7 64 bit.

3
It is probably number 2. - Hans Passant
Nope, I have checked the lazarus app process has medium priority same as the explorer process. I also made c++ app that has same functionality and it works fine. - null
It's a big mistake to rely on the form's window not being recreated. Create a window handle whose life you control. AllocateHWnd in Delphi. - David Heffernan
@DavidHeffernan: correct me if I'm wrong, it seems AllocateHWnd doesn't exist in Lazarus. - null
@david: No, I know what it means. Thanks. - null

3 Answers

1
votes

DragAcceptFiles is not true (for Lazarus), since it is a platform-dependent code ))

There is the correct cross-platform code: OnDropFiles - Only works with dock icon, not with Application Form

He doesn't use "Windows", "Messages" and "ShellAPI".

1 Set property "AllowDropFiles" of MainForm to True;

2 Declaration of the procedure:

  type
  { TMainForm }
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    procedure OnApplicationDropFiles(Sender: TObject; const FileNames: array of String);
  public  
  end;

3 The procedure:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Application.AddOnDropFilesHandler(@OnApplicationDropFiles);
end;  

procedure TMainForm.OnApplicationDropFiles(Sender: TObject; const FileNames: array of String);
begin
  ShowMessage('Files dropped');
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  Application.RemoveOnDropFilesHandler(@OnApplicationDropFiles);
end;
1
votes

Below answer solely elaborates on Win32WidgetSet which the question is apparently about.

First, as an answer to the question asked, the reason why WM_DROPFILES handler of the form is not called is that, simply the message is not delivered to the respective window procedure of the control class that the message is sent. LCL code is selective in what messages are delivered. Some details are in next paragraph, safe to skip...


DispatchMessageTWin32WidgetSet.AppProcessMessagesWindowProcWindowProcHelperDoWindowProcDeliverMessageWM_DROPFILESDeliverMessageTControl.WndProcTWinControl.WndProc

Second, to achieve the desired behavior of handling dropped files on the edit control, one obvious solution, mentioned in so many places - even in lazarus' documentation as linked in a comment to the question, is to subclass the window of the control. Your subclass will be delivered the message before LCL has a chance to handle it, hence you can act on the message.

But, once you trace the code in TWindowProcHelper.HandleDropFiles in "win32callback.inc", it becomes apparent how easy it would be to set up the special handling of WM_DROPFILES in LCL so that only the edit control handles dropped files. Normally this is for handling at the form level as already mentioned in a previous answer, but a form also actually acts on messages received on behalf of its children.

No need to go into specifics as it is just implementation detail and I don't know if it is intended but, set AllowDropFiles of the form to true, and then in the OnCreate handler of the form, unregister the form as a drop target (which is automatically registered) and register the edit.

procedure TForm1.FormCreate(Sender: TObject);
begin
  AllowDropFiles:= True;
  DragAcceptFiles(Handle, False);
  DragAcceptFiles(Edit1.Handle, True);
end;

Only the edit will accept files, but you'd handle it still on the form's event handler.

procedure TForm1.FormDropFiles(Sender: TObject; const FileNames: array of String);
begin
  if Length(FileNames) > 0 then
    Edit1.Text := FileNames[0];
end;


It is also possible to use Application.OnDropFiles after the same setup, but I don't see any advantage over the previous method.

0
votes
procedure TForm1.FormDropFiles(Sender: TObject; const FileNames: array of String);
var i   : Integer;
    aTxt: String;
begin
    showmessage('oh it works, this is filename #1 ' + filenames[0])
end;