Сегодня поговорим снова о FireMonkey. На этот раз я решил разобраться с тем как работает Drag&Drop в FMX 2.0., посмотреть с чем мы можем работать при разработке проектов как под Windows, так и под Mac OS. В общем провести небольшое такое исследование на тему.
Конечно, то, что FireMonkey — это платформа для разработки приложений под различные операционные системы, даст о себе знать и придется вникать в тонкости работы. В общем
Разбираться я начал с самых, что ни есть простых моментов по работе с 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 срабатывает.
С одной стороны такое положение дел с Accept несколько сбивает с толку, но, думаю, что такая организация работы для FireMonkey скорее плюс, чем минус и к этому просто надо привыкнуть.
Второй момент относительно параметра Accept при работе с TDropTarget можно озвучить следующим образом:
Проверить это довольно просто. Убираем все явные присваивания 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
На это стоит обратить внимание.
Вот, пожалуй и все, что имеется у нас для работы с Drag&Drop. Настало время использовать полученные знания на примерах.
Вместо примеров
Признаться на этом самом месте статьи я планировал привести несколько примеров Drag&Drop в FireMonkey, в т.ч. перетаскивание узлов между двумя деревьями. Но, когда дело дошло до работы над примерами очень быстро понял — без большущего напильника тут не обойтись…Нет с TDropTarget вроде бы все в порядке — компонент, в принципе, выполняет возложенные на него функции, но вот другие компоненты — это что-то с чем-то в плане Drag&Drop. Поэтому здесь я решил просто выложить то, с чем Вам, возможно, придется столкнуться и некоторые решения проблем от других разработчиков.
QC 110783 — TreeView dragging requires two mouse clicks to initiate. Для того, чтобы инициировать Drag&Drop необходимо дважды кликнуть мышкой по узлу TreeView. Первый клик — определяем Selected, второй клик — запускаем Drag&Drop.
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…


[…] по другому работают стили, по другому реализован механизм Drag&Drop. И даже имея более-менее целостное представление о том […]
Добрый день.
Подскажите, в Delphi Berlin 10.1 почему-то не срабатывает данный пример, хотя в XE7 завелось все прекрасно.
ну что необходимо обратить внимание?
Заранее благодарен
Добрый день.
Не знаю отправился ли предыдущий комментарий.
Повторюсь с вопросом, если что простите!
В Berlin 10.1 не срабатывает Droptarget, хотя на примере этой статьи в ХЕ7 все прекрасно работает.
Не подскажете как выкрутиться?
Заранее благодарен
К сожалению, не подскажу, в 10.1 с Drag&Drop не работал