LCL Drag Dock/ru

From Free Pascal wiki

English (en) русский (ru)

Элементы управления или целые формы в графическом интерфейсе пользователя можно склеить и снова отсоединить, перетаскивая их мышью. Такая стыковка похожа на drag-drop, но отличается в некоторых аспектах.

Операции

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

DragInit

Как обычно, при запуске стыковки необходимо активировать визуальную обратную связь.

DragTo

На ходу нужно сделать пару шагов:

  • Хочет ли пользователь пристыковаться вообще
  • Определение возможных целевых стыковочных узлов
  • Определение точной зоны бросания элемента управления
  • Визуальная обратная связь

DragDone

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

  • Элемент управления пристыкован к новому сайту док-станции.
  • Перетаскиваемый элемент управления становится плавающим
    • как есть, если он уже был плавающим, или когда он может стать самостоятельно плавающим это TWinControl),
    • или завернутый в новую плавающую форму.
  • Вся операция прервана.

Обзор стыковочных элементов

Помимо drag-drop участников, на сцену выходят еще несколько участников:

  • Стыковочные узлы (Dock sites)
  • Стыковочные прямоугольники в качестве визуальной обратной связи
  • Диспетчеры стыковки (Dock managers)

Введение диспетчеров стыковки создало кучу новых проблем с плохой интеграцией в модель неуправляемой док-станции. Давайте пока проигнорируем "управляемую" стыковку и рассмотрим только "неуправляемую" стыковку.

Прикрепляемый элемент (Dock Source)

Элемент управления или форму можно сделать прикрепляемыми, установив для его свойства DragKind значение dkDock.

Стыковочные узлы (Dock Sites)

Для стыковки требуются специальные целевые зоны сброса [для прикрепляемого элемента], называемые стыковочными узлами. TWinControl становится такой стыковочной целью, при установке его свойства DockSite в значение True.

Прикрепленные элементы управления находятся в отдельном массиве элементов, отделенном от других компонентов в элементе управления контейнера. Для правильного поведения настоятельно рекомендуется изначально не иметь никаких других элементов управления на стыковочном сайте и заполнять его только закрепленными элементами управления.

Плавающие стыковочные узлы

Другой вид стыковочных узлов используется для незакрепленных (плавающих) элементов управления. Когда элемент управления бросается за пределы стыковочного узла, и это не TWinControl, для него создается новая форма в месте размещения. Когда плавающий элемент управления позже стыкуется со стыковочным узлом, плавающая форма уничтожается.

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

Стыковочный прямоугольник (DockRect)

Как и в операциях drag-drop, при операции стыковки за указателем мыши следует значок в форме прямоугольника. Но значок стыковочного прямоугольника может динамически изменять свой размер и положение, чтобы сигнализировать о положении и размере элемента управления, когда последний бросается в текущем месте.

Когда не удается найти принимающий стыковочный узел или когда пользователь запрещает перетаскивание, удерживая клавишу Ctrl, DockRect отражает плавающий размер и положение перетаскиваемого элемента управления. Плавающий размер инициализируется исходным размером перетаскиваемого элемента управления и обновляется при перемещении элемента управления. В противном случае стыковочный узел может определить размещение и размер брошенного элемента управления.

Реализация Delphi запоминает координаты нарисованного DockRect в EraseDockRect, так что старый фрейм может быть удален с экрана с помощью XOR-рисования, когда это необходимо.

Стыковочный объект (DockObject)

В операции стыковки используется специальный DockObject, производный от TDragObject. Непонятно, почему этот класс вводит новые методы вместо того, чтобы переопределять существующие виртуальные методы для различной визуальной обратной связи.

Унаследованный DragTarget - это целевой стыковочный сайт в стыковке. Добавленные свойства DropOnControl и DropAlign указывают, где и как перетаскиваемый элемент управления должен располагаться относительно уже закрепленного элемента управления.

Помощники (Helpers)

В классы TControl и TWinControl добавлен ряд вспомогательных методов и полей для внутреннего управления закрепленными элементами управления и настройки процесса стыковки. Большинство этих помощников играют особую роль в процессе стыковки и будут описаны в рабочем процессе ниже.

(место для описания общих элементов)

Механика процесса

Это подробное описание необходимых действий до, во время и после закрепления элемента управления.

Предпосылки

Элемент управления становится стыкуемым, если для его DragKind задано значение dkDock. Для стыковочного узла больше ничего не требуется.

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

RegisterDockSite

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

GetSiteInfo

Метод TWinControl.GetSiteInfo возвращает прямоугольник влияния (захвата), связанный со стыковочным узлом. Это соглашение позволяет, например, скрыть стыковочные узлы в графическом интерфейсе, без превращения их в недоступные для удаления. Реализация по умолчанию возвращает видимый размер элемента управления, увеличенный на определенную величину в любом направлении.

DragInit

Стыковка запускается автоматически, когда для элемента управления задано значение DragKind=dkDock и DragMode=dmAutomatic, а на этом элементе управления нажата левая кнопка мыши. TControl предоставляет метод BeginAutoDrag для этого случая и метод BeginDrag для запуска программной стыковки, когда DragMode=dmManual.

