OOP Delphi

Title: Drag and Drop any TObject anywhere
Question: The problem with standard drag-and-drop is that at the drop stage you have to find out what was dragged and ask it for the data you need (e.g. what employee from the grid? which grid? which form?!). Yuck, painful and non-modular. Package a TObject in the drag operation instead!
Answer:
Adding standard drag-and-drop to a gui got painful when my OnDragDrop and OnDragOver events got too messy. I really wanted to pass an object around (e.g. a TEmployee object) and not have to pass a 'TListBox' and then figure out what employee that really was, etc. Plus, if someone later added more drag sources, then the OnDragDrop event would have to be changed. Not nice.
Here is a "TDragAnything" class with which you can start a drag operation in an OnMouseDown event, e.g.
TDragAnything.Start( fEmployee );
where in this case fEmployee is an object representing an employee that the user wants to drag to somewhere else on the form (or to another form).
You still use OnDragOver/OnDragDrop events but they look a lot simpler. There "Source" parameter is now a TDragAnything object whose Drag property is the object originally passed (fEmployee in this example).
E.g.
procedure TForm1.Memo1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
var
vDrag: TObject;
begin
Accept := False;
if (Source is TDragAnything) then
begin
vDrag := TDragAnything(Source).Drag;
if (vDrag nil) then
Accept := (vDrag is TEmployee);
end; //if
end;
procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
vDrag: TObject;
begin
if (Source is TDragAnything) then
begin
vDrag := TDragAnything(Source).Drag;
if (vDrag nil) then
begin
if (vDrag is TEmployee) then
... do stuff with TEmployee(vDrag) ...
end; //if
end; //if
end;
The TDragAnything class handles its own cleanup when the drag operation ends.
That's all there is to it.
Source for the class is below. (A zip file with this source in a small demo project should be attached to this article.)
PS: You can easily descend from TDragAnything to create more specialized drag object carriers.
PPS: Any suggestions or bugs, please add them via the comments or email me.
----------------- TDragAnything source -----------------------
unit clsDragAnything;
// Class:
//
// TDragAnything
//
// Use this class to allow you to drag-and-drop
// any object. Why drag components around when
// you really want to drag data?!
//
// Other classes:
//
// TDragAnythingTakeOwnership
//
// Same but the 'drag operation' takes ownership
// of the object you are passing. Useful when
// you create an object-to-drag on the fly to
// pass data around. Let the drag operation take
// ownership as you won't otherwise know when to
// free it.
//
// Cheers,
// Matthew Hobbs, mfhobbs@attglobal.net, June 6, 2001
interface
uses
Controls, SysUtils;
type
TDragAnything = class(TWinControl)
protected
fDrag: TObject;
procedure DoEndDrag(Target: TObject; X, Y: Integer); override;
public
destructor Destroy; override;
class procedure Start(pDrag: TObject); virtual;
class function CurrentDrag: TObject; virtual;
property Drag: TObject read fDrag;
end; //TDragAnything
TDragAnythingClass = class of TDragAnything;

TDragAnythingTakeOwnership = class(TDragAnything)
public
destructor Destroy; override;
end; //TDragAnythingTakeOwnership
implementation
var
vCurrentDragAnything: TDragAnything; //There is only ever zero or
//one 'current' drag operation
class procedure TDragAnything.Start(pDrag: TObject);
begin
if (vCurrentDragAnything nil) then
vCurrentDragAnything.Free;
vCurrentDragAnything := Self.Create(nil);
vCurrentDragAnything.fDrag := pDrag;
vCurrentDragAnything.BeginDrag(False);
end; //Create
class function TDragAnything.CurrentDrag: TObject;
begin
if (vCurrentDragAnything = nil) then
Result := nil
else
Result := vCurrentDragAnything.Drag;
end; //CurrentDrag
destructor TDragAnything.Destroy;
begin
vCurrentDragAnything := nil; //set the singleton instance variable to nil as it no longer exists
inherited;
end; //Destroy
procedure TDragAnything.DoEndDrag(Target: TObject; X, Y: Integer);
begin
Free;
end; //DoEndDrag
destructor TDragAnythingTakeOwnership.Destroy;
begin
inherited;
if (fDrag nil) then
FreeAndNil(fDrag);
end; //Destroy
initialization
vCurrentDragAnything := nil;
finalization
if (vCurrentDragAnything nil) then
FreeAndNil(vCurrentDragAnything);
end.
----------------- end TDragAnything source -----------------------
Note: this class could be simplified by not keeping track of the drag object using the vCurrentDragAnything variable. You would have to remove the class function CurrentDrag though (which would be no great loss for most uses).