Периодически в этом подфоруме возникают вопросы "Как перетащить в свое приложение файлы из Проводника Windows?". И я даже пару раз на них отвечал. Посколько мне сегодня с утра нечего делать, я решил написать развернутое пособие на эту тему, благо там ничего сложного нет. Просто обычно формат ответа на вопрос в форуме не подразумевает подробных пояснений, а я все же думаю, что всегда полезно понимать, почему это работает так, а не иначе, и понимать, что ты в данный момент делаешь.
Итак, сложного, как я сказал уже, ничего нет, но есть один нюанс. В большинстве примеров, которые я видел, рассматривается перенос файлов
на форму, тогда как обычно в приложении за прием файлов отвечает не форма целиком, а какой-то конкретный элемент формы -
TMemo,
TListBox и т.п. Делается это потому, что для формы легко организовать перехват сообщения
WM_DROPFILES, которым Windows информирует приложение о том, что на него осуществляется перенос файлов. В Delphi для этого существуют т.н. "методы сообщений".
Вот
тут я писал, как сделать прием файлов формой.
Здесь я рассмотрю аналогичную форму, только вместо
TMemo буду использовать
TListBox. Принципиально это ни на что не влияет, просто
TListBox мне в данном случае больше нравится.
Как же сделать, чтобы файлы принимались отдельным элементом формы, и не принимались другими? На первый взгляд достаточно заменить вызов:
Код:
DragAcceptFiles(Form1.Handle, True);
на
Код:
DragAcceptFiles(ListBox1.Handle, True);
Это, однако, не работает, поскольку сообщения по-прежнему поступают в обработчик формы.
То есть задача сводится к тому, чтобы перехватить
WM_DROPFILES не в обработчике формы, а непосредственно в обработчике того элемента, который у нас отвечает за прием файлов.
Хитрость тут вот чем. И
TForm, и
TMemo, и
TListBox - все это так называемые "оконные" компоненты, т.е. они имеют дескриптор окна, который содержится в свойстве
Handle и процедуру окна. Вот до этой процедуры окна нам и надо как-то добраться.
Для этого мы напишем свою процедуру окна для
ListBox1 и заменим ею стандартную. В WinAPI есть функция
SetWindowLong:
Код:
SetWindowLong(hWnd: HWND; nIndex: Integer; dwNewLong: Integer): Integer;
Эта функция, вообще говоря, делает разные вещи в зависимости от значения nIndex, но нас сейчас интересует вызов ее с параметром
GWL_WNDPROC - это и позволит нам заменить процедуру окна.
Сама же процедура окна должна быть описана в виде функции такого вида:
Код:
function NewWndProc(Handle: HWND; Msg, WParam, LParam: LongInt): LongInt; stdcall;
NewWndProc - это имя, оно может быть любым (в рамках правил именования, конечно). При вызове для установки новой оконной процедуры
SetWindowLong возвращает указатель на старую. Это важно, потому что он нам понадобится - мы ведь не хотим обрабатывать абсолютно все сообщения, которые попадают к
ListBox1.
Итак, чтобы заменить оконную процедуру мы вызываем
SetWindowLong:
Код:
OldListWndProc := Pointer(SetWindowLong(ListBox1.Handle, GWL_WNDPROC, Integer(@NewListWndProc)));
Сделать это лучше всего в обработчике формы
OnCreate.
OldListWndProc - это тот самый указатель на старую процедуру окна, он имеет тип
Pointer.
Сама же новая процедура окна будет выглядеть так:
Код:
function NewListWndProc(Handle: HWND; Msg, WParam,
LParam: LongInt): LongInt; stdcall;
//------------------------------------------------------------------------------
// Новая процедура для окна списка
//------------------------------------------------------------------------------
var
i, Count: Integer;
SFileName: Array [0..256] of Char;
begin
// Мы обрабатываем сообщение WM_DROPFILES
if Msg = WM_DROPFILES then begin
// Файлов может быть более одного. Чтобы узнать сколько их,
// вызываем функцию DragQueryFiles и вместо порядкового номера файла
// указываем $FFFFFFFF. Функция вернет количество файлов.
Count := DragQueryFile(WParam, $FFFFFFFF, SFilename, SizeOf(SFilename));
// Теперь когда знаем количество файлов, можем перебрать их в цикле
// и добавить в список
for i := 0 to Count - 1 do begin
// Сейчас нам нужно уже конкретное имя файла,
// поэтому мы указываем порядковый номер файла и получаем в
// переменной SFileName его имя.
DragQueryFile(WParam, i, SFileName, SizeOf(SFileName));
// Добавляем имя в список
MainForm.AddFileToList(MainForm.ListBox1.Items, SFileName);
end;
// Windows для переноса файлов выделяла память.
// Вызов DragFinish сообщает системе, что память можно освободить
DragFinish(WParam);
end;
// Вызываем старый обработчик сообщений ListBox
Result := CallWindowProc(OldListWndProc, Handle, Msg, WParam, LParam);
end;
Эта процедура перехватывает сообщение
WM_DROPFILES, обрабатывает его и затем вызывает старый обработчик для остальных сообщений.
Собственно, на этом и все. Остается сделать, как описано выше,
ListBox1 приемником файлов
Код:
DragAcceptFiles(ListBox1.Handle, True);
(а при удалении формы - запретить ему прием файлов) и нужно для порядка вернуть на место старый обработчик событий для
ListBox1 когда мы заканчиваем работу
Код:
SetWindowLong(ListBox1.Handle, GWL_WNDPROC, Integer(OldListWndProc));
Обратите внимание, что в данном примере новая оконная процедура описана в виде отдельной функции, не принадлежащей форме. Можно заменить оконную процедуру и методом формы, но это сделать чуть сложнее. Для этого понадобится предварительно получить указатель на метод формы с помощью вызова
MakeObjectInstance; при этом метод должен быть описан в виде
Код:
TMyForm = class(TForm)
...
private
procedure WndMethod(var Msg: TMessage);
...
end;
Здесь я это не рассматриваю, если кому интересно - пишите, сделаю пример. Ну и вообще, если есть вопросы - задавайте, постараюсь ответить.
Всем, кто дочитал до этого места спасибо.
Во вложении полный проект на D2007.