Pinning Drag-and-drop to the mat: a primer
Having just wrestled with drag-and-drop for the last few hours, I figured I'd better document what I've been through in the hope of saving someone (maybe myself) some time the next time this comes up. What I wanted to do seemed like a simple task: drag the gradient array from one graphic image to another. This ended up with several hours of experimenting and seemingly unexplainable failures, finally ending up with a bunch of "put into msg" statements to figure out what was going on.
This lesson does not apply on mobile because the drag messages are Desktop only.
To start with, the documentation is a bit misleading, so let's take a look at the available commands:
dragStart : sent to source control
dragEnter : sent to possible destination
dragLeave : sent to possible destination (not used here)
dragDrop : sent to destination control
dragEnd : sent to source control (not used here)
and the engine properties of interest - these can be queried any time drag-and-drop is in effect:
the dragSource
the control the drag started from (not used here)
the dragDestination
the control the data was dropped onto
the allowableDragActions
one or more of {none or move or copy or link}
the dragAction
one of {none or move or copy or link}
the dragData
the data being dragged
dragStart
In chronological order, the dragStart message is sent to the source control when you start dragging from it. If you want to be able to drag it or its contents or something else from a control then you must have a dragStart handler either in the control or in its path. This could possibly mean in the owning group or in a behavior control.
In the dragStart handler you need to specify what will be dragged and in what format it will be supplied. Note in particular that you can *not* use arrays as draggable data - if you need to drag array data then you need to combine it into a string representation before setting it as the dragData. See dragData below.
So here's what happens when I start dragging from one of my gradient source controls:
on dragStart
local tGradient
-- start dragging the gradient to the strokeGradient box
set the allowableDragActions to "copy"
set the dragAction to "copy"
put the fillgradient of the target into tGradient
-- convert the gradient array to string data
combine tGradient using tab and ":"
-- say what type of gradient this is
set the dragData["private"] to "fill" & cr & tGradient
pass dragStart
end dragStart
the dragData
The documentation for the dragData property says "the dragData is an array with one or more of the following elements:" (text, html, rtf, Unicode, image, files, styles, private), but don't construe that to mean that you can do something like
on dragStart
set the dragData["text"] to "hello, world"
set the dragData["private"] to "top secret"
end dragStart
because the second incarnation ("private") will overwrite the first and you will lose the "text" part. For this reason (and after much experimenting) I ended up using "private" for all my dragData. That way I could put "fill" or "stroke" into line one of the dragData and fill the rest with with string gradient values.
dragEnter
The dragEnter message is sent to a control when the mouse moves over it while dragging data. This is essential - if you don't have a dragEnter handler in the destination control or somewhere in its path then it will not be a candidate for dropping the data (the cursor will not change to a drop cursor and you won't be able to drop data onto it).
Another way of saying this is that if you want to enable a control as a destination for dropped objects you need to add a dragEnter handler to its script.
on dragEnter
set the dragAction to "copy"
pass dragEnter
end dragEnter
dragLeave
The dragLeave message is sent to a control when the mouse leaves it. I didn't need to use the dragLeave message, but you could use it if you wanted to control the visual display of the image, for example.
the dragSource
In my case I wanted to make sure that the data being dropped came from one of the gradient controls, so I wanted to check the dragSource. Here's another case where the documentation failed me. The documentation says the dragSource contains the long id of the control where the drag started from, and it does. However, the example in the documentation
if the short name of the dragSource is empty then beep
doesn't work because you can't get the short name of the dragSource (you'll get a runtime error if you try). What does work, however, is
put the dragSource into tSource
if the short name of tSource is empty then beep
In the end I opted not to use the dragSource and instead to place the source type as the first line of my private data. Using the dragSource would have worked as well, but for my needs here it would have limited dragging from only those objects, and I wanted a more generic solution.
the dragDestination
The dragDestination is also a long id, this time of the control that is the recipient of the dropped data. See the dragDrop description below to see how I'm using this.
dragDrop
The dragDrop message is sent when the mouse is released over a valid drop candidate control. Here's where you want to handle getting the dragData information and doing something with it. As noted above, I opted not to use the dragSource to determine the type of gradient and so I'm extracting that data from the first line of the private data. Then I delete the first line since I'm done with it, and what's left over is the contents of the gradient array. I copy the gradient array I extracted from the dragData over to the dragDestination control.
My dragDrop handler looks like this:
on dragDrop
local tFillArray
local tDestination
local tType
-- see where we're dropping the data
put the dragdestination into tDestination
put the dragData["private"] into tFillArray
-- extract the data type
put line 1 of tFillArray into tType
-- we're done with the data type
-- what's left over is the gradient data as a set of strings
delete line 1 of tFillArray
-- turn gradient data back into an array
split tFillArray with tab and ":"
if the keys of tFillArray is not empty then
-- handle the different types of gradients
if tType is "stroke" then
set the uStrokeGradient of tDestination to tFillArray
else
set the uFillGradient of tDestination to tFillArray
end if
end if
-- play well with others
pass dragDrop
end dragDrop
dragEnd
The dragEnd message is sent to the source control after the drop has occurred. I didn't need to do any postprocessing, so I'm not using this message. But here's where you might check the dragDestination to see where the data went, especially if you're moving data rather than copying it.
So here's the destination group arrangement I ended up with...
The script of my group of destination controls has these handlers:
dragStart
dragEnter
dragDrop
control 1
control 2
control 3
control 4
-- can drag from sources to these controls (dragEnter and dragStop)
-- or from one control to another (dragStart)
...and the source control group arrangement
group of source controls
dragEnter (enable the controls as destination points)
gradient source 1
dragStart (these enable the drag source functionality)
dragDrop
gradient source 2
dragStart
dragDrop
-- can drag from source 1 to source 2 and vice versa
Trevix
This doesn't work on mobile.
It should be stated.
Elanor Buchanan
Hi Trevix
Thanks for bringing that to our attention. I have added a desktop tag and a note to the introduction.
Kind regards
Elanor
Robert
Thanks! It is indeed not obvious to get the sequences right, nor which handlers need to be present to get it all to work.
Eric Guerin
Any change you could update or augment this on how this would work for mobile?
Elanor Buchanan
Hi Eric
Most of these drag messages are desktop only. The grab command does work on mobile which you might find useful. We have a lesson on using the grab command here.
https://lessons.livecode.com/m/4071/l/743709-how-do-i-create-and-move-graphics-in-an-app
I hope that helps.
Elanor