Как в приложении получить список файлов, переданных из проводника посредством Drag-and-Drop

в 17:56, , рубрики: Delphi

О чем данная статья:

Множество программ позволяют пользователю открывать файлы посредством перетаскивания их из проводника в окно приложения. Как правило это более удобно для пользователя, в отличии от стандартной модели открытия посредством выбора меню «Файл -> Открыть» или нажатия соответствующей кнопки на панели инструментов, так как в данном случае пользователь пропускает этап работы с диалоговым окном.
Добавление данной возможности в ваше приложение сделает его более «профессиональным».

Как это работает:

Есть два основных пути добавления данного функционала в Windows приложение.
Первым способ заключается в использовании стандартных Windows Drag-and-Drop API функций и обработка сопутствующих оконных сообщений.
Вторым методом является использование технологии OLE (COM), который предоставляет расширенные методы передачи данных между приложениями.
В данной статье будет рассмотрен первый и самый простой способ.

Краткий обзор:

По умолчанию, поддержка перетаскивания и приема файлов в Windows приложениях отключена.
Для включения данной возможности мы должны сказать Windows о том, что мы хотим получать уведомления о перетаскивании, а так-же указать окно, отвечающее за обработку данных уведомлений.
Данное окно должно уметь обрабатывать сообщение WM_DROPFILES, приходящее ему в тот момент, когда пользователь завершил операцию перетаскивания.
Это сообщение предоставляет нам возможность реализовать обработчик события Drop, из которого мы может вызвать различные API функции, для получения информации о том, что именно нам было передано.
По завершении обработки данного сообщения, хорошим тоном будет уведомить Windows о том, что мы обработали данную операцию и больше не хотим получать уведомления о ней.

Проблемы с безопасностью в Windows Vista (и выше)

По соображениям безопасности в Windows Vista возможность перетаскивания между окнами, имеющими различные параметры безопасности, может быть запрещена.
Как следствие получение сообщения WM_DROPFILES вашим приложением может быть заблокирована.

Рассмотрим шаг за шагом, что нужно сделать в Delphi приложении для реализации приема файлов посредством Drag-and-Drop:

1. Для получения доступа к необходимым API функциям необходимо подключить модуль ShellAPI:

uses 
  ShellAPI;

2. Вызовите функцию DragAcceptFiles, передав первым параметром хэндл окна, которое будет отвечать за обработку сообщения WM_DROPFILES, а вторым значение True, указывающее что данное окно готово получать уведомления.
К примеру данным окном будет главная форма нашего приложения:

DragAcceptFiles(Form1.Handle, True);

3. Обработайте сообщение WM_DROPFILES. В Delphi мы можем объявить обработчик сообщения в классе главной формы приложения:

procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;

4. При получении сообщения WM_DROPFILES используйте функции DragQueryXXX для получения информации о переданных файлах — смотрите пояснения ниже.

5. Уведомьте Windows об окончании обработки данного сообщения.
Это делается посредством вызова функции DragFinish, в которую параметром передается дескриптор HDROP, полученный при обработке сообщения WM_DROPFILES.
Вызов данной функции освободит ресурсы, используемые для хранения данных об операции Drag-and-Drop.

DragFinish(DropH);

6. Когда будет происходить закрытие приложения, необходимо повторно вызвать функцию DragAcceptFiles, но в этот раз вторым параметром необходимо передать False, что уведомит Windows о том, что окно более не обрабатывает сообщение WM_DROPFILES.

DragAcceptFiles(Form1.Handle, False);

Получение информации о перенесенных файлах.

Мы будем использовать две функции для получения информации о перенесенных файлов — DragQueryFile и DragQueryPoint.
Обе данных функции требуют параметром дескриптор HDROP, полученный при обработке сообщения WM_DROPFILES.

Приветствуем мастера на все руки — DragQueryFile.

Как и большинство функций Windows API, DragQueryFile может реализовывать несколько вариантов поведения, в зависимости от переданных ему параметров.
Такое поведение иногда приводит к тому, что программисту достаточно сложно запомнить правильный вариант использования данной функции.
Давайте рассмотрим её параметры (с учетом синтаксиса Delphi):

  1. DropHandle: HDROP – дескриптор операции, переданный сообщением WM_DROPFILES.
  2. Index: Integer – индекс файла, при запросе к списку переданных файлов.
  3. FileName: PChar – указатель на буфер, в который будет передано имя файла.
  4. BufSize: Integer – размер буфера FileName.

