Problem Description

Here's is a drag/drop demo program exploring some of the concepts and techniques for dragging images.

Background & Techniques

I recently decided to create a "human intelligence" version of our Cutlist program which will allow users to drag required parts outlines onto available supply pieces as an alternative to the current program's search and place algorithm.  Once a project has more than 9 or 10 pieces, the number of arrangements to check makes finding an optimal solution through brute force search unlikely.  And, as  a user pointed out recently, hardwood is quite expensive and there may be knots other defects in the supply pieces that the user would want to manually exclude from consideration.  I decided to master some of the general drag/drop techniques preliminary to  tackling the complete Cutlist project.

Scroll boxes

In addition to the drag drop problem,  I wanted two separate scroll boxes to be able to handle lots of "pieces" (Tshape controls in this demo) on the left and  and supply parts (Tpanels) on the right.   For the rest of this discussion "shape" and "piece" will be used interchangeably.  The drag/drop event exits for both shapes and panels are set dynamically at FormCreate time.   Even though they could have been set using the object inspector here, the real application will be creating the parts from project description files.   At initialization time we also place the shapes in a TObjectList named PieceList.  The piece list will help us check for overlaps with other pieces with the same parent when we drop a piece.  

Dropping shapes

When shapes are dropped on the panels, we need to make sure that they do not overlap each other. To check this,  the  shapes in a Piecelist  change their parent property as they are moved to new locations.  A check must be made to ensure that the piece being dropped does not overlap any other piece.  The Windows API function IntersectRect called from the Overlaps function makes this an easy test.   

The Snap function

Users may want to make the pieces abut each other as closely as possible without overlapping.  I added a "Snap" function to accomplish this.  If the Snapbox checkbox is checked, each dropped panel will alternately move up and left until it can move no further.  Defining the tests to do this was the most challenging (i.e. most fun) part of the project.   

Dragging Images

 The steps required to drag images seem more complicated one would expect, but the "cookbook" approach works  OK.

  • Drop a TImageList on the form.  It is a descendant of TDragImageList and has all we need for defining the image (or images) to be displayed while dragging.  It would have been possible to define all of the images initially and choose which one to display at start drag time.  I chose the approach of defining each drag image at start drag time so there is always only a single image in the list.
  • Define a descendant of  TDragObject to override the default GetDragImages.  The default function returns nil so no drag image is displayed.  Our GetDragImages function just  returns the address of our Dragimagelist to tell Delphi that images are to be drawn as dragging takes place
  • An OnStartDrag event exit for the shape being dragged has several tasks:
    • Clear the DragImageList, build  a bitmap the size and color of the shape being dragged and add it to the image list.
    • Call SetDragImage method of our DragImageList to tell it to use image 0, the first (and only) image in the list. 
    • Create a TDragObject specifying the actual shape object being dragged and return in the DragObject parameter of the start drag procedure. The drag object becomes the Source parameter for other dragover and dragdrop event exits.  It contains a property named Control which points to the real shape being dragged. 
  • Any control where you want the image to be displayed must have csDisplayDragImage constant added to it's ControlStyle property.  Another job we perform in the  FormCreate  method.
  • We need OnDragover event exits for shapes, scroll boxes and panels to set the Accept parameter which controls the dragcursor to be displayed determines if the dragdrop exit needs to be taken.    For the shapes event, I do not allow a shape to be dropped on any event except itself (i.e. when making a small adjustment to its position).  Scrollbox1, the home base for the shapes, and the panels will accept only shapes that do not overlap other shapes.  The DragCursor property of the control could also be changed here but by default Delphi uses the crDrag cursor for accept condition and the crNoDrop cursor if the piece cannot be dropped here.  
  • Our Ondragdrop exits  make a final check for no overlap (probably unnecessary), change the parent of the piece being dropped, change the coordinates to the new location, and  if we're dropping on a panel and the option is set,  "snaps" the shape up and left as far as possible without overlapping other pieces already in place. 

Still a long ways from what we'll need for CutList-HI, but it's a good start.

Running/Exploring the Program 

Suggestions for Further Explorations

A few ideas I didn't figure out or get around to implementing:

  1. Change the image of the shape being dragged somehow to indicate that it is the source.  e.g. grayed or dotted outline, made invisible, etc.
  2. I wanted a second drag image to distinguish where a drop was allowed or not allowed.  The drag cursor reflects that status, but I thought some other change to the actual drag image would be good.  However, it looks like once a drag image is selected, it cannot be  modified during that drag operation.
  3. If a drop fails, the dragged image just disappears.  The original piece is still in place at the drag start location, but it would be cool to animate the movement of the drag image back to where it started.      

 

Original Date: April 30, 2006 

Modified: November 07, 2008

 


  [Feedback]   [Newsletters (subscribe/view)] [About me]
Copyright © 2000-2008, Gary Darby    All rights reserved.