Sign in Join
Wiki copytemporary

Drag and Drop sample: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Drag and Drop sample

OldNewLine
1+Drag and Drop is a common operation that makes the interface user friendly: a user can drag/drop information to controls instead of having to type etc.
2+
3+The following sample explains basics of drag and drop. For detailed information you should refer to other articles in the wiki and reference documentation.
4+
5+Please note, since LCL is partially compatible with Delphi's VCL, some articles/examples about Delphi drag-and-drop may also apply to LCL.
6+
7+## Drag and Drop
8+
9+Despite of the operation's simplicity from the user's point of view, it might give hard times to an inexperienced developer.
10+
11+For the code, Drag and Drop operation always consists of at least these 3 steps:
12+
13+1. Some control starts the drag-and-drop operation. This is called the Source.
14+2. User drags the mouse cursor around, above other controls or the Source itself. Now a dragged over control needs to decide, if it is able to accept the dragged data.
15+3. Drop happens if a control agrees to accept the dragged data. The accepting control is called the Sender.
16+
17+To simplify drag-and-drop handling, the LCL provides "automatic" mode. It doesn't mean, that LCL does the whole drag-and-drop for you, but it will handle low-level drag object managing (which is not covered in this article).
18+
19+## Example
20+
21+<figure>
22+<img src="DnDTest.PNG" title="DnDTest.PNG" />
23+<figcaption>DnDTest.PNG</figcaption>
24+</figure>
25+
26+The example covers automatic drag-and-drop feature between 2 controls (Edit-\>Treeview) as well as inside a single control (Treeview-\>Treeview)
27+
28+- Start the new application.
29+- Put a TreeView component and Edit on the form.
30+- Enable Automatic drag-and-drop mode for TreeView by setting **DragMode: dkAutomatic**
31+- Edit in Object Inspector and add a few items
32+
33+Drag and drop still won't work, but launching the application and trying to drag the items in the Treeview will show cursor icon changes.
34+
35+### Dragging between controls
36+
37+Let's make a drag-and-drop operation between Edit and TreeView. There the content of Edit will be "dragged" to TreeView and a new tree node created.
38+
39+To initiate the drag, controls have a special method: `BeginDrag()`
40+
41+Create OnMouseDown event for the Edit:
42+
43+``` pascal
44+procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton;
45+ Shift: TShiftState; X, Y: Integer);
46+begin
47+ if Button = mbLeft then {check if left mouse button was pressed}
48+ Edit1.BeginDrag(true); {starting the drag operation}
49+end;
50+```
51+
52+If you launch the application right now and try drag and drop, you'll notice that Edit starts the operation, but still nothing happens then you're trying to drop to TreeView.
53+
54+This is because TreeView doesn't accept the data. None of the controls accept data by default, so you'll always need to provide the proper event handler.
55+
56+Assign TreeView.OnDragOver operation:
57+
58+``` pascal
59+procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
60+ State: TDragState; var Accept: Boolean);
61+begin
62+ Accept := true;
63+end;
64+```
65+
66+Of course in some cases, TreeView might deny dropping (if Source or data cannot be handled), but for now, TreeView always accepts the dragging.
67+
68+Run application and test. Everything should be better now, though still nothing happens on drop.
69+
70+Assign TreeView.OnDragDrop operation:
71+
72+``` pascal
73+procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
74+var
75+ tv : TTreeView;
76+ iNode : TTreeNode;
77+begin
78+ tv := Sender as TTreeView; { Sender is TreeView where the data is being dropped }
79+ iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) }
80+ { since Sender is TreeView we can evaluate }
81+ { a tree at the X,Y coordinates }
82+
83+ { TreeView can also be a Source! So we must make sure }
84+ { that Source is TEdit, before getting its text }
85+ if Source is TEdit then
86+ tv.Items.AddChild(iNode, TEdit(Source).Text); {now, we can add a new node, with a text from Source }
87+end;
88+```
89+
90+Run and test. It should be working now. Dragging a text from Edit to TreeView should create a new node.
91+
92+### Dragging within a control
93+
94+Sender and Source can be the same control! It's not prohibited in any way. Let's add the ability to a TextView, to change its nodes location. Since TextView is in automatic DragMode, you don't need to start the drag by using `DragBegin()`. It's started automatically on mouse moved with left button hold.
95+
96+Make sure you have "Accept:=true;" inside the DragOver operation for TreeView source.
97+
98+Modify the DragDrop event handler to the following:
99+
100+``` pascal
101+procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
102+var
103+ tv : TTreeView;
104+ iNode : TTreeNode;
105+begin
106+ tv := Sender as TTreeView; { Sender is TreeView where the data is being dropped }
107+ iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) }
108+ { since Sender is TreeView we can evaluate }
109+ { a tree at the X,Y coordinates }
110+
111+ { TreeView can also be a Source! So we must make sure }
112+ { that Source is TEdit, before getting its text }
113+ if Source is TEdit then
114+ tv.Items.AddChild(iNode, TEdit(Source).Text) {now, we can add a new node, with a text from Source }
115+
116+ else if Source = Sender then begin { drop is happening within a TreeView }
117+ if Assigned(tv.Selected) and { check if any node has been selected }
118+ (iNode <> tv.Selected) then { and we're dropping to another node }
119+ begin
120+ if iNode <> nil then
121+ tv.Selected.MoveTo(iNode, naAddChild) { complete the drop operation, by moving the selectede node }
122+ else
123+ tv.Selected.MoveTo(iNode, naAdd); { complete the drop operation, by moving in root of a TreeView }
124+ end;
125+ end;
126+end;
127+```
128+
129+That's it. If you run the application now, you should have both features working.
130+
131+- Adding a new node by dragging text from Edit to TreeView
132+- Dragging nodes inside the treeview
133+
134+## Hints
135+
136+- You can?/cannot? use some global data to inspect what is being dragged now. Don't use global variables, but your form class's fields.
137+ - Put the data when you start the drag
138+ - Inspect the data, during control's drag over event, and modify Accept flag accordingly
139+ - Read and use the data on drop event
140+
141+## Dragging from other applications
142+
143+You can drag/drop
144+
145+### Files
146+
147+Files can be dropped and handled easily by implementing the FormDropFiles event, once AllowDropFiles on the form is set:
148+
149+``` pascal
150+procedure TForm1.FormDropFiles(Sender: TObject; const FileNames: array of String);
151+var FileName : String;
152+begin
153+ for FileName in FileNames do
154+ begin
155+ ShowMessage(FileName);
156+ end;
157+end;
158+```
159+
160+### Text etc
161+
162+You can drag and drop e.g. text from another application (e.g. Notepad) to a control on your application. The way this is implemented is platform-dependent.
163+
164+#### Windows
165+
166+This code lets you drag and drop selected text from other applications onto an edit control.
167+
168+``` pascal
169+unit Unit1;
170+
171+{$mode objfpc}{$H+}
172+
173+interface
174+
175+uses
176+ Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
177+ Windows, ActiveX, ComObj;
178+
179+type
180+
181+ { TForm1 }
182+
183+ TForm1 = class(TForm, IDropTarget)
184+ Memo1: TMemo;
185+ procedure FormCreate(Sender: TObject);
186+ procedure FormDestroy(Sender: TObject);
187+ private
188+ // IDropTarget
189+ function DragEnter(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;
190+ function DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;
191+ function DragLeave: HResult;StdCall;
192+ function Drop(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD):HResult;StdCall;
193+ // IUnknown
194+ // Ignore referance counting
195+ function _AddRef: Integer; stdcall;
196+ function _Release: Integer; stdcall;
197+ public
198+ { public declarations }
199+ end;
200+
201+var
202+ Form1: TForm1;
203+
204+implementation
205+
206+{$R *.lfm}
207+
208+{ TForm1 }
209+
210+procedure TForm1.FormCreate(Sender: TObject);
211+begin
212+ OleInitialize(nil);
213+ OleCheck(RegisterDragDrop(Handle, Self));
214+end;
215+
216+procedure TForm1.FormDestroy(Sender: TObject);
217+begin
218+ RevokeDragDrop(Handle);
219+ OleUninitialize;
220+end;
221+
222+function TForm1.DragEnter(const dataObj: IDataObject; grfKeyState: DWORD;
223+ pt: TPoint; var dwEffect: DWORD): HResult; StdCall;
224+begin
225+ dwEffect := DROPEFFECT_COPY;
226+ Result := S_OK;
227+end;
228+
229+function TForm1.DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD
230+ ): HResult; StdCall;
231+begin
232+ dwEffect := DROPEFFECT_COPY;
233+ Result := S_OK;
234+end;
235+
236+function TForm1.DragLeave: HResult; StdCall;
237+begin
238+ Result := S_OK;
239+end;
240+
241+function TForm1._AddRef: Integer; stdcall;
242+begin
243+ Result := 1;
244+end;
245+
246+function TForm1._Release: Integer; stdcall;
247+begin
248+ Result := 1;
249+end;
250+
251+function TForm1.Drop(const dataObj: IDataObject; grfKeyState: DWORD;
252+ pt: TPoint; var dwEffect: DWORD): HResult; StdCall;
253+var
254+ aFmtEtc: TFORMATETC;
255+ aStgMed: TSTGMEDIUM;
256+ pData: PChar;
257+begin
258+ {Make certain the data rendering is available}
259+ if (dataObj = nil) then
260+ raise Exception.Create('IDataObject-Pointer is not valid!');
261+ with aFmtEtc do
262+ begin
263+ cfFormat := CF_TEXT;
264+ ptd := nil;
265+ dwAspect := DVASPECT_CONTENT;
266+ lindex := -1;
267+ tymed := TYMED_HGLOBAL;
268+ end;
269+ {Get the data}
270+ OleCheck(dataObj.GetData(aFmtEtc, aStgMed));
271+ try
272+ {Lock the global memory handle to get a pointer to the data}
273+ pData := GlobalLock(aStgMed.hGlobal);
274+ { Replace Text }
275+ Memo1.Text := pData;
276+ finally
277+ {Finished with the pointer}
278+ GlobalUnlock(aStgMed.hGlobal);
279+ {Free the memory}
280+ ReleaseStgMedium(aStgMed);
281+ end;
282+ Result := S_OK;
283+end;
284+
285+end.
286+```
287+
288+Source forum: <http://forum.lazarus.freepascal.org/index.php/topic,25769.msg156933.html#msg156933>
289+
290+## See also
291+
292+- [[LCL Drag Drop]]
293+
294+---
295+*From the Free Pascal wiki, page "Drag and Drop sample", as it stood on 2023-12-14 after a change by Alextpp, 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