уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Сегодня поговорим снова о FireMonkey. На этот раз я решил разобраться с тем как работает Drag&Drop в FMX 2.0., посмотреть с чем мы можем работать при разработке проектов как под Windows, так и под Mac OS. В общем провести небольшое такое исследование на тему.
Конечно, то, что FireMonkey — это платформа для разработки приложений под различные операционные системы, даст о себе знать и придется вникать в тонкости работы. В общем
nelzya-prosto-tak-vzyat-i_poniat
Разбираться я начал с самых, что ни есть простых моментов по работе с Drag&Drop в Delphi вообще — с обработки событий, но применительно к платформе FireMonkey.

Последовательности событий в VCL и FireMonkey

Первым делом я решил посмотреть на то в какой последовательности срабатывают те или иные события в VCL и FMX. В качестве примера я создал два простеньких приложения:

Тестовые приложения

Тестовые приложения

В VCL-проекте приемником является TPanel, в FMX — специальный компонент TDropTarget. В обоих приложениях я в Memo выписываю порядок срабатывания тех или иных событий, относящихся к Drag&Drop. Таскать я решил все тот же Memo (хотя для первого примера можно таскать вообще что угодно) поэтому, чтобы сильно не углубляться во всякие методы Drag&Drop для TMemo я выставил свойство

DragMode = dmAutomatic

пусть пока механизм стартует автоматически. Дальше, в обоих приложениях как для приемника, так и для источника я определил все возможные события DnD в таком формате

procedure TFormXXX.ObjectDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  Memo1.Lines.Add(Sender.ClassName+' DragDrop')
end;

То есть — проще некуда. Теперь смотрим на очередность срабатывание событий.
VCL-приложение:
TMemo StartDrag -->зажал левую кнопку мыши
TMemo DragOver
TMemo DragOver
TMemo DragOver --> потянул мышь на TPanel
TMemo DragOver
[.....] TPanel DragOver -->тянем Memo над панелькой
TPanel DragOver
TPanel DragOver
[.....] TPanel DragDrop --> отпустил кнопку мыши
TMemo EndDrag

При этом стоит отдельно отметить — когда я зажимаю левую кнопку мыши, то срабатывает именно StartDrag и сразу же 2 раза DragOver и пока я не начну движение мышкой события срабатывать не будут. Почему обращаю на это внимание? Смотрим, что происходит в приложении FireMonkey:
TMemo DragEnter -->зажал левую кнопку мыши
TMemo DragOver
TMemo DragOver
[.....] TMemo DragOver --> потянул мышь на TDropTarget
TMemo DragOver
TMemo DragOver
[.....] TMemo DragOver
TMemo DragLeave --> курсор мыши ушел из Memo
TDropTarget DragEnter --> курсор попал в область TDropTarget
TDropTarget DragOver --> держим мышь в одном положении, не отпуская кнопку
TDropTarget DragOver
[...] TDropTarget DragOver
TDropTarget DragOver
TDropTarget DragOver
TDropTarget DragLeave --> отпустили кнопку мыши

Первое, на что обратил внимание — это на срабатывание событие DragOver. Молотит даже, когда мышь стоит неподвижно. Что, на мой взгляд, не совсем хорошо, т.к. мало ли, что будет записано в обработчике и его постоянные срабатывания без повода могу повлиять на работу приложения. По-хорошему бы добавить в FMX такой замечательный тип как TDragState (как это сделано в VCL), но, чего не нашел в FireMonkey, того не нашел.

Второй момент — вместо ожидаемого события DragDrop, которое должно сигнализировать нам, что источник упал на приемник, почему-то сработало событие DragLeave, которое недвусмысленно сообщило, что источник упал не там где надо. И при этом так и не сработал DragEnd. Разберемся почему произошло не то, на что мы рассчитывали.