Вызов данной функции дает нам возможность получения следующих данных:

  • Получение количества переданных файлов. Мы можем получить данную информацию передав $FFFFFFFF или DWORD(-1) в параметре Index, при этом параметр FileName должен быть «nil», а параметр BufSize, отвечающий за размер буфера FileName, должен быть равен нулю. Возвращаемое функцией значение будет равно количеству переданных файлов. Достаточно очевидно.
  • Получение размера области памяти, требуемой для хранения имени файла на основании параметра Index (отсчет идет от нуля, т.е. первый файл индексируется в списке нулем). Для этого необходимо указать необходимый файл в списке, посредством параметра Index, оставив параметры FileName и BufSize такими-же, как и в предыдущем пункте. В результате функция возвратит количество байт, требуемых для хранения имени файла.
  • Получение имени файла на основе переданного индекса. Для этого необходимо, помимо указания индекса файла через параметр Index, подготовить буфер (чуть больше требуемого размера), который сможет принять данные о пути к файлу, плюс терминирующий ноль.

Пока что все это выглядит достаточно не понятно, но в конце статьи мы рассмотрим все эти этапы в виде самостоятельного кода, реализованного в виде класса.

DragQueryPoint

Данная функция в принципе является альтернативной DragQueryFile, вызвав ее, мы можем узнать координаты, по которым завершилось перетаскивание файлов.

Рассмотрим ее параметры:

  • DropHandle: HDROP – дескриптор операции, переданный сообщением WM_DROPFILES.
  • var Point: TPoint – структура TPoint, в которой возвращаются данные о точке завершения операции Drag-and-Drop. Если операция завершилась в клиентской области окна, то она вернет ненулевое значение, в противном случае функция вернет ноль.

Рассмотрим все целиком.

Создайте новый проект.
У нас есть «рыба» Delphi приложения и нам необходимо добавить в нее возможность получения информации о переданных файлах посредством Drag-and-Drop.
В нашей форме мы зарегистрируем обработчик интересующего нас события:

procedure TForm1.FormCreate(Sender: TObject);
begin
  // ... здесь некий код
  DragAcceptFiles(Self.Handle, True);
  // ... здесь некий код
end;

Данным кодом мы подписались на прием сообщения WM_DROPFILES.
Вот так выглядит обработчик данного сообщения:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
var
  DropH: HDROP;               // дескриптор операции перетаскивания
  DroppedFileCount: Integer;  // количество переданных файлов
  FileNameLength: Integer;    // длина имени файла
  FileName: string;           // буфер, принимающий имя файла
  I: Integer;                 // итератор для прохода по списку
  DropPoint: TPoint;          // структура с координатами операции Drop
begin
  inherited;
  // Сохраняем дескриптор
  DropH := Msg.Drop;
  try
    // Получаем количество переданных файлов
    DroppedFileCount := DragQueryFile(DropH, $FFFFFFFF, nil, 0);
    // Получаем имя каждого файла и обрабатываем его
    for I := 0 to Pred(DroppedFileCount) do
    begin
      // получаем размер буфера
      FileNameLength := DragQueryFile(DropH, I, nil, 0);
      // создаем буфер, который может принять в себя строку с именем файла
      // (Delphi добавляет терминирующий ноль автоматически в конец строки)
      SetLength(FileName, FileNameLength);
      // получаем имя файла
      DragQueryFile(DropH, I, PChar(FileName), FileNameLength + 1);
      // что-то делаем с данным именем (все зависит от вашей фантазии)
      // ... код обработки пишем здесь
    end;
    // Опционально: получаем координаты, по которым произошла операция Drop
    DragQueryPoint(DropH, DropPoint);
    // ... что-то делаем с данными координатами здесь
  finally
    // Финализация - разрушаем дескриптор
    // не используйте DropH после выполнения данного кода...
    DragFinish(DropH);
  end;
  // Говорим о том, что сообщение обработано
  Msg.Result := 0;
end;

В самом начале мы запоминаем дескриптор операции Drop в локальной переменной.
Это делается просто для удобства, в дальнейшем мы будем использовать данное значение для вызовов функции DragQueryFile (как впрочем и для остальных DragХХХ), первым вызовом которой мы получим количество файлов в списке.
Следующий наш шаг заключается в получении имени каждого файла в цикле по его индексу (файлы в списке индексируются от нуля — не забывайте это).
Для каждого файла необходимо изначально узнать размер буфера, получить который можно при помощи второго способа использования DragQueryFile, который был описан выше, а затем выделить память под строку, которая будет хранить в себе путь к файлу.
В заключение требуется прочитать путь к файлу в выделенный буфер, посредством третьего способа DragQueryFile.

