Mega Code Archive

 
Categories / Delphi / OOP
 

Drag and Drop any TObject anywhere

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).