LCL Drag Drop/ru
│
English (en) │
français (fr) │
русский (ru) │
Руководство DoDi по Dragging (перетаскиванию), Dropping (бросанию компонента) и Docking (стыковке)
Элементы управления в графическом интерфейсе можно захватить с помощью мыши, перетащить в другие места и перетащить на другие элементы управления или на рабочий стол. Когда перетаскиваемый элемент управления перемещается в другое место, операция называется "docking". В противном случае исходный и целевой элементы управления могут обмениваться информацией или взаимодействовать любым другим способом, что называется операцией "drag-drop" (тащи-бросай).
Но все это не работает само по себе, приложение должно настроить разрешенные операции, чтобы LCL знал, что делать с каждым элементом управления в графическом интерфейсе. Сложные операции требуют дополнительного кода для управления визуальной обратной связью с пользователем приложения и того, что произойдет, когда перетаскиваемый элемент управления, наконец, будет отброшен.
Для простой поддержки стыковки во всех приложениях вы можете использовать EasyDockingManager или Anchor Docking, что сводит к минимуму необходимые адаптации.
Drag Drop
Это простое и изначально действие только перетаскивания. Участники:
- Позволяющий перетаскивать себя элемент управления в графическом интерфейсе
- Объект перетаскивания, производный от
TDragObject
- Вспомогательные функции
- События
Теперь давайте посмотрим, как они работают вместе.
Источник перетаскивания
Элемент управления можно сделать перетаскиваемым, установив для его свойства DragMode
значение dmAutomatic
. Когда пользователь нажимает левую кнопку мыши на таком элементе управления, указатель мыши превращается в индикатор перетаскивания, пока пользователь не отпустит кнопку или не отменит операцию, нажав клавишу Escape
. Индикатор теперь следует за движениями мыши и меняет свою форму в соответствии с тем, что произойдет, когда элемент управления будет брошен в текущее место.
Перетаскивание также можно запустить в коде, вызвав SourceControl.BeginDrag
. В любом случае вызывается вспомогательная процедура DragManager.DragStart
для начала перетаскивания. DragManager
- это глобальный экземпляр, реализующий фактическое перетаскивание. Он инициализируется значением по умолчанию TDragManagerDefault
и может быть заменен программистом (Вами). В зависимости от DragKind
элемента управления запускается операция перетаскивания или стыковки. Исходный элемент управления запускает событие OnStartDrag
, в котором программист может предоставить пользовательский объект перетаскивания. Если такой объект не указан, создается объект по умолчанию (TDragControlObject
или TDragDockObject
). Инициализируются внутренние структуры данных для визуальной обратной связи и прочего.
Перетаскиваемый объект (TDragObject, определенный в элементах управления модуля)
Перетаскиваемый объект получает весь пользовательский ввод при перетаскивании. В реализации по умолчанию он реагирует вызовом либо DragMove
, либо DragStop
.
Перетаскиваемый объект также отвечает за визуальную обратную связь:
GetDragCursor
возвращает форму указателя мыши.GetDragImages
возвращает список перетаскиваемых изображений, прикрепляемых к указателю мыши.ShowDragImage
иHideDragImage
реализуют визуальную обратную связь.
Метод EndDrag
уведомляет исходный элемент управления об остановке перетаскивания. В случае отмены операции он вызывает SourceControl.DragCanceled
, за которым следует SourceControl.DoEndDrag
в любом случае, что вызывает событие OnEndDrag
.
События
OnStartDrag/Dock
Это событие происходит при запуске операции. Обработчик может предоставить настраиваемый TDragObject
или TDragDockObject
для операции.
OnDrag/DockOver
Это событие уведомляет возможную цель, когда над ней перетаскивают объект. Обработчик может сигнализировать о принятии или отклонении сброса компонента.
Событие сигнализирует не только о перемещении мыши, но и о том, когда мышь входит в зону целевого элемента управления или покидает ее.
Событие OnDockOver
происходит на DockSite
, а не в целевом элементе управления под указателем мыши. Обработчик может сигнализировать об отклонении сброса и может регулировать визуальную обратную связь. Событие происходит, когда мышь входит в зону элемента управления или выходит из нее, а также при каждом движении мыши.
OnDrag/DockDrop
Это событие уведомляет целевой элемент управления об окончании операции перетаскивания.
OnEndDrag/Dock
Это событие уведомляет исходный элемент управления об окончании операции перетаскивания.
Основные недостатки
- Те же недостатки, что и у Delphi VCL.
- Нет поддержки платформы LCL (кроссплатформа) для перетаскивания компонентов из приложения LCL в любое другое приложение. Чтобы это работало, LCL необходима поддержка протокола XDND в X11, поддержка OLE Drag-n-Drop в Windows и Mac Drag Manager в OSX.
- Нет поддержки платформы LCL (кроссплатформа) для приема сбрасываемых компонентов внутри приложения LCL из другого приложения (рабочего стола, файлового менеджера и т.д.). Обработчик событий
OnDragDrop
всегда ожидает, что перетаскиваемый компонент(Source) будет передаваться как потомокTObject
, что не будет иметь место при перетаскивании объекта из другого приложения в приложение LCL.
Delphi
Drag Drop
Это простое и изначально действие только перетаскивания. Участники:
- Позволяющий перетаскивать себя элемент управления в графическом интерфейсе
- Объект перетаскивания, производный от
TDragObject
- Вспомогательные функции
- События
Теперь давайте посмотрим, как они работают вместе, на основе исходной реализации Delphi для ясности. Реализация LCL несколько отличается в использовании вспомогательных объектов, подробности будут обсуждены позже.
Перетаскиваемый источник(объект)
Элемент управления можно сделать перетаскиваемым, установив для его свойства DragMode
значение dmAutomatic
. Когда пользователь нажимает левую кнопку мыши на таком элементе управления, указатель мыши превращается в индикатор перетаскивания, пока пользователь не отпустит кнопку или не отменит операцию, нажав клавишу Escape. Индикатор теперь следует за движениями мыши и меняет свою форму в соответствии с тем, что произойдет, когда элемент управления будет брошен в текущее место.
Перетаскивание также можно запустить программно, вызвав Source.BeginDrag
. В любом случае вызывается вспомогательная процедура DragInitControl
для начала перетаскивания. Исходный элемент управления вызывает событие StartDrag
, в котором приложение может предоставить настраиваемый объект перетаскивания. Если такой объект не указан, создается объект по умолчанию. Инициализируются внутренние структуры данных для визуальной обратной связи и прочего. Затем вызывается DragTo
, чтобы показать, что перетаскивание началось.
Перетаскиваемый объект (TDragObject, определенный в элементах управления модуля)
Перетаскиваемый объект получает весь пользовательский ввод при перетаскивании. В реализации по умолчанию он реагирует вызовом либо DragMove
, либо DragStop
.
Перетаскиваемый объект также отвечает за визуальную обратную связь:
GetDragCursor
возвращает форму указателя мыши.GetDragImages
возвращает список перетаскиваемых изображений, прикрепляемых к указателю мыши.ShowDragImage
иHideDragImage
реализуют визуальную обратную связь.
Метод EndDrag
уведомляет исходный элемент управления об остановке перетаскивания. В случае отмены операции он вызывает SourceControl.DragCanceled
, за которым следует SourceControl.DoEndDrag
в любом случае, что вызывает событие OnEndDrag
.
Модификации LCL
Текущая реализация LCL удалила обработку ввода из объекта перетаскивания, так что приложение больше не может настраивать пользовательский интерфейс. И реализация LCL далеко не в порядке :-(
Также объект перетаскивания игнорируется при представлении визуальной обратной связи.
Вспомогательные (Helper) функции
Вспомогательные функции в первую очередь позволяют получить доступ к защищенным свойствам и методам элементов управления и других классов извне модуля Controls
. Они также могут выполнять общие задачи и могут инкапсулировать специфические для платформы и другие детали реализации.
Общие помощники
DragTo (Только для Delphi, отсутствует в LCL) определяет текущую цель перетаскивания, отправляет уведомления задействованным элементам управления и управляет визуальной обратной связью. Он также обрабатывает отложенный старт операции перетаскивания, то есть может ждать, пока мышь не уйдет достаточно далеко от начальной точки.
DragDone (Только для Delphi, отсутствует в LCL) завершает перетаскивание либо cбросом элемента управления в контейнер, либо отменой операции.
CancelDrag (Только для Delphi, не путайте с глобальной процедурой LCL CancelDrag
) защищен от ложных вызовов, иначе дальнейшие действия откладываются на DragDone
.
Специальные помощники
Существует еще несколько общедоступных вспомогательных процедур, которые недокументированы в Delphi и не требуются для настраиваемых операций перетаскивания.
DragFindWindow выполняет поиск целевого перетаскиваемого окна (зависит от платформы).
DragInit Инициализирует внутреннее управление, такое как захват мыши, и визуальную обратную связь.
DragInitControl создает объект перетаскивания, затем вызывает DragInit
.
SetCaptureControl обрабатывает захват мышью.
События
OnStartDrag/Dock
Это событие происходит при запуске операции. Обработчик может предоставить настраиваемый DockObject
для операции.
OnDrag/DockOver
Это событие уведомляет возможную цель, когда над ней перетаскивают объект. Обработчик может сигнализировать о принятии или отклонении отбрасывания.
Событие сигнализирует не только о перемещении мыши, но и о том, когда мышь входит в целевой элемент управления или покидает его.
Событие OnDockOver
происходит на DockSite
, а не в целевом элементе управления под указателем мыши. Обработчик может сигнализировать об отклонении сброса и может регулировать визуальную обратную связь. Событие происходит, когда мышь входит в элемент управления или выходит из него, а также при каждом движении мыши.
OnDrag/DockDrop
Это событие уведомляет целевой элемент управления об окончании операции перетаскивания.
OnEndDrag/Dock
Это событие уведомляет исходный элемент управления об окончании операции перетаскивания.
События, специфические для стыковки
(этот раздел нужно переместить в раздел стыковки)
OnGetSiteInfo
Это событие происходит во время поиска подходящего стыковочного узла. Несколько стыковочных узлов могут перекрывать друг друга, располагаться в перекрывающихся окнах или могут быть невидимыми. Каждый кандидат должен предоставить прямоугольник влияния (область захвата) и может сигнализировать об отклонении сброса элемента управления. Диспетчер перетаскивания выбирает наиболее подходящий стыковочный узел в качестве цели для последующих событий OnDockOver
.
OnUnDock
Это событие происходит, когда перетаскиваемый элемент управления необходимо удалить со стыковочного узла.
Непонятно, что должно произойти, если обработчик откажется отстыковывать элемент управления.
LCL реализация
Реализация механизма перетаскивания в Delphi имеет много недостатков, особенно в части стыковки. До сих пор никто, кажется, не понимал деталей модели перетаскивания Delphi, большая часть кода была смоделирована после VCL и, следовательно, реализует точно такое же неправильное поведение. Этот раздел и, что более важно, страница, посвященная стыковке, должны пролить свет на модель перетаскивания в целом, а также на серьезные недостатки дизайна и реализации в реализации Delphi.
Текущая реализация LCL использует объект DragManager и исполнителей drag/dock вместо вспомогательных функций. Причина этого критического изменения неясна - кто сможет объяснить?
Хотя модель Delphi должна быть предоставлена по соображениям совместимости, введение класса и объекта DragManager(singleton) позволяет реализовать альтернативный диспетчер перетаскивания с улучшенным поведением и позволяет приложению выбирать наиболее подходящий диспетчер.
DragManager
Этот объект обрабатывает захват ввода, предоставляет вспомогательные методы и сохраняет параметры перетаскивания и стыковки.
Сомнительно, чтобы объекту перетаскивания не разрешалось обрабатывать ввод пользователя. Это делает систему более безопасной, но не позволяет приложениям расширять пользовательский интерфейс для определенных операций перетаскивания.
Для вспомогательных методов требуются разные вызовы, но это безвредно, если они вызываются только из компонентов LCL.
Более важным является хранение параметров перетаскивания в объекте DragManager, которые исчезают при установке другого диспетчера перетаскивания. Это фатально в случае списка зарегистрированных стыковочных узлов :-(
Исполнители механизма перетаскивания
Внутреннее использование разных классов для перетаскивания и стыковки допустимо, поскольку процедуры значительно различаются между операциями drag-drop и drag-dock. Тем не менее общие действия должны использовать общий код, например унаследован от общего базового класса.
Диаграмма последовательности сообщений или диаграмма последовательности UML
Предоставлено Tom Verhoeff
Что-то вроде линий (это для Delphi; просмотр с моноширинным шрифтом):
Main Drag (Optional) Drag User Event Source Drag Target (Mouse) Loop Control Object Control = = = = | | | | 1|--MouseDown--->| | | | 2#--OnMouseDown->| | | | 3# | | 4#<-BeginDrag()--# | | # # | | | | | | 5#--OnStartDrag->| | | # 6#----Create---->= | | | # | | 7|--MouseMove--->| | | | | 8#-----------------------------------OnDragOver->| | # | | 9# | # | | # | | | | | 10|---MouseUp---->| | | | | 11#-----------------------------------OnDragDrop->| | # | | 12# | # | | # | #---OnEndDrag-->| | | | | 13#----Destroy--->= | | | | |
DragObject
не является обязательным. Может быть несколько потенциальных целей.
Цикл главного события определяет, какое событие и какому объекту отправлять,
в зависимости от действия пользователя, положения курсора и текущего состояния
графического интерфейса.
4 некоторым образом запускает 5 есть задержка в зависимости от Boolean-параметра
Immediate в BeginDrag
).
6 определяет местоположение MouseDown
и может использовать это для принятия решения,
что перетаскивается (через GetCursorPos
, ScreenToClient
, MouseToCell
).
Он может вызывать SetDragImage
для визуализации перетаскиваемого объекта.
Цепочка 7, 8, 9 может повторяться; 9 указывает на приемлемость
сброса компонента на эту цель через Boolean-параметр var Accepted
.
12 вызывается только в том случае, если непосредственно предшествующий событию OnDragOver
этой цели
вернул Accepted = True
; т.е. Accepted
- предварительное условие 12.
13 получает параметр Target
, чтобы узнать, где был принят брошенный компонент;
Если Target=nil
, то сброс компонента был отменен пользователем
(MouseUp
в недопустимом месте), а OnDragDrop
не было выполнено;
Если Target<>nil
, то OnDragDrop
уже был выполнен на Target
.
Таким образом, работа, связанная с фактическим сбросом, делится
на OnDragDrop
(для действий в целевом объекте) и
OnEndDrag
(для действий в источнике). Поскольку объект перетаскивания необходимо
уничтожить, независимо от того, было ли перетаскивание успешным или отмененным,
лучше всего вызывать Free
(или Destroy
) только в обработчике OnEndDrag
.
В любой момент может быть активным только одно перетаскивание; Он либо завершается сбросом компоента, либо отменой сброса.
Есть много других вещей, которые нужно включить, но это основная часть. (Можно также посмотреть Delphi-Central или "Implementing drag-and-drop in controls".)
Надеюсь, это поможет. Tom