Форум по Delphi программированию

Delphi Sources



Вернуться   Форум по Delphi программированию > Все о Delphi > [ "Начинающим" ]
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 07.03.2008, 06:42
Rosenkrantz Rosenkrantz вне форума
Активный
 
Регистрация: 04.12.2007
Адрес: Москва
Сообщения: 234
Версия Delphi: Delphi 7
Репутация: 40
По умолчанию Drag&Drop файлов из проводника (демо)

Периодически в этом подфоруме возникают вопросы "Как перетащить в свое приложение файлы из Проводника 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.
Вложения
Тип файла: zip DnD_Demo2007.zip (4.7 Кбайт, 90 просмотров)
Ответить с цитированием
  #2  
Старый 19.05.2008, 13:40
Аватар для The Shadow
The Shadow The Shadow вне форума
Продвинутый
 
Регистрация: 11.06.2007
Адрес: Уфа, Россия
Сообщения: 793
Репутация: 35
По умолчанию

У меня не работает. ListBox даже такого сообщения не получает. Windows Vista.
__________________
Что делать, когда сломался комп:
1. Если вы юзер - делать ноги.
2. Если ремонтник - делать деньги.
3. Если вы программист - делать вид, что так было задумано.
Ответить с цитированием
Ответ


Delphi Sources

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 04:42.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources" by BrokenByte Software, 2004-2023

ВКонтакте   Facebook   Twitter