Сначала рассмотрим технологию Drag&Drop в Windows так как большинство знакомых мне программистов работает именно в этой ОС.

 

Немного Windows API

 

Итак, чтоб заставить приложение реагировать на события Drag&Drop, нам нужно воспользоваться функциями DragAcceptFiles, DragQueryFile и DragFinish из модуля ShellAPI.pas.

 

Первая из них имеет вид:

 

procedure DragAcceptFiles (Wnd: HWND; Accept: BOOL); stdcall;

  • Wnd - дескриптор окна, для которого будет установлено разрешение на прием перетаскиваемых объектов;
  • Accept - собственно разрешение (True - разрешить прем объектов; False - запретить).

При установленном флаге Accept реакция приложения распространяется на все файлы и папки, расположенные на любых дисках. Реакция приложения не распространяется на метаелементы оболочки - то есть на Панель управления, Принтеры, Сетевое окружение, иконки дисков в Моем компьютере…

 

       


После этого при перетаскивании файлов или папок на "допущенный" элемент приложения курсор меняет свою форму (сравните рис. 1 и рис. 2). При "отпускании" объекта на элементе тому посылается сообщение WM_DROPFILES, которое оповещает о произошедшем событии Drag&Drop. Параметр wParam сообщения содержит идентификатор события и потребуется нам дальше.

 

Вторая функция используется для получения списка файлов (как мы помним, перетаскивать можно несколько файлов, папок…), которые были передвинуты на наш компонент:

 

function DragQueryFile (Drop: HDROP; FileIndex: UINT; FileName: PChar;
cb: UINT): UINT; stdcall;

  • Drop - идентификатор, который был передан нам через сообщение WM_DROPFILES;
  • FileIndex - номер запрашиваемого файла;
  • FileName - указатель на строку, которая содержит имя файла с индексом (FileIndex);
  • Cb - размер буфера FileName.

При передаче параметру FileIndex значения $FFFFFFFF DragQueryFile возвращает количество файлов, которые были перетащены на компонент; в других случаях возвращаемое значение - количество скопированных в буфер FileName символов.

 

DragFinish - используется для освобождения памяти занятой при перетаскивании. Формат функции:

 

procedure DragFinish (Drop: HDROP); stdcall;
 

Алгоритм работы

 

Во-первых, мы разрешим "системное" перетаскивание на какой-нибудь компонент посредством DragAcceptFiles. Далее мы каким-либо образом должны отследить возникновение сообщения WM_DROPFILES и записать из полученных при его отправке данных значение wParam, которое потом используем вместе с DragQueryFile, для того чтоб вывести список перетасканных файлов. Ну и под конец - завершаем эту процедуру освобождением памяти (DragFinish).

 

В первом приближении все очень просто. Трудности начинают возникать, когда мы доходим до отлавливания сообщения WM_DROPFILES. И тут есть несколько вариантов. Во-первых, можно создать новый класс, в котором перехватывается это сообщение. Например, так:

 

TMyListBox = class (TListBox)
private
FOnDropFiles:TNotifyEvent;
FDrop:THandle;
protected
procedure WMDropFiles (var Message:TMessage); message WM_DROPFILES;
public
property Drop:THandle read FDrop write FDrop;
property OnDropFiles:TNotifyEvent read FOnDropFiles write FOnDropFiles;
end;

 

Кроме того, можно создать универсальный компонент, который подменял бы метод WindowProc заданного компонента и таким образом давал нам возможность реагировать на сообщение. Этот метод более сложен при начальной реализации (написании компонента), но дает ощутимый прирост в скорости проектирования приложений.

 

В этой статье мы рассмотрим первый метод, а кто захочет "пойти другим путем", может скачать компонент ShellDragDrop c моего сайта (www.g299792458.boom.ru).

 

Реализация

 

Для демонстрации мы сначала нарисуем форму и разместим на ней кнопку. В секции private нашей формы разместим декларацию объекта LB типа TMyListBox и добавим метод:

 

procedure DoDropFiles (Sender:TObject);
 

Далее запишем реакцию кнопки на нажатие:

 

procedure TForm1.Button1Click (Sender: TObject);
begin
LB:=TMyListBox.Create (Self);
LB.Parent:=Self;
LB.SetBounds (10,10,100,100);
LB.OnDropFiles:=Self.DoDropFiles;
DragAcceptFiles (LB.Handle,True);
end;

 

И формы на уничтожение:

 

procedure TForm1.FormDestroy (Sender: TObject);
begin
DragAcceptFiles (LB.Handle,False);
LB.Free;
end;

 

Теперь перейдем к реализации нашего вновь созданного компонента TmyListBox (см. листинг 1):

 

