Sign in Join
Wiki copytemporary

LCL Drag Drop: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11DoDi's Guide to Dragging, Dropping and Docking
22
33Controls in a GUI can be grabbed with the mouse, dragged to other locations, and dropped onto other controls or onto the desktop. When the dragged control is moved to a different location, the operation is called [[LCL Drag Dock|"docking"]]. Otherwise the source and target control can exchange information or interact in any other way, what's called a "drag-drop" operation.
44
55But all that does not work by itself, an application must set up the allowed operations so that the LCL knows what to do with every control in a GUI. Sophisticated operations require further code for managing the visual feedback to the application user, and what will happen when the dragged control finally is dropped.
66
77For simple docking support in entire applications you can use either [[EasyDockingManager]] or [[Anchor Docking]], that reduces the required adaptations to a minimum.
88
99## Drag Drop
1010
1111This is the simple and originally only dragging action. The players are:
1212
1313- A draggable source control in the GUI
1414- A drag object, derived from TDragObject
1515- Helper functions
1616- Events
1717
1818Now let's have a look at how these are working together.
1919
2020### The Drag Source
2121
2222A control can be made draggable by setting its **DragMode** property to **dmAutomatic**. When the user presses the left mouse button over such a control, the mouse pointer changes into an dragging indicator until the user releases the button, or cancels the operation by pressing the Escape key. The indicator now follows the mouse movements and changes its shape, according to what will happen when the control is dropped in the current location.
2323
2424Dragging also can be started in code, by calling *SourceControl*.**BeginDrag**. In either case the helper procedure *DragManager.DragStart* is called to start dragging. The **DragManager** is a global instance implementing the actual dragging. It is initialized with the default *TDragManagerDefault* and can be replaced by the programmer (You). Depending on the control's **DragKind** a drag or a dock operation is started. The source control triggers an **OnStartDrag** event, where the programmer can provide a custom drag object. If no such object is provided, a default object (*TDragControlObject* or *TDragDockObject*) is created. Internal data structures for visual feedback and more are initialized.
2525
2626### The Drag Object (TDragObject defined in unit controls)
2727
2828A drag object receives all user input while dragging. In the default implementation it reacts by calling either DragMove or finally DragStop.
2929
3030The drag object also is responsible for the visual feedback:
3131
3232- **GetDragCursor** returns the mouse pointer shape.
3333- **GetDragImages** returns a list of drag images, attachable to the mouse pointer.
3434- **ShowDragImage** and **HideDragImage** implement the visual feedback.
3535
3636The **EndDrag** method notifies the source control when dragging stops. In case of a cancel'd operation it invokes *SourceControl*.**DragCanceled**, followed by *SourceControl*.**DoEndDrag** in either case, which raises an **OnEndDrag** event.
3737
3838### Events
3939
4040#### OnStartDrag/Dock
4141
4242This event occurs when the operation starts. The handler can provide a custom TDragObject or TDragDockObject for the operation.
4343
4444#### OnDrag/DockOver
4545
4646This event notifies a possible target, when an object is dragged over it. The handler can signal to accept or reject a drop.
4747
4848The event signals not only mouse moves, but also when the mouse enters or leaves a target control.
4949
5050An OnDockOver event occurs on the DockSite, not on the target control under the mouse. The handler can signal to deny an drop, and can adjust the visual feedback. The event occurs when the mouse enters or leaves a control, and with every single movement of the mouse.
5151
5252#### OnDrag/DockDrop
5353
5454This event notifies the target control of the end of a drag operation.
5555
5656#### OnEndDrag/Dock
5757
5858This event notifies the source control of the end of a drag operation.
5959
6060## Major Shortcomings
6161
6262- The same flaws as Delphi's VCL.
6363- No LCL framework (cross-platform) support for dragging from an LCL application to any other application. For this to work, LCL needs support for the XDND protocol under X11, OLE Drag-n-Drop support under Windows and Mac Drag Manager under macOS.
6464- No LCL framework (cross-platform) support for receiving drops inside an LCL application from another application (desktop, file manager, etc). The OnDragDrop event handler always expects the Source of the drop to come through as a TObject descendant which will **not** be the case when dragging an object from another application to a LCL application.
6565
6666## Delphi
6767
6868### Drag Drop
6969
7070This is the simple and originally only dragging action. The players are:
7171
7272- A draggable source control in the GUI
7373- A drag object, derived from TDragObject
7474- Helper functions
7575- Events
7676
7777Now let's have a look at how these are working together, based on the original Delphi implementation for clarity. The LCL implementation is somewhat different in the use of helper objects, details will be discussed later.
7878
7979#### The Drag Source
8080
8181A control can be made draggable by setting its **DragMode** property to **dmAutomatic**. When the user presses the left mouse button over such a control, the mouse pointer changes into an dragging indicator until the user releases the button, or cancels the operation by pressing the Escape key. The indicator now follows the mouse movements and changes its shape, according to what will happen when the control is dropped in the current location.
8282
8383Dragging also can be started in code, by calling source.BeginDrag. In either case the helper procedure DragInitControl is called to start dragging. The source control raises an StartDrag event, where the application can provide a custom drag object. If no such object is provided, a default object is created. Internal data structures for visual feedback and more are initialized. Then DragTo is called to show that dragging has started.
8484
8585#### The Drag Object (TDragObject defined in unit controls)
8686
8787A drag object receives all user input while dragging. In the default implementation it reacts by calling either DragTo or finally DragDone.
8888
8989The drag object also is responsible for the visual feedback:
9090
9191- GetDragCursor returns the mouse pointer shape.
9292- GetDragImages returns a list of drag images, to be attached to the mouse pointer.
9393- ShowDragImage and HideDragImage implement the visual feedback.
9494
9595The Finished method notifies the source control when dragging stops. In case of a cancel'd operation it invokes source.DragCanceled, followed by source.DoEndDrag in either case, which raises an EndDrag event.
9696
9797#### LCL Modifications
9898
9999The current LCL implementation has removed input processing from the drag object, so that the application can no more customize the UI. And the LCL implementation is far from being okay :-(
100100
101101Also the drag object is bypassed in the presentation of the visual feedback.
102102
103103### Helper Functions
104104
105105Helper functions primarily allow for access to protected properties and methods of the controls and other classes, from outside the Controls unit. They also can perform common tasks, and can encapsulate platform specific and other implementation details.
106106
107107#### General Helpers
108108
109109**DragTo** (Delphi only, not present in LCL) determines the current drag target, sends notifications to the involved controls, and manages the visual feedback. It also handles the delayed start of a dragging operation, i.e. can wait for the mouse moving away far enough from the starting point.
110110
111111**DragDone** (Delphi only, not present in LCL) terminates dragging, either with a drop or cancel of the operation.
112112
113113**CancelDrag** (Delphi only, do not confuse with the LCL global procedure CancelDrag) is protected against false calls, otherwise it defers further actions to DragDone.
114114
115115#### Special Helpers
116116
117117Some more public helper procedures exist, which are undocumented in Delphi and are not required for customized dragging operations.
118118
119119**DragFindWindow** searches for a drag target window (platform specific).
120120
121121**DragInit** initializes the internal management, like mouse capture, and the visual feedback.
122122
123123**DragInitControl** creates the drag object, then calls DragInit.
124124
125125**SetCaptureControl** handles the mouse capture.
126126
127127### Events
128128
129129#### OnStartDrag/Dock
130130
131131This event occurs when the operation starts. The handler can provide a custom DockObject for the operation.
132132
133133#### OnDrag/DockOver
134134
135135This event notifies a possible target, when an object is dragged over it. The handler can signal to accept or reject an drop.
136136
137137The event signals not only mouse moves, but also when the mouse enters or leaves a target control.
138138
139139An OnDockOver event occurs on the DockSite, not on the target control under the mouse. The handler can signal to deny an drop, and can adjust the visual feedback. The event occurs when the mouse enters or leaves a control, and with every single movement of the mouse.
140140
141141#### OnDrag/DockDrop
142142
143143This event notifies the target control of the end of a drag operation.
144144
145145#### OnEndDrag/Dock
146146
147147This event notifies the source control of the end of a drag operation.
148148
149149### Docking specific Events
150150
151151(this section should be moved to docking topic)
152152
153153#### OnGetSiteInfo
154154
155155This event occurs during the search for a qualifying dock site. Multiple dock sites can overlap each other, reside in overlapping windows, or can be invisible. Every candidate must supply an influence rectangle (catching area), and can signal to reject an drop. The drag manager selects the most appropriate dock site as the target for subsequent OnDockOver events.
156156
157157#### OnUnDock
158158
159159This event occurs when the dragged control has to be removed from its host dock site.
160160
161161It's unclear what should happen when the handler denies to undock the control.
162162
163163## LCL Implementation
164164
165165The Delphi implementation of dragging has many flaws, in detail with regards to docking. Until now nobody seems to understand the details of the Delphi dragging model, most code was modeled after the VCL and consequently implements exactly the same misbehaviour. This section, and more important the docking specific page, shall shed some light on the dragging model in general and the severe design and implementation flaws in the Delphi implementation.
166166
167167The current LCL implementation uses a DragManager object and drag/dock performers instead of helper functions. While the Delphi model must be provided for compatibility reasons, the introduction of a DragManager class and object (singleton) allows for the implementation of an alternative drag manager, with improved behaviour, and allows an application to select the most appropriate manager.
168168
169169### DragManager
170170
171171This object handles the captured input, provides helper methods, and stores dragging and docking parameters.
172172
173173It's questionable whether the drag object shall not be allowed to handle user input. It makes the system safer, but disallows applications to extend the user interface for specific dragging operations.
174174
175175The helper methods require different calls, but this is harmless as long as they are only called from LCL components.
176176
177177More critical is the storage of dragging parameters in the DragManager object, which vanish when a different drag manager is installed. This is fatal in case of the list of registered dock sites :-(
178178
179179### Drag Performers
180180
181181The internal use of different classes for dragging and docking is okay in so far, as the procedures differ significantly between drag-drop and drag-dock operations. Nonetheless common actions should use common code, e.g. inherited from the common base class.
182182
183183## Delphi Message Sequence Chart or UML Sequence Diagram
184184
185185Provided by Tom Verhoeff
186186
187187Something along the lines (this is for Delphi; view with monospaced font):
188188
189-`                  Main            Drag          (Optional)        Drag`\
190-`  User            Event          Source           Drag           Target`\
191-` (Mouse)          Loop           Control          Object         Control`\
192-`    =               =               =                               =`\
193-`    |               |               |                               |`\
194-`   1|--MouseDown--->|               |                               |`\
195-`    |              2#--OnMouseDown->|                               |`\
196-`    |               |              3#                               |`\
197-`    |              4#<-BeginDrag()--#                               |`\
198-`    |               #               #                               |`\
199-`    |               |               |                               |`\
200-`    |              5#--OnStartDrag->|                               |`\
201-`    |               #              6#----Create---->=               |`\
202-`    |               |               #               |               |`\
203-`   7|--MouseMove--->|               |               |               |`\
204-`    |              8#-----------------------------------OnDragOver->|`\
205-`    |               #               |               |              9#`\
206-`    |               #               |               |               #`\
207-`    |               |               |               |               |`\
208-`  10|---MouseUp---->|               |               |               |`\
209-`    |             11#-----------------------------------OnDragDrop->|`\
210-`    |               #               |               |             12#`\
211-`    |               #               |               |               #`\
212-`    |               #---OnEndDrag-->|               |               |`\
213-`    |               |             13#----Destroy--->=               |`\
214-`    |               |               |                               |`
189+``` pascal
190+ Main Drag (Optional) Drag
191+ User Event Source Drag Target
192+ (Mouse) Loop Control Object Control
193+ = = = =
194+ | | | |
195+ 1|--MouseDown--->| | |
196+ | 2#--OnMouseDown->| |
197+ | | 3# |
198+ | 4#<-BeginDrag()--# |
199+ | # # |
200+ | | | |
201+ | 5#--OnStartDrag->| |
202+ | # 6#----Create---->= |
203+ | | # | |
204+ 7|--MouseMove--->| | | |
205+ | 8#-----------------------------------OnDragOver->|
206+ | # | | 9#
207+ | # | | #
208+ | | | | |
209+ 10|---MouseUp---->| | | |
210+ | 11#-----------------------------------OnDragDrop->|
211+ | # | | 12#
212+ | # | | #
213+ | #---OnEndDrag-->| | |
214+ | | 13#----Destroy--->= |
215+ | | | |
216+```
215217
216218The DragObject is optional. There could be multiple candidate targets. The Main Event Loop determines which event to send to which object, depending on the user action, the cursor location, and the current state of the GUI.
217219
2182204 somehow triggers 5 (there is a delay depending on the Boolean parameter Immediate to BeginDrag).
219221
2202226 determines the location of the MouseDown and may use this to decide on what gets dragged (through GetCursorPos, ScreenToClient, MouseToCell). It can call SetDragImage to visualize a thing being dragged.
221223
222224The chain 7, 8, 9 can occur repeatedly; 9 indicates acceptability of a drop at this target via the Boolean var parameter Accepted.
223225
22422612 is called only if the immediately preceding OnDragOver of this target returned Accepted = True; i.e. Accepted is a precondition of 12.
225227
22622813 receives the parameter Target to know where the drop was accepted; if Target = nil, then the drop was cancelled by the user (MouseUp at an unacceptable location), and OnDragDrop was not performed; if Target \<\> nil, then OnDragDrop was already performed at the Target. The work associated with an actual drop is thus divided over the OnDragDrop (for actions at the target) and OnEndDrag (for actions at the source). Since a Drag Object needs to be destroyed, independent of whether the drop was successful or canceled, it is best to call Free (or Destroy) only in the OnEndDrag handler.
227229
228230Only one drag can be active at any moment; it either ends in a drop or is canceled.
229231
230232There are various other things to be included, but this is the fundamental part. (Maybe also see [Delphi-Central](http://www.delphi-central.com/drag.aspx) or ["Implementing drag-and-drop in controls"](http://www.podgoretsky.com/ftp/Docs/Delphi/D5/dg/controls.html).)
231233
232234I hope this is helpful. Tom
233235
234236## See also
235237
236238- [[Drag and Drop sample]]
237239
238240---
239241*From the Free Pascal wiki, page "LCL Drag Drop", as it stood on 2023-12-13 after a change by Coz, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history