7

I am attempting to create a descendant of TShellListView that accepts files dropped from Windows Explorer. I want to handle the drag/drop in my component definition without having to implement it in any of the applications that use the component (I've found examples of accepting files dropped from Windows Explorer, but all at the application / TForm level).

I'm calling DragAcceptFiles() in my constructor, and I've defined a message handler for WM_DROPFILES. However, when I use this component in a sample project and drag a file from Windows Explorer:

  1. I see the "not accepted" icon (circle w/ slash) rather than an indication that I can drop the file.

  2. If I do attempt to drop the file, I do not hear the Beep().

I presume that I'm not properly alerting Windows to the fact that my control would like to accept dragged files. Can anyone suggest what I'm missing?

Here's my component code with the uninteresting bits removed:

    unit LJLShellListView;

    interface

    type

      TLJLShellListView = class(TShellListView)
      private
        procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
      published
        constructor Create(AOwner: TComponent);
      end;

    implementation

    uses ShellAPI;

    constructor TLJLShellListView.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      DragAcceptFiles(self.Handle, True);
    end;

    procedure TLJLShellListView.WMDropFiles(var Msg: TWMDropFiles);
    begin
      Beep();  // I never hear this.
    end;

    end.
Larry Lustig
  • 49,320
  • 14
  • 110
  • 160
  • 1
    Maybe the control is getting recreated and the handle is changing? – Sertac Akyuz Jan 18 '13 at 18:09
  • @Sertac: Does a HandleNeeded call prevent that? – jachguate Jan 18 '13 at 18:12
  • 5
    @jachguate - No, it would have the exact same effect with accessing the handle. Moving the 'DragAcceptFiles' line to an overriden CreateWnd after calling it after inherited would be the solution. – Sertac Akyuz Jan 18 '13 at 18:13
  • @Sertac You shall post that as an answer! – jachguate Jan 18 '13 at 18:23
  • @SertacAkyuz: Moving to CreateWnd solved the problem. Please post as an answer and, if you have a moment, add a short explanation of why this can't be done after inherited Create(). – Larry Lustig Jan 18 '13 at 18:37
  • CreateWnd will do it Setparent won't ... :-( – bummi Jan 18 '13 at 19:04
  • 1
    @LarryLustig: the `HWND` provided by the `TWinControl.Handle` property is **not** persistent. It can get destroyed and recreated during a component's lifetime, even multiple times. Every time the `TShellListView` gets a new `HWND` assigned, you have to call `DragAcceptFiles(TRUE)` again. Overriding `CreateWnd()` allows you to do that. For good measure, you should also override `DestroyWnd()` to call `DragAcceptFiles(FALSE)` before the `HWND` is destroyed. – Remy Lebeau Jan 18 '13 at 19:07
  • Merci @Remy. Is this a correct restatement of what you said: Delphi creates the underlying Windows control (and gets a different handle) several times, but it will call CreateWnd() internally each time it does so? – Larry Lustig Jan 18 '13 at 19:17
  • @Larry In fact, the CreateWnd is the call who gets the new handle, and DestroyWnd is the call that frees it. – jachguate Jan 18 '13 at 19:24
  • 1
    @jachguate: to be more accurate, `CreateHandle()` is called first, which calls `CreateWnd()`, which calls `CreateWindowHandle()` to create the actual `HWND`. Later on, `DestroyHandle()` is called, which calls `DestroyWnd()`, which calls `DestroyWindowHandle()` to destroy the `HWND`. And there are some cases where `DestroyWindowHandle()` is called directly without calling `DestroyWnd()`, like in the `TWinControl` destructor. – Remy Lebeau Jan 18 '13 at 19:35

2 Answers2

8

The DragAcceptFiles call, in the code in the question, requires the window handle of the ShellListView. When code accesses the handle of a wincontrol, the VCL checks if the window has been created or not, and if not, VCL calls CreateHandle and proceeds with creating the window and giving back its handle. At this stage, the code in the question successfully registers the ShellListView's window for file drag and drop. But there's a problem, the control is not parented yet. When it is going to be parented, the native control will be destroyed and then created anew in its new parent and of course will acquire a different handle, invalidating the registered state.

A control might get recreated for various other reasons too. For this reason, it is better to put our code in overriden CreateWnd and DestroyWnd methods, whenever the handle changes or the window is about to be destroyed, our code will run.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Thank you. The fact that the native windows control has an existence completely distinct from the VCL control is news to me. Your explanation made it possible for me to understand (at least, enough) what's happening internally. – Larry Lustig Jan 21 '13 at 15:26
3

As @SertacAkyuz said, the solution to your question is to override the CreateWnd() method to call DragAcceptFiles() instead of doing so in the constructor. I just want to mention that under Windows Vista and later, User Interface Privilege Isolation (UIPI) takes effect, so if your app is running in an elevated state under UAC then you will also need to call ChangeWindowMessageFilter() to allow WM_COPYGLOBALDATA ($0049) and WM_DROPFILES messages to be delivered to your app from lower-privileged processes, like Windows Explorer, or else the drag-and-drop will not work correctly.

WM_DROPFILES has been deprecated for a long time in favor of the newer IDropTarget interface, which is much more powerful and flexible. New code should use IDropTarget instead of WM_DROPFILES. Refer to MSDN for more details:

Transferring Shell Objects with Drag-and-Drop and the Clipboard

One of the flexible features that IDropTarget offers that WM_DROPFILES does not is the ability to use a single IDropTarget object to accept drag-and-drop not only on a specific HWND, but also on your app's .exe file itself, as well as using it in Shell popup menus, and even to allow other apps to pass data directly to your app without using window messages or other IPC mechanisms.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    My answer here may help with IDropTarget: http://stackoverflow.com/questions/4354071/how-can-i-allow-a-form-to-accept-file-dropping-without-handling-windows-messages#4354441 – David Heffernan Jan 18 '13 at 19:51
  • Thanks for the suggestions on IDropTarget. I may re-implement this control in the future using this technique rather than WM_DROPFILES. For my immediate case, however, in which I'm creating several instances of TShellListView and having the user "sort" a bunch of incoming files, WM_DROPFILES has all the functionality I need. – Larry Lustig Jan 19 '13 at 14:23