问题
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:
I see the "not accepted" icon (circle w/ slash) rather than an indication that I can drop the file.
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.
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/14404782/handle-file-drop-in-tshelllistview-descendant