Search code examples
delphidrag-and-dropvcl

Not seeing TWMDropFiles after file drop into ListView


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?


Solution

  • 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.