Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle file drop in TShellListView descendant

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.
like image 235
Larry Lustig Avatar asked Jan 18 '13 17:01

Larry Lustig


2 Answers

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.

like image 183
Sertac Akyuz Avatar answered Nov 10 '22 03:11

Sertac Akyuz


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.

like image 34
Remy Lebeau Avatar answered Nov 10 '22 05:11

Remy Lebeau