I'm trying to follow the instructions here, but only accept dragged files in a specific ListView
as opposed to the whole form:
http://delphidabbler.com/articles?article=11
I have this in the FormCreate
procedure:
DragAcceptFiles(CustomAppsListView.Handle, True);
This in the private declarations:
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
And basically the exact same code for the WMDropFiles
example, with some DebugStrings:
procedure TMainFrm.WMDropFiles(var Msg: TWMDropFiles);
var
DropH: HDROP; // drop handle
DroppedFileCount: Integer; // number of files dropped
FileNameLength: Integer; // length of a dropped file name
FileName: string; // a dropped file name
I: Integer; // loops thru all dropped files
DropPoint: TPoint; // point where files dropped
begin
inherited;
// Store drop handle from the message
DropH := Msg.Drop;
try
OutputDebugString(PChar('Entered Try'));
// Get count of files dropped
DroppedFileCount := DragQueryFile(DropH, $FFFFFFFF, nil, 0);
// Get name of each file dropped and process it
for I := 0 to Pred(DroppedFileCount) do
begin
// get length of file name
FileNameLength := DragQueryFile(DropH, I, nil, 0);
// create string large enough to store file
// (Delphi allows for #0 terminating character automatically)
SetLength(FileName, FileNameLength);
// get the file name
OutputDebugString(PChar(FileName));
DragQueryFile(DropH, I, PChar(FileName), FileNameLength + 1);
// process file name (application specific)
// ... processing code here
end;
// Optional: Get point at which files were dropped
DragQueryPoint(DropH, DropPoint);
// ... do something with drop point here
finally
// Tidy up - release the drop handle
// don't use DropH again after this
DragFinish(DropH);
end;
// Note we handled message
Msg.Result := 0;
end;
When I drag a file over, I see the cursor switch to the "drag accept" cursor, but when I drop I'm not seeing any file names come out. In fact, I don't think I'm entering this message handling block of code at all. Is there something else I need to do or add, maybe to the ListView
itself to get this to fire?
You are registering the ListView's window to receive the messages, but then you are trying to handle them in the MainForm. That is why you never see them - they are not sent to the MainForm's window. You need to subclass the ListView's WindowProc
property to receive the messages that are sent to the ListView's window.
You also need to take into account that VCL windows are not persistent. The ListView's HWND
is most likely going to be recreated dynamically at least once during its lifetime, thus losing your DragAcceptFiles()
registration. That is another reason to subclass the ListView's WindowProc
property, so you can re-register every time the ListView's window gets recreated.
A better option is to derive a new component (or at least an interceptor class) from TListView
and have it override the virtual CreateWnd()
and DestroyWnd()
methods to handle the registration:
unit MainForm;
interface
uses
...;
type
TListViewDroppedFilesEvent = procedure(Sender: TObject; Files: TStrings; const DropPt: TPoint) of object;
TListView = class(ComCtrls.TListView)
private
FOnDroppedFiles: TListViewDroppedFilesEvent;
procedure SetOnDroppedFiles(Value: TListViewDroppedFilesEvent);
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
protected
procedure CreateWnd; override;
procedure DestroyWnd; override;
public
property OnDroppedFiles: TListViewDroppedFilesEvent read FOnDroppedFiles write SetOnDroppedFiles;
end;
TMainForm = class(TForm)
CustomAppsListView: TListView;
...
private
procedure CustomAppsListViewDroppedFiles(Sender: TObject; Files: TStrings; const DropPt: TPoint);
...
end;
...
implementation
procedure TListView.CreateWnd;
begin
inherited;
if Assigned(FOnDroppedFiles) then DragAcceptFiles(Handle, True);
end;
procedure TListView.DestroyWnd;
begin
DragAcceptFiles(Handle, False);
inherited;
end;
procedure TListView.WMDropFiles(var Msg: TWMDropFiles);
var
DropH: HDROP; // drop handle
DroppedFileCount: Integer; // number of files dropped
FileNameLength: Integer; // length of a dropped file name
FileName: string; // a dropped file name
I: Integer; // loops thru all dropped files
DropPoint: TPoint; // point where files dropped
Files: TStringList;
begin
inherited;
if not Assigned(FOnDroppedFiles) then Exit;
// Store drop handle from the message
DropH := Msg.Drop;
try
// Get count of files dropped
DroppedFileCount := DragQueryFile(DropH, $FFFFFFFF, nil, 0);
Files := TStringList.Create;
try
// Get name of each file dropped and process it
for I := 0 to Pred(DroppedFileCount) do
begin
// get length of file name
FileNameLength := DragQueryFile(DropH, I, nil, 0);
// create string large enough to store file
// (Delphi allows for #0 terminating character automatically)
SetLength(FileName, FileNameLength);
// get the file name
OutputDebugString(PChar(FileName));
DragQueryFile(DropH, I, PChar(FileName), FileNameLength + 1);
Files.Add(FileName);
end;
// Optional: Get point at which files were dropped
DragQueryPoint(DropH, DropPoint);
FOnDroppedFiles(Self, Files, DropPoint);
finally
Files.Free;
end;
finally
// Tidy up - release the drop handle
// don't use DropH again after this
DragFinish(DropH);
end;
// Note we handled message
Msg.Result := 0;
end;
procedure TListView.SetOnDroppedFiles(Value: TListViewDroppedFilesEvent);
begin
if (TMethod(FOnDroppedFiles).Code <> TMethod(Value).Code) or
(TMethod(FOnDroppedFiles).Data <> TMethod(Value).Data) then
begin
FOnDroppedFiles := Value;
if HandleAllocated then
DragAcceptFiles(Handle, Assigned(FOnDroppedFiles));
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
CustomAppsListView.OnDroppedFiles := CustomAppsListViewDroppedFiles;
end;
procedure TMainForm.CustomAppsListViewDroppedFiles(Sender: TObject; Files: TStrings; const DropPt: TPoint);
begin
// process Files (application specific)
// ... processing code here
// ... do something with DropPt here
end;
...
end.