DockObject создается либо обработчиком OnDockStart исходного элемента управления (TControl.DoStartDock), либо диспетчером перетаскивания, если такой объект не был предоставлен. DockRect инициализируется прямоугольником элемента управления. Для удобного положения DockRect во время перемещений запоминается смещение указателя мыши на DockRect.

Захват ввода перемещается в элемент управления или в его родительский элемент TWinControl, когда элемент управления не может получать системные (Windows) сообщения.

Когда перетаскивание должно начаться немедленно, вызывается DragTo.

DragTo

DragTo проверяет наличие отложенного запуска и ничего не делает, если порог перемещения мыши еще не достигнут.

В противном случае ищется цель перетаскивания. Когда цель изменяется, DoDragOver отправляет в цель dmDragLeave и dmDragEnter в виде сообщений CM_DRAG.

Сообщение dmDragMove отправляется таким же образом и обрабатывается в TWinControl.DockOver, который позиционирует DockRect и вызывает обработчик OnDockOver. Результат указывает, будет ли принято решение о сбросе, и соответственно обновляется визуальная обратная связь.

Горести Delphi

Следующий шаг в реализации Delphi - фиктивный. Когда cброс(dropping) отклонен, DockRect сбрасывается в плавающее положение и размер перетаскиваемого элемента управления, что уже должно было быть сделано раньше. Кроме того, на стыковочном узле выполняется поиск DropOnControl и определяется DropAlign. Ни одно из этих значений не отражается в ранее определенном DockRect, поэтому предполагаемый эффект становится видимым в лучшем случае (если вообще когда-либо) после следующего движения мыши. Когда DropOnControl не найден, что весьма вероятно на неуправляемом стыковочном узле, DockRect будет охватывать весь стыковочный узел - что не очень важно. Когда DropOnControl найден, значение DropAlign не определено; реализация Delphi использует это значение для настройки DockRect на верхнюю (левую ...) половину DropOnControl, но такое размещение отброшенного элемента управления может иметь смысл только тогда, когда сам DropOnControl соответствующим образом сокращается. В управляемом стыковочном узле DockManager может настраивать DockRect, а затем может уместить затронутые элементы управления в указанные границы, но у него нет возможности самому определить DropOnControl или DropAlign. Таким образом, значение DropOnControl и DropAlign вызывает сомнения как для управляемых, так и для неуправляемых стыковочных узлов.

При лучшем решении этот шаг будет полностью опущен, а обработчики событий и стыковочного узла будут управлять визуальной обратной связью и окончательным сбросом или оставят все это установленному DockManager.

DragDone

Когда перетаскивание завершено, либо при отмене, либо при успешном перетаскивании, захват снимается, а визуальная обратная связь удаляется с экрана. Дальнейшая процедура зависит от успеха операции.

Ручная стыковка

Метод TControl.ManualDock обходит все действия пользователя и закрепляет элемент управления на стыковочном узле или делает его плавающим (TControl.ManualFloat). Если требуется для дальнейшей обработки запроса, должен быть создан и инициализирован DockObject из параметров.

Сброс внутри стыковочного узла

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

Наконец, в соответствии с последним DockRect, элемент управления помещается в новый родительский элемент. В управляемой стыковочном узле DockManager может при необходимости переставлять закрепленные элементы управления.

Сброс в никуда

Перетаскиваемый элемент управления становится плавающим. Форма может перемещаться сама по себе, но для удобства все элементы управления могут быть заключены в специальный контейнер плавающей формы (по умолчанию: FloatingDockSiteClass=CustomDockForm).

Сообщение CM_FLOAT отправляется в исходный код элемента управления. Когда элемент управления уже плавает, он перемещает свой родительский элемент, и все готово.

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

Отмена сброса

Исходный элемент управления получает уведомление DragCanceled, вот и все.

События

Во время стыковки происходят следующие события:

DragInit

Обработчик StartDrag/Dock может возвращать пользовательский Drag/DockObject.

DragTo

При стыковке зарегистрированные стыковочные узлы получают событие GetSiteInfo. Обработчики могут предоставить прямоугольник влияния и указать, допустим ли сброс.

Когда цель изменяется, старая и новая цели получают событие Drag/DockOver с состоянием dmDragEnter/Leave.

Во всех случаях происходит событие Drag/DockOver в состоянии dmDragMove. Обработчик может отклонить перетаскивание и может настроить визуальную обратную связь в данном объекте drag/dock.

DragDone

Конечное значение Drag/DockMove состояния dmDragLeave отправляется цели. Это первое событие в программном сбросе элемента управления (TControl.ManualDock). Состояние лучше должно быть dmDragMove, но реализация Delphi использует dmDragLeave.

При стыковке событие UnDock позволяет исходному сайту отклонить (отменить) сброс. Обработчику событий, стыковочному узлу и стыковочному диспетчеру предлагается последовательно отклонить или выполнить соответствующее внутреннее действие.

Если перетаскивание не отклоняется, возникает событие Drag/DockDrop.

Во всех случаях источник получает событие EndDrag/Dock.