После небольшой проверки параметров в событии OnDragOver оказалось следующее: в VCL параметр Accept по умолчанию равен True, т.е. как только над панелькой в VCL-проекте зависла мышка с чем-то «в лапах», то TPanel автоматом стала приемником, а раз так, то DragDrop срабатывает.

В FireMonkey же все наоборот — по умолчанию Accept равен False и OnDragDrop не сработает до тех пор пока мы явно не изменим значение параметра на True.

С одной стороны такое положение дел с Accept несколько сбивает с толку, но, думаю, что такая организация работы для FireMonkey скорее плюс, чем минус и к этому просто надо привыкнуть.

Второй момент относительно параметра Accept при работе с TDropTarget можно озвучить следующим образом:

TDropTarget будет автоматически становиться приемником в случае, если у него заполнено свойство Filter и над компонентом «висит» хотя бы один файл, удовлетворяющий текущему фильтру.

Проверить это довольно просто. Убираем все явные присваивания Accept:=True из кода, добавляем в свойство Filter компонента строку ‘*.txt’, запускаем программу и пробуем бросить на TDropTarget текстовый файл — несмотря на то, что в обработчике события Accept так и будет оставаться в значении False, событие DragDrop сработает.

Вот чего не смог найти, так это того как заставить срабатывать событие OnDragEnd, но зато нашел у DropTarget’a два идентичных по содержанию события, которые работают:

property OnDropped: TDragDropEvent read FOnDrop write FOnDrop;
property OnDragDrop: TDragDropEvent;

OnDropped срабатывает именно в том месте, где по логике VCL должен бы сработать EndDrag, так что, в принципе, может нам и не нужно OnDropEnd

Что ещё следует учитывать при работе с Drag&Drop в FireMonkey? Из того, что обнаружил пока писал этот пост — это отсутствие методов у компонентов FMX методов:

BeginDrag();
EndDrag();

Которые позволяли нам при работе с VCL запускать механизм D&D вручную. Поэтому получается следующая ситуация: если у компонента DragMode = dmAutomatic, то Drag&Drop запускается автоматически, если  dmManual — то не работает вообще.

Теперь разберемся, что у нас есть в распоряжении при работе с механизмом Drag&Drop.

TDropObject

TDropObject предоставляет нам информацию о том, что пользователь пробует перетащить на приемник и  выглядит следующим образом:

  TDragObject = record
    Source: TObject;
    Files: array of string;
    Data: TValue;
  end;

Здесь Data содержит информацию, полученную из Source или Files (в зависимости от того, что перетаскивается)
Source — если содержит не nil, то переносится какой-либо объект
Files — содержит список файлов, которые переносятся на источник.

Посмотрим, что будет содержать TDragObject при перетаскивании на наш компонент TDragTarget разных элементов. Напишем такой обработчик для OnDropped:

procedure TForm9.DropTarget1Dropped(Sender: TObject; const Data: TDragObject;
  const Point: TPointF);
var
  I: Integer;
begin
  if Assigned(Data.Source) then
    Memo1.Lines.Add('Data.Source ='+Data.Source.ClassName);
 
  for I := Low(Data.Files) to High(Data.Files) do
    Memo1.Lines.Add('File: '+Data.Files[i]);
 
  Memo1.Lines.Add('Data.Data.Kind: '+GetEnumName(TypeInfo(TTypeKind),ord(Data.Data.Kind)));
end;

В обработчике OnDragOver разрешим перетаскивать, что угодно:

procedure TForm9.DropTarget1DragOver(Sender: TObject; const Data: TDragObject;
  const Point: TPointF; var Accept: Boolean);
begin
  Memo1.Lines.Add(Sender.ClassName+' DragOver '+BoolToStr(Accept,True));
  Accept:=True
end;

Теперь запустим приложение и попробуем перетащить и бросить на TDropTarget какой-нибудь компонент. В Memo мы получим следующую запись:
Data.Source =TMemo
Data.Data.Kind: tkUnknown