После чтения всей информации об именах файлов, мы можем получить координаты точки, в которую пользователь перетащил файлы.
После того как мы выполнили все операции, мы вызовем функцию DragFinish, для освобождения дескриптора.
Обратите внимание на то, что мы должны выполнить это действие в любом случае, даже при поднятии исключения.
Ну и в самом конце мы укажем результат обработки сообщения, выставив значение 0 в структуре Msg.Result, указав, таким образом, что мы успешно обработали данное сообщение.

При завершении работы приложения мы отключим главное окно от получения сообщений WM_DROPFILES.

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // ... здесь некий код
  DragAcceptFiles(Self.Handle, False);
  // ... здесь некий код
end;

Реализуем Delphi — компонент (The Delphi Way)

Если вы согласны с тем, что использование API напрямую, непосредственно в коде базового приложения, вносит некоторый беспорядок,
то думаю вы согласитесь и с тем, что гораздо удобнее вынести данный функционал во вне, в виде некоего вспомогательного класса.
Как пример, вот вам небольшой класс, который умеет обрабатывать сообщение WM_DROPFILES.
Он прячет в себе достаточно много кода, который мы должны были-бы реализовать самостоятельно без его использования.
Правда есть нюанс — мы все-же должны уведомлять Windows о наличии окна, обрабатывающего сообщение WM_DROPFILES.

Декларация данного класса выглядит так:

type
  TFileCatcher = class(TObject)
  private
    fDropHandle: HDROP;
    function GetFile(Idx: Integer): string;
    function GetFileCount: Integer;
    function GetPoint: TPoint;
  public
    constructor Create(DropHandle: HDROP);
    destructor Destroy; override;
    property FileCount: Integer read GetFileCount;
    property Files[Idx: Integer]: string read GetFile;
    property DropPoint: TPoint read GetPoint;
  end;

… ну и его реализация:

constructor TFileCatcher.Create(DropHandle: HDROP);
begin
  inherited Create;
  fDropHandle := DropHandle;
end;

destructor TFileCatcher.Destroy;
begin
  DragFinish(fDropHandle);
  inherited;
end;

function TFileCatcher.GetFile(Idx: Integer): string;
var
  FileNameLength: Integer;
begin
  FileNameLength := DragQueryFile(fDropHandle, Idx, nil, 0);
  SetLength(Result, FileNameLength);
  DragQueryFile(fDropHandle, Idx, PChar(Result), FileNameLength + 1);
end;

function TFileCatcher.GetFileCount: Integer;
begin
  Result := DragQueryFile(fDropHandle, $FFFFFFFF, nil, 0);
end;

function TFileCatcher.GetPoint: TPoint;
begin
  DragQueryPoint(fDropHandle, Result);
end;

В принципе здесь нет ничего такого, что не было рассказано ранее. Весь код вы уже видели.
Конструктор принимает дескриптор HDROP, деструктор финализирует его вызовом DragFinish.
Список файлов и их количество представлены свойствами класса.
Все методы класса по сути являются оберткой над API DragQueryFile и DragQueryPoint.

Давайте перепишем обработчик сообщения WM_DROPFILES с учетом использования нового класса:

procedure TForm1.WMDropFiles(var Msg: TWMDropFiles);
var
  I: Integer;                 // итератор для прохода по списку
  DropPoint: TPoint;          // структура с координатами операции Drop
  Catcher: TFileCatcher;      // экземпляр класса TFileCatcher
begin
  inherited;
  Catcher := TFileCatcher.Create(Msg.Drop);
  try
    for I := 0 to Pred(Catcher.FileCount) do
    begin
      // ... код обработки пишем здесь
    end;
    DropPoint := Catcher.DropPoint;
    // ... что-то делаем с данными координатами здесь
  finally
    Catcher.Free;
  end;
  Msg.Result := 0;
end;

Планы на будущее

Класс TFileCatcher может быть расширен для скрытия всех API используемых при операциях Drag-and-Drop. Правда для этого потребуется доступ к окну формы, для осуществления перехвата сообщения WM_DROPFILES.
Один из вариантов такой реализации заключается в сабклассинге окна формы. Правда это выходит за рамки данной статьи. Тем не менее, если вы хотите получить больше информации, посмотрите мой набор компонент Drop Files.

Демо приложение

Исходный код демоприложения базируется на коде, опубликованном в данной статье. Код был протестирован в версиях Delphi с четвертой по седьмую.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an «AS IS» basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. The code may be used in any way providing it is not sold or passed off as the work of another person. If you agree to all this then please download the code using the following link.

Скачать демонстрационный пример

Автор: Rouse

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js