procedure TMyListBox.WMDropFiles (var Message:TMessage);
begin
Drop:=Message.WParam;
if Assigned (OnDropFiles) then OnDropFiles (Self);
end;

 

И наконец - реализация собитыя OnDropFiles:

 

procedure TForm1.DoDropFiles (Sender:TObject);
var CB:Integer;I,j:Integer;
Str:Array [0..MAX_PATH] of Char;
begin
I:=DragQueryFile ((Sender as TMyListBox).Drop,$FFFFFFFF,nil,cb);
(Sender as TMyListBox).Items.Add (IntToStr (I));
For j:=0 to i-1 do
begin
FillChar (Str,SizeOf (Str),0);
DragQueryFile ((Sender as TMyLIstBox).Drop,j,Str,MAX_PATH);
(Sender as TMyListBox).Items.Add (Str);
end;
end;

 

После запуска приложения появляется главная форма с кнопкой, по нажатии которой создается компонент LB. Теперь попробуем перетащить на него один или несколько ярлыков, которые находятся на рабочем столе. Как видим, в нашем боксе появилась требуемая информация. Полный текст программы приведен в листинге 1.

 

А теперь - Kylix...

 

Те из вас, кто уже пробовал Kylix, могут заметить, что переход на него действительно не вызывает сложностей, пока вы не выходите за рамки готовых компонент, но теперь мы покажем, как можно проделать такое "нестандартное" действие под Linux.


Определения

 

Для начала, чтоб не было неясностей, скажу, что испытания проводились на ASPLinux 7.3, которая поставлялась на диске "К + П" № 2/2003г. Программа была написана на Borland Kylix v1.0 Server Developer. При проверке приложения использовался Konqueror (рис. 3) - стандартный проводник KDE.

 

 

Немного теории

 

Во-первых, наше приложение будет основываться на работе с модулем Qt.pas, в котором объявлены все жизненно важные объекты, переменные, типы и т.д. Заметим также, что работа приложений под Linux (на мой взгляд) кардинально отличается от работы приложений в Windows. Так, если Windows API основано на обработке сообщений, то в Linux роль сообщений играют сигналы и слоты, а при использовании Qt - события (отдаленно напоминающие события компонентов Delphi). Взаимодействие с системой осуществляется не путем перехвата сообщений, а путем создания реакции на событие и его регистрации.

 

Лезем в "дебри" Qt

 

Сначала воспользуемся функцией QEvent_hook_create для создания экземпляра объекта, который бы реагировал на события:

 

function QEvent_hook_create (handle: QObjectH): QEvent_hookH; cdecl;

  • Handle - идентификатор объекта, для которого создается реакция на событие;
  • Результат - идентификатор реагирующего объекта.

По завершении работы приложения надо будет освободить реагирующий объект:

 

procedure QEvent_hook_destroy (handle: QEvent_hookH); cdecl;
 

Теперь нам нужно создать собственно реакцию на событие, которое должно иметь следующий вид:

 

TEventFilterMethod = function (Sender: QObjectH; Event: QEventH):
Boolean of object cdecl;

  • Sender - идентификатор объекта который должен реагировать на событие;
  • Event - идентификатор события, на которое должен реагировать объект.

После этого нам необходимо инициализировать ее:

 

procedure Qt_hook_hook_events (handle: QObject_hookH;
hook: QHookH); cdecl;

  • Handle - идентификатор объекта-реакции на событие;
  • Hook - метод, который собственно и реагирует на события объекта.
  • результат - True, если на событие успешно отреагировали; False - если нет.

 Поскольку мы пишем реакцию, которая должна незаметно влиять на работу нашего компонента, то результат всегда должен быть равен False.


В самом методе Hook нам необходимо разобрать, на какие события следует реагировать, так как ему передаются все без исключения события, связанные с прикрепленным (через QEvent_hook_create) объектом. Для выделения необходимого события используются методы QEvent_isXXXXXX, где XXXX - название события.

 

Для наших нужд потребуется только одни метод - QEvent_isQDropEventEvent:

 

function QEvent_isQDropEventEvent (e: QEventH): Boolean; cdecl;

  • e - значение, переданное параметру Event из шаблона TeventFilterMethod;
  • результат - True, если событие относится к Drag&Drop; False - в противном случае.

После того как QEvent_isQDropEventEvent вернул true, нам следует перекодировать событие в QMimeSourceH посредством метода:

 

function QDropEvent_to_QMimeSource (handle: QDropEventH):
QMimeSourceH; cdecl;

 

далее принять на обработку это событие:

 

procedure QDropEvent_acceptAction (handle: QDropEventH;
y: Boolean); cdecl;
 

и, наконец, декодировать событие в приятный нашему взгляду вид:

 

function QTextDrag_decode (e: QMimeSourceH; s: PWideString):
Boolean; overload; cdecl;

 

После этого мы можем спокойно обрабатывать наши "перетасканные" файлы.

 

К делу

 

Итак, для начала создадим форму и разместим на ней кнопку и компонент TListBox, который, собственно, и будет играть роль приемника перетаскиваний. В кнопке запишем код, который будет инициализировать наш TListBox на обработку событий Drag&Drop:

 

procedure TForm1.Button1Click (Sender: TObject);
begin
QWidget_setAcceptDrops (ListBox1.Handle,True);
Evt:=QEvent_hook_create (Self.ListBox1.Handle);
//Filter:=Self.EvtFilter;
TEventFilterMethod (H):=EvtFilter;
Qt_hook_hook_events (Evt,H);
end;

 

При закрытии формы мы должны освободить наш объект Evt от исполняемых им обязанностей:

 

procedure TForm1.FormDestroy (Sender: TObject);
begin
QEvent_hook_destroy (Evt);
end;
 

Теперь нам осталось только написать необходимую реакцию на события:

 

function TForm1.EvtFilter (Sender:QObjectH; Event:QEventH):Boolean;
var QMS:QMimeSourceH;
S:WideString;
begin
Result:=False;
if QEvent_isQDropEventEvent (Event) then
begin
QMS:=QDropEvent_to_QMimeSource (QDropEventH (Event));
QDropEvent_acceptAction (QDropEventH (Event),QTextDrag_canDecode (QMS));
if QTextDrag_canDecode (QMS) then
begin
ListBox1.Items.Clear;
QTextDrag_decode (QMS,@S);
ListBox1.Items.Add (S);
end
end else
if QEvent_isQCloseEvent (Event) then
QEvent_hook_destroy (Evt);
end;
 

Код:

 

if QEvent_isQCloseEvent (Event) then
QEvent_hook_destroy (Evt);

 

- добавлен просто так, на всякий случай. У меня Kylix время от времени зависал при закрытии приложения, а вот после добавления такой строчки кода - ни разу.

 

В листинге 2 показан код приложения, о котором шла речь в этой статье.

 

Послесловие

 

Сразу же хочу предупредить тех, кто, читая эту статью, уже "сидит" в Kylix'е: в портированном Borland экземпляре Qt.pas (в версии Kylix 1.0, а, возможно, и в других) неправильно определена функция QEvent_isQDropEventEvent. Для правильной работы вам необходимо в модуле Qt.pas в секции реализации заменить имя функции на которую ссылается QEvent_isQDropEventEvent c QEvent_isQDropEventEvent на QEvent_isQDropEvent.

 

Несколько слов для тех, кто хочет самостоятельно попробовать написать универсальный компонент для работы с Drag&Drop. Во-первых, дерзайте - ибо лучше наступать на свои "грабли", нежели на чужые (да и обиды будет меньше, если что не так). Не забывайте освобождать занятые ресурсы; и, если вы пишите в моем стиле, будьте осторожны при отладке компонента и при закрытии приложения. Ну а если уж совсем ничего не получается, используйте мой компонент.

 

А для юзающих Kylix скажу, что здесь также можно "пойти другим путем" - созданием собственного компонента, на который возлагалась бы задача по захвату событий и освобождению занятых ресурсов по окончании работы. Мы бы только трудились над реакцией компонента на те или иные события. В модуле QControls.pas есть некое подобие такого компонента (TX11DragFilter), и хотя этот компонент и не подходит под концепцию "визуального программирования", он может зато послужить основой для создания собственных компонентов.

 

Листинг 1. Создание компонента TmyListBox под Windows


unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyListBox = class (TListBox)
private
FOnDropFiles:TNotifyEvent;
FDrop:THandle;
protected
procedure WMDropFiles (var Message:TMessage); message WM_DROPFILES;
public
property Drop:THandle read FDrop write FDrop;
property OnDropFiles:TNotifyEvent read FOnDropFiles write FOnDropFiles;
end;
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click (Sender: TObject);
procedure FormDestroy (Sender: TObject);
private
LB:TMyListBox;
procedure DoDropFiles (Sender:TObject);
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
implementation
Uses ShellAPI;
{$R *.dfm}
procedure TMyListBox.WMDropFiles (var Message:TMessage);
begin
Drop:=Message.WParam;
if Assigned (OnDropFiles) then OnDropFiles (Self);
end;
procedure TForm1.DoDropFiles (Sender:TObject);
var CB:Integer;I,j:Integer;
Str:Array [0..MAX_PATH] of Char;
begin
I:=DragQueryFile ((Sender as TMyListBox).Drop,$FFFFFFFF,nil,cb);
(Sender as TMyListBox).Items.Add (IntToStr (I));
For j:=0 to i-1 do
begin
FillChar (Str,SizeOf (Str),0);
DragQueryFile ((Sender as TMyLIstBox).Drop,j,Str,MAX_PATH);
(Sender as TMyListBox).Items.Add (Str);
end;
end;
procedure TForm1.Button1Click (Sender: TObject);
begin
LB:=TMyListBox.Create (Self);
LB.Parent:=Self;
LB.SetBounds (10,10,300,100);
LB.OnDropFiles:=Self.DoDropFiles;
DragAcceptFiles (LB.Handle,True);
end;
procedure TForm1.FormDestroy (Sender: TObject);
begin
DragAcceptFiles (LB.Handle,False);
LB.Free;
end;
end.

Листинг 2. Создание компонента TmyListBox под Kylix
unit Unit1;
interface
uses
SysUtils, Types, Classes, Variants, QGraphics, QControls, QForms, QDialogs,Qt,
QStdCtrls;
type
TForm1 = class (TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click (Sender: TObject);
procedure FormDestroy (Sender: TObject);
private
Evt:QEvent_hookH;
H:TMethod;
// FFilter:TEventFilterMethod;
function EvtFilter (Sender: QObjectH; Event: QEventH): Boolean; cdecl;
{Private declarations}
public
{Public declarations}
// property Filter:TEventFilterMethod read FFilter write FFilter;
end;
var
Form1: TForm1;
implementation
{$R *.xfm}
function TForm1.EvtFilter (Sender:QObjectH; Event:QEventH):Boolean;
var QMS:QMimeSourceH;
S:WideString;
begin
Result:=False;
if QEvent_isQDropEventEvent (Event) then
begin
QMS:=QDropEvent_to_QMimeSource (QDropEventH (Event));
QDropEvent_acceptAction (QDropEventH (Event),QTextDrag_canDecode (QMS));
if QTextDrag_canDecode (QMS) then
begin
ListBox1.Items.Clear;
QTextDrag_decode (QMS,@S);
ListBox1.Items.Add (S);
end
end else
if QEvent_isQCloseEvent (Event) then
QEvent_hook_destroy (Evt);
end;
procedure TForm1.Button1Click (Sender: TObject);
begin
QWidget_setAcceptDrops (ListBox1.Handle,True);
Evt:=QEvent_hook_create (Self.ListBox1.Handle);
//Filter:=Self.EvtFilter;
TEventFilterMethod (H):=EvtFilter;
Qt_hook_hook_events (Evt,H);
end;
procedure TForm1.FormDestroy (Sender: TObject);
begin
QEvent_hook_destroy (Evt);
end;
end.

2004.05.14
19.03.2009
В IV квартале 2008 г. украинский рынок серверов по сравнению с аналогичным периодом прошлого года сократился в денежном выражении на 34% – до $30 млн (в ценах для конечных пользователей), а за весь календарный год – более чем на 5%, до 132 млн долл.


12.03.2009
4 марта в Киеве компания Telco провела конференцию "Инновационные телекоммуникации", посвященную новым эффективным телекоммуникационным технологиям для решения задач современного бизнеса.


05.03.2009
25 февраля в Киеве компания IBM, при информационной поддержке "1С" и Canonical, провела конференцию "Как сохранить деньги в условиях кризиса?"


26.02.2009
18-19 февраля в Киеве прошел юбилейный съезд ИТ-директоров Украины. Участниками данного мероприятия стали ИТ-директора, ИТ-менеджеры, поставщики ИТ-решений из Киева, Николаева, Днепропетровска, Чернигова и других городов Украины...


19.02.2009
10 февраля в Киеве состоялась пресс-конференция, посвященная итогам деятельности компании "DiaWest – Комп’ютерний світ" в 2008 году.


12.02.2009
С 5 февраля 2009 г. в Киеве начали работу учебные курсы по использованию услуг "электронного предприятия/ учреждения" на базе сети информационно-маркетинговых центров (ИМЦ).


04.02.2009
29 января 2009 года в редакции еженедельника "Computer World/Украина" состоялось награждение победителей акции "Оформи подписку – получи приз!".


29.01.2009
22 января в Киеве компания "МУК" и представительство компании Cisco в Украине провели семинар для партнеров "Обзор продуктов и решений Cisco Small Business"

 

 
 
Copyright © 1997-2008 ИД "Комиздат".