Watch, Follow, &
Connect with Us

Please visit our new home
community.embarcadero.com.


Welcome, Guest
Guest Settings
Help

Thread: Dragging Files from Explorer to Application Main Form


This question is not answered. Helpful answers available: 2. Correct answers available: 1.


Permlink Replies: 8 - Last Post: Oct 12, 2017 6:53 AM Last Post By: Kenneth Norrie
Jeff Cope

Posts: 88
Registered: 1/27/01
Dragging Files from Explorer to Application Main Form  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 13, 2016 12:09 PM
We would like to be able to drag files from explorer onto our main form. There are several examples of doing just this but all of them are for versions up through Delphi 7.

This is the most comprehensive example we found but it doesn't work in XE7:
[http://delphidabbler.com/articles?article=11]

This one used the WM_DROPFILES message defined in winapi.messages. the DragAccept files method is called where the main form handle is passed in to allow drag/drop. When you run and try to drag a file it acts like the file is allowed to drop but once you do the message never gets called.

Is there another way to accomplish dragging files into the application? Or perhaps there's a windows limitation (windows 8 in our case) that's preventing it from working? We've tried elevated permissions but that didn't solve it.
Remy Lebeau (Te...


Posts: 9,064
Registered: 12/23/01
Re: Dragging Files from Explorer to Application Main Form  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 13, 2016 1:37 PM   in response to: Jeff Cope in response to: Jeff Cope
Jeff wrote:

We would like to be able to drag files from explorer onto our main
form. There are several examples of doing just this but all of them
are for versions up through Delphi 7.

The same techniques still apply in modern Delphi versions. You must implement
a WM_DROPFILES message handler (the deprecated way) or the IDropTarget COM
interface (the preferred way).

This is the most comprehensive example we found but it doesn't work
in XE7: [http://delphidabbler.com/articles?article=11]

That is not even close to being the "most comprehensive" example available.
It only covers WM_DROPFILES, but not IDropTarget at all.

This one used the WM_DROPFILES message defined in winapi.messages.
the DragAccept files method is called where the main form handle is
passed in to allow drag/drop.

Make sure that you are either:

1. overriding the Form's virtual CreateWnd() method to call DragAcceptFiles().

2. overriding the Form's virtual CreateParams() method to enable the WS_EX_ACCEPTFILES
window style.

Whenever the Form's HWND is (re-)created at runtime, WM_DROPFILES has to
be (re-)enabled on the new HWND.

When you run and try to drag a file it acts like the file is allowed to
drop
but once you do the message never gets called.

The message is being blocked by User Interface Privilege Isolation (UIPI).
Explorer runs as a low-integrity process. UIPI prevents a lower-integrity
process from sending window messages to a higher-integrity process, unless
the higher-integrity process explicitly allows the message by calling ChangeWindowMessageFilter/Ex().
In order for WM_DROPFILES to work properly, you need to allow the WM_DROPFILES,
WM_COPYDATA, and WM_COPYGLOBALDATA messages (WM_COPYGLOBALDATA is undocumented
and undefined in Dephi, but its value is $0049).

You also need to do this if your app is run elevated and you want to allow
non-elevated apps to drag&drop files to you.

Is there another way to accomplish dragging files into the application?

Yes. Create an object that implements IDropTarget and pass it to RegisterDragDrop().
WM_DROPFILES has been deprecated for a LONG time, you really should not
be using it anymore. All modern drag&drop operations in Windows are handled
via the IDragSource, IDropTarget, and IDataObject interfaces nowadays. For
backwards compatibility, if an IDragSource app provides an IDataObject containing
a DROPFILES structure, and the target drop window does not implement IDropTarget
but does enable WM_DROPFILES, Windows will extract the DROPFILES and simulate
WM_DROPFILES with it. So you should cut out the middle-man and implement
IDropTarget yourself.

The easiest and most popular implementation of OLE Drag&Drop for Delphi/C++Builder
Anders Melander's Drag&Drop suite:

http://melander.dk/delphi/dragdrop/

--
Remy Lebeau (TeamB)
Jeff Cope

Posts: 88
Registered: 1/27/01
Re: Dragging Files from Explorer to Application Main Form  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 14, 2016 1:52 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thank you for the reply.

or the IDropTarget COM interface (the preferred way).
We have been trying to create a sample application implementing IDropTarget but have been unsuccessful. Using the old method of implementing WM_DROPFILES allowed us to drag files from explorer to the memo box and the file name would appear.

I submitted a sample program to the attachments forum:
[Sample Application Implementing IDropTarget|https://forums.embarcadero.com/thread.jspa?threadID=223546&stqc=true]

Do you have any suggestion for what we are doing wrong with the sample application?

Your help is greatly appreciated.
Remy Lebeau (Te...


Posts: 9,064
Registered: 12/23/01
Re: Dragging Files from Explorer to Application Main Form  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jul 14, 2016 2:49 PM   in response to: Jeff Cope in response to: Jeff Cope
Jeff wrote:

We have been trying to create a sample application implementing
IDropTarget but have been unsuccessful.

What kind of problems are you having? It is not a trivial interface to implement,
but it is also not a complex interface, either.

Have you tried Anders' component suite, like I suggested earlier? It handles
most of the complexities for you. The TDropFileTarget component will accept
dropped files for you.

Using the old method of implementing WM_DROPFILES allowed us to
drag files from explorer to the memo box and the file name would appear.

While WM_DROPFILES will "work" (UIPI permitting), it is limited to only accepting
dropping filenames of physical files. IDropTarget can handle those (and
CF_HDROP is not the only format that can drop files), and just about anything
else (virtual files, shell items, data streams, custom formats, etc).

Another benefit is once you have IDropTarget working, it is a small step
to enable the same IDropTarget object to handle additional shell operations,
like dropping things on your app's .exe file itself or its Taskbar buton
instead of your app's window directly.

Do you have any suggestion for what we are doing wrong with the
sample application?

As far as your IDropTarget implementation itself goes, it looks OK. But
I see several problems in how your code is managing the TDropTarget object
at runtime.

1) I would not suggest calling RegisterDragDrop() and RevokeDragDrop() from
inside of TDropTarget itself. Your TForm should be calling those instead.
Let CreateWnd() create the TDropTarget object THEN register it, and hav
DestroyWnd() revoke the object BEFORE destroying its window.

2) In addition to #1, your TForm's FDropTarget member is declared as TDropTarget,
but it needs to be declared as IDropTarget instead so the object's reference
count gets managed correctly. By declaring the variable as TDropTarget,
you are bypassing reference count management. I would also suggest making
FDropTarget be a local variable of CreateWnd() instead of a member of the
TForm at all. RegisterDragDrop() will increment the object's reference count
if successful, and RevokeDragDrop() will decrement it. You don't need to
maintain your own reference to the object since you are not accessing it
outside of CreateWnd() (except to destroy it in DestroyWnd(), which is the
wrong thing to do - let the reference count handle that for you).

constructor TDropTarget.Create(const ADragDrop: IDragDrop);
begin
  inherited Create;
  FDragDrop := ADragDrop;
end;
 
destructor TDropTarget.Destroy;
begin
  inherited;
end;
 
...
 
procedure TForm1.CreateWnd;
var
  LDropTarget: IDropTarget;
begin
  inherited;
  // ChangeWindowMessageFilterEx(Handle, WM_DROPFILES, MSGFLT_ALLOW, nil);
  // ChangeWindowMessageFilterEx(Handle, WM_COPYDATA, MSGFLT_ALLOW, nil);
  // ChangeWindowMessageFilterEx(Handle, WM_COPYGLOBALDATA, MSGFLT_ALLOW, 
nil);
  LDropTarget := TDropTarget.Create(Self) as IDropTarget;
  OleCheck(RegisterDragDrop(Handle, LDropTarget));
end;
 
procedure TForm1.DestroyWnd;
begin
  RevokeDragDrop(Handle);
  inherited;
end;


3) You also should not be calling OleInitialize() or OleUninitialize() in
your TDropTarget unit at all. That is the responsibility of individual threads
to call, and the VCL main thread also initializes OLE/COM at program startup
for you.

--
Remy Lebeau (TeamB)
Kenneth Norrie

Posts: 3
Registered: 3/16/00
Re: Dragging Files from Explorer to Application Main Form  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 10, 2017 11:25 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:

procedure TForm1.DestroyWnd;
begin
  RevokeDragDrop(Handle);
  inherited;
end;

Firstly, I'd like to thank both of you for this very helpful thread, and I hope that I'm not seen as hi-jacking it.

I have adapted the example code here with Remy's suggested changes to Delphi 2009, and further changed it so that the drop target is a TPanel instead of the form like this:

  TPanel = class(ExtCtrls.TPanel)
  private
    Target: IDropTarget;
    FDragDrop: IDragDrop;
  protected
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
  public
    procedure SetDragDrop(const ADragDrop: IDragDrop);
  end;


The SetDragDrop method is called in the form's constructor on the instance of the TPanel that I want the drag drop to happen on.

The actual drag/drop works fine. The problem is that the TPanel.DestroyWnd method is never called, so that the drag drop registration is never revoked with RevokeDragDrop, nor the TDropTarget object destroyed.

This might be OK for a simple form application, in that (I guess) the drag drop registration would be revoked when OleUninitialize is called when the main thread exits. However in our application we need to dynamically create and destroy several forms where drag/drop operations can happen, and this can be done by the user any number of times during the program's execution. So I'm concerned this might be a problematic memory and resource leak.

I have also debugged the SimpleDropTarget demo program in the Anders' component suite and this has exactly the same problem.

I can also see that the TPanel's DestroyWindowHandle is also never called, yet in the destructor the Handle is 0, so something somewhere has nuked the control's window.

Can anyone shed any light on this? Is it possibly a VCL bug in Delphi 2009?
Remy Lebeau (Te...


Posts: 9,064
Registered: 12/23/01
Re: Dragging Files from Explorer to Application Main Form [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 10, 2017 3:00 PM   in response to: Kenneth Norrie in response to: Kenneth Norrie
Kenneth Norrie wrote:

The problem is that the TPanel.DestroyWnd method is never called

DestroyWnd() is only called by certain operations during the controls's
lifetime (such as HWND recreation). It is not called when the control
is being destroyed.

You can override the virtual DestroyWindowHandle() method instead of
DestroyWnd(). But, as you noticed, it may not always be called, either.

you will likely have to handle the WM_DESTROY message directly.

I can also see that the TPanel's DestroyWindowHandle is also never
called, yet in the destructor the Handle is 0, so something somewhere
has nuked the control's window.

Chances are, the Panel received a WM_DESTROY message before it was
destructed. For instance, when the Form's HWND is destroyed, its HWND
and all of its (grand)child HWNDs receive WM_DESTROY.

--
Remy Lebeau (TeamB)
Kenneth Norrie

Posts: 3
Registered: 3/16/00
Re: Dragging Files from Explorer to Application Main Form [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 11, 2017 6:06 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Many thanks for your quick reply!

Remy Lebeau (TeamB) wrote:

DestroyWnd() is only called by certain operations during the controls's
lifetime (such as HWND recreation). It is not called when the control
is being destroyed.

Hmm, so if this is by design and not a Delphi 2009 limitation, and you can't rely on DestroyWnd being paired with CreateWnd at all, then it would seem to me that this is a serious bug in all of the Delphi drag drop code examples around, as well as the Drag and Drop Component suite.

you will likely have to handle the WM_DESTROY message directly.

OK thanks, I will try this.

Chances are, the Panel received a WM_DESTROY message before it was
destructed. For instance, when the Form's HWND is destroyed, its HWND
and all of its (grand)child HWNDs receive WM_DESTROY.

So are you saying that the VCL is destroying a control's window directly (e.g. with DestroyWindow) when it gets the WM_DESTROY message, instead of calling DestroyWindowHandle?
Remy Lebeau (Te...


Posts: 9,064
Registered: 12/23/01
Re: Dragging Files from Explorer to Application Main Form [Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 11, 2017 11:41 AM   in response to: Kenneth Norrie in response to: Kenneth Norrie
Kenneth Norrie wrote:

Hmm, so if this is by design and not a Delphi 2009 limitation

It is by design, and always has been, long before 2009.

and you can't rely on DestroyWnd being paired with CreateWnd at all,
then it would seem to me that this is a serious bug in all of the
Delphi drag drop code examples around, as well as the Drag and Drop
Component suite.

I would say so, yes.

So are you saying that the VCL is destroying a control's window
directly (e.g. with DestroyWindow) when it gets the WM_DESTROY
message, instead of calling DestroyWindowHandle?

No. When a TWinControl receives a WM_DESTROY message, the HWND is
already in progress of being destroyed, so the VCL simply sets the
TWinControl.Handle property to 0.

When a TWinControl object is being destroyed, if its Handle is still
valid, the destructor calls DestroyWindowHandle(), bypassing
DestroyWnd(). But, the HWND is usually 0 by this time (unless you
destroy the object directly in your own code), so even
DestroyWindowHandle() is not always called. That is why I suggested
handling WM_DESTROY directly to perform the revokation.

--
Remy Lebeau (TeamB)
Kenneth Norrie

Posts: 3
Registered: 3/16/00
Re: Dragging Files from Explorer to Application Main Form [Edit] [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Oct 12, 2017 6:53 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
I just wanted to let you know that your suggestion of handling the clean-up in the WM_DESTROY works well. The window handle is still valid, so the drag drop revocation can be performed, which in turn releases the associated IDropTarget object.

Many thanks again for your help with this!
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02