Получив в поле Source перетаскиваемый объект мы уже можем выполнять какие-то действия над ним (скопировать текст из Memo и вставить его в другой компонент, очистить и т.д.).
Соответственно, если мы перетащим на компонент группу файлов, то получим, например, такую запись:

File: С:\Temp.txt
File: С:\Temp2.txt
File: С:\Temp.dat
Data.Data.Kind: tkUString

Теперь немного изменим задачу — разрешим перетаскивать на TDropTarget только текстовые файлы. Изменяем свойства у TDropTarget следующим образом:

Filter:='*.txt'
FilterIndex:=1

Из DragOver убираем строку

Accept:=True

Запускаем приложение и пробуем бросить на компонент группу файлов с разными расширениями — txt, dat, dpr и т.д. В Memo получим:

TDropTarget DragDrop
File: C:\Temp.dpr
File: C:\Temp.dat
File: C:\Temp.txt
Data.Data.Kind: tkUString

На это стоит обратить внимание.

При перетаскивании на TDropTarget группы файлов в которой хотя бы один файл удовлетворяет фильтру поле Data.Files будет содержать все имена файлов вне зависимости от того удовлетворяют ли эти файлы фильтру или нет

Вот, пожалуй и все, что имеется у нас для работы с Drag&Drop. Настало время использовать полученные знания на примерах.

Вместо примеров

Признаться на этом самом месте статьи я планировал привести несколько примеров Drag&Drop в FireMonkey, в т.ч. перетаскивание узлов между двумя деревьями. Но, когда дело дошло до работы над примерами очень быстро понял — без большущего напильника тут не обойтись…Нет с TDropTarget вроде бы все в порядке — компонент, в принципе, выполняет возложенные на него функции, но вот другие компоненты — это что-то с чем-то в плане Drag&Drop. Поэтому здесь я решил просто выложить то, с чем Вам, возможно, придется столкнуться и некоторые решения проблем от других разработчиков.

QC 110783 — TreeView dragging requires two mouse clicks to initiate. Для того, чтобы инициировать Drag&Drop необходимо дважды кликнуть мышкой по узлу TreeView. Первый клик — определяем Selected, второй клик — запускаем Drag&Drop.

Аналогичная ситуация наблюдается и у ListBox

QC 110721 — TreeView drag over highlighted target is wrong. Неправильно подсвечивается элемент дерева на который мы хотим «бросить» узел. Более того, при перетаскивании Bitmap созданный из узла, который должен находиться всегда вблизи курсора, иногда почему-то смещается влево от курсора пикселей на 50 — тащишь узел мышкой, а его изображение болтается где-то за формой.

QC 110646 — Drag/Drop between TreeViews: Destination must have Selected <> nil. В дереве-приемнике обязательно должен быть выделен хотя бы один узел, чтобы сработало событие OnDragDrop.

А если в приемнике вообще нет узлов? …А нечего дерево без узлов держать! :) 

Все три бага в итоге решены самим же отправителем. Скачать компонент TreeView с решенными проблемами Drag&Drop Вы можете здесь.

Вот такая ситуация с Drag&Drop в FireMonkey. Будем ждать Update 2…

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
4 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
trackback

[…] по другому работают стили, по другому реализован механизм Drag&Drop. И даже имея более-менее целостное представление о том […]

Максим
Максим
02/03/2017 21:52

Добрый день.
Подскажите, в Delphi Berlin 10.1 почему-то не срабатывает данный пример, хотя в XE7 завелось все прекрасно.
ну что необходимо обратить внимание?
Заранее благодарен

Maxim Voland
02/03/2017 21:56

Добрый день.
Не знаю отправился ли предыдущий комментарий.
Повторюсь с вопросом, если что простите!
В Berlin 10.1 не срабатывает Droptarget, хотя на примере этой статьи в ХЕ7 все прекрасно работает.
Не подскажете как выкрутиться?
Заранее благодарен