简单的文本拖放,也不是那么简单的,这里有个 memo 作为 drag & drop
Target 的例子,作为 Source 自己找找吧。
From undu 9809
//=== SIMPLE TEXT DROP ONTO A MEMO EXAMPLE =====================================
//
// This is a simple demonstration of a Form with a TMemo that you can drop
// text onto. The Memo will only accept simple text, cannot link to the
// source of the text, but does permit both Copy and Move (in which case
// the text is deleted from the source).
//
// Try typing some text into wordpad, highlight a block, the drag the block
// across to the memo and drop. Press Ctrl to Copy; no key to Move.
//
// Watch for the insertion point being highlighted by means of a thin grey caret.
// Move the mouse near to an edge of the memo's text area to scroll the memo in
// that direction.
//
// Grahame Marsh 18/ 8/98 Freeware for UNDU Tested under Delphi 4.00 only
// 7/ 9/98 Resource leak corrected, autoscrolling and user
// feedback (grey caret) added
//
//==============================================================================
unit EditDrop1;
{$ASSERTIONS ON} // for debugging
{$OPTIMIZATION OFF} // for debugging and single stepping only
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ActiveX;
type
TDropEditForm = class(TForm)
Panel1: TPanel;
DropMemo: TMemo;
procedure FormCreate(Sender: TObject);
private
public
end;
var
DropEditForm: TDropEditForm;
implementation
{$R *.DFM}
//--- UTILITIES ----------------------------------------------------------------
// Drop effects as Delphi style constants (originals in ActiveX)
const
deNone = DROPEFFECT_NONE;
deMove = DROPEFFECT_MOVE;
deCopy = DROPEFFECT_COPY;
deLink = DROPEFFECT_LINK;
deScroll = longint(DROPEFFECT_SCROLL);
//Default drag constants as Delphi style (originals in ActiveX)
const
ddScrollInset = DD_DEFSCROLLINSET; // = 11
ddScrollDelay = DD_DEFSCROLLDELAY; // = 50
ddScrollInterval = DD_DEFSCROLLINTERVAL; // = 50
ddDragDelay = DD_DEFDRAGDELAY; // = 200
ddDragMinDist = DD_DEFDRAGMINDIST; // = 2
//Inset region codes for scroll operations
const
inNone = 0; // not in an inset regions
inLeft = 1; // left edge
inRight = 2; // right edge - left and right are exclusive
inTop = 4; // top edge
inBottom = 8; // bottom edge - top and bottom are exclusive
// Data transfer direction during data type enumeration (original in ActiveX)
// as Delphi style constants
const
ddGet = DATADIR_GET;
ddSet = DATADIR_SET;
// Type of storage medium for data formats (original in ActiveX)
// as Delphi style constants
const
tsGlobal = TYMED_HGLOBAL; // handle to global memory clock
tsFile = TYMED_FILE; // file
tsStream = TYMED_ISTREAM; // stream interface
tsStorage = TYMED_ISTORAGE; // storage interface
tsGDI = TYMED_GDI; // gdi object
tsMetafilePict = TYMED_MFPICT; // metafilepict structure
tsEnhMetafile = TYMED_ENHMF; // enhanced metafile
tsNull = TYMED_NULL; // no storage
// View Aspect
const
dvaContent = DVASPECT_CONTENT;
dvaThumbnail = DVASPECT_THUMBNAIL;
dvaIcon = DVASPECT_ICON;
dvaDocPrint = DVASPECT_DOCPRINT;
//--- returns the normal response for a wanted effect:
// no keys = "move"
// control only = "copy"
// control/shift = "link" - ignored in this case
function StandardEffect (Keys : TShiftState) : integer;
begin
Result := deMove;
if ssCtrl in Keys then
Result := deCopy
end;
//=== ENUMERATE FORMATS ========================================================
// This is a simple format enumerator for an IDataObject interface. It has
// been written to be expanded but this version can only respond to an
// IDataObject that contains a CF_TEXT format.
type
TEnumFormats = class
private
FDataObject : IDataObject;
FEnumerator : IEnumFormatEtc;
FFormatEtc : TFormatEtc;
FMediumValid,
FValid : boolean;
FCount : integer;
FMedium : TStgMedium;
procedure SetDataObject (Value : IDataObject);
function SomeText (Format : TClipFormat) : string;
public
constructor Create (DataObject : IDataObject);
destructor Destroy; override;
// frees memory associated with the storage medium
procedure FreeMedium;
// reset to the start of the enum list
function Reset : boolean;
// returns next formatetc or first if reset just called
function Next : boolean;
// returns true if a given format is available
function HasFormat (ClipFormat : TClipFormat) : boolean;
// returns the handle to a given type of medium required
function Handle (Tymed : integer): hGlobal;
// Global handle from a tsGlobal type of medium
function GlobalHandle : hGlobal;
// Text available?
function HasText : boolean;
function Text : string;
// number of formats available
property Count : integer read FCount;
// the dataobject interface for which enum is required
property DataObject : IDataObject read FDataObject write SetDataObject;
// true if formatetc stuff is valid
property Valid : boolean read FValid;
// information held by current formatetc if valid
property FormatEtc : TFormatEtc read FFormatEtc;
property Aspect : integer read FFormatEtc.dwAspect write FFormatEtc.dwAspect;
property Format : TClipFormat read FFormatEtc.cfFormat write FFormatEtc.cfFormat;
property Index : integer read FFormatEtc.lIndex write FFormatEtc.lIndex;
property Medium : integer read FFormatEtc.Tymed write FFormatEtc.Tymed;
end;
// Create the enumerator and set the dataobject
constructor TEnumFormats.Create (DataObject : IDataObject);
begin
inherited Create;
SetDataObject (DataObject)
end;
// Destroy the dataobject copy and the enumerator
destructor TEnumFormats.Destroy;
begin
FreeMedium;
SetDataObject (nil);
inherited Destroy
end;
//--- free the memory associated with the storage medium record FMedium
procedure TEnumFormats.FreeMedium;
begin
if FMediumValid then
ReleaseStgMedium (FMedium);
FMediumValid := false
end;
//--- function to obtain the next Format supported by the DataObject, or the
// first if Reset has just been called, returns true on success
function TEnumFormats.Next : boolean;
var
Returned : integer;
begin
inc (FCount);
FValid := FEnumerator.Next (1, FFormatEtc, @Returned) = S_OK;
Result := FValid
end;
//--- Reset the Enumerator interface back to the beginning of the list,
// returns true on success
function TEnumFormats.Reset : boolean;
begin
FValid := false;
FCount := 0;
Result := Succeeded (FEnumerator.Reset)
end;
//--- Enumerate the data object for a specific format, returns true if
// found, then the FFormatEtc record will be valid
function TEnumFormats.HasFormat (ClipFormat : TClipFormat) : boolean;
begin
Result := false;
if Reset then
while (not Result) and Next do
Result := ClipFormat = Format
end;
procedure TEnumFormats.SetDataObject (Value : IDataObject);
var
Result : integer;
begin
// clear current values and free
FDataObject := nil;
// new interfaces
FDataObject := Value;
if Assigned (FDataObject) then
begin
Result := FDataObject.EnumFormatEtc (ddGet, FEnumerator);
Assert (Succeeded (Result), 'Cannot get the format enumerator');
Reset
end
end;
// returns a handle to the current formatetc given the
// type of medium required
function TEnumFormats.Handle (Tymed : integer): hGlobal;
var
FormatEtc : TFormatEtc;
begin
FreeMedium;
Result := 0;
if FValid and (FFormatEtc.tymed and Tymed = Tymed) then
begin
FormatEtc := FFormatEtc;
FormatEtc.tymed := FormatEtc.tymed and Tymed; // use only the requested type
if Succeeded (FDataObject.GetData (FormatEtc, FMedium)) then
begin
FMediumValid := true;
Result := FMedium.hGlobal
end
end
end;
// Get a global data handle (eg for CF_TEXT)
function TEnumFormats.GlobalHandle : hGlobal;
begin
Result := Handle (tsGlobal)
end;
//--- function to return a string, used by CF_TEXT
function TEnumFormats.SomeText (Format : TClipFormat) : string;
var
H : hGlobal;
P : PChar;
begin
Result := '';
if HasFormat (Format) then // check that text is available *AND* position
begin // the enumerator on the text data
H := GlobalHandle; // get the global handle to the data
if FMediumValid then
try
if H <> 0 then
begin
P := GlobalLock (H); // it's a pointer to a null terminated string
try
Result := P // get our copy
finally
GlobalUnLock (H) // let it go
end
end
finally
FreeMedium // free the storage medium
end
end
end;
//--- TEXT ---
// Returns a text item or empty if not present
function TEnumFormats.Text : string;
begin
Result := SomeText (CF_TEXT)
end;
// Returns true if some text is available
function TEnumFormats.HasText : boolean;
begin
Result := HasFormat (CF_TEXT)
end;
//--- Drop Target Interface ----------------------------------------------------
// This is the IDropTarget interface implemented using TInterfacedObject
// that will respond to some text being dropped on a TMemo control
type
TDropTarget = class (TInterfacedObject, IDropTarget)
function DragEnter (const DataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
function DragOver (grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
function DragLeave : HResult; stdcall;
function Drop (const DataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
private
FInsertPos : TPoint;
FOk : boolean;
FMemo : TMemo;
FScrollTick,
FTextHt : integer;
public
constructor Create (AMemo : TMemo);
end;
constructor TDropTarget.Create (AMemo : TMemo);
begin
inherited Create;
FMemo := AMemo
end;
function TDropTarget.DragEnter (const DataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
var
DC : hDC;
TM : TTextMetric;
begin
// Determine if text is available and set the Ok flag accordingly
with TEnumFormats.Create (DataObj) do
try
FOk := HasText
finally
Free
end;
if FOk then
begin
// set tick counter to zero to indicate not in an inset region initially
FScrollTick := 0;
// get memo text height
DC := GetDC (FMemo.Handle);
try
SelectObject (DC, FMemo.Font.Handle);
GetTextMetrics (DC, TM)
finally
ReleaseDC (FMemo.Handle, DC)
end;
FTextHt := TM.tmHeight;
// create a thin grey caret to give user feedback
CreateCaret (FMemo.Handle, 1, 1, FTextHt);
ShowCaret (FMemo.Handle);
// set the cursor display effect according to keys
dwEffect := StandardEffect (KeysToShiftState (grfKeyState))
end else
dwEffect := deNone;
Result := NOERROR;
end;
function TDropTarget.DragOver (grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
var
Caret,
Offset,
Point : TPoint;
Tick,
Loop,
Inset : integer;
Text : string;
DC : hDC;
Size : TSize;
begin
if FOk then
begin
// convert Screen mouse position to Memo relative
Point := FMemo.ScreenToClient (Pt);
//=== DISALLOW DROP ON SCROLL BARS
if (Point.X >= FMemo.ClientWidth) or
(Point.Y >= FMemo.ClientHeight) then
begin
SetCaretPos (-1, -1);
dwEffect := deNone;
FInsertPos.X := -1;
FInsertPos.Y := -1
end else begin
//=== PROVIDE USER FEEDBACK
// get the current scroll bar positions to offset mouse position
Offset.X := GetScrollPos (FMemo.Handle, SB_HORZ);
Offset.Y := GetScrollPos (FMemo.Handle, SB_VERT);
// calculate line number that mouse is on, get the text for that line
Caret.Y := (Point.Y div FTextHt) + Offset.Y;
Text := FMemo.Lines [Caret.Y];
// round mouse position to calculate caret Y position
Caret.Y := Caret.Y * FTextHt;
// get X position by locating text character just to the left of the
// mouse position, or end of the line if mouse is beyond
DC := GetDC (FMemo.Handle);
try
SelectObject (DC, FMemo.Font.Handle);
GetTextExtentPoint32 (DC, PChar (Text), length (Text), Size);
inc (Size.cx, 3);
if Point.X + Offset.X < Size.cx then
for Loop := length (Text) - 1 downto 0 do
begin
GetTextExtentPoint32 (DC, PChar (Text), Loop, Size);
inc (Size.cx, 3);
if Size.cx < Point.X + Offset.X then
break
end
finally
ReleaseDC (FMemo.Handle, DC)
end;
// calculate the caret X, Y position in the scrolled memo area
Caret.X := Size.cx - Offset.X;
Caret.Y := Caret.Y - Offset.Y * FTextHt;
// position caret
SetCaretPos (Caret.X, Caret.Y);
// remember this position as the insert position if the user
// should drop the text here
FInsertPos.X := Caret.X;
FInsertPos.Y := Caret.Y + FTextHt;
//=== SCROLL MEMO
Inset := inNone;
// check left and right bounds
if Point.X <= ddScrollInset then
Inset := Inset or inLeft
else
if FMemo.ClientWidth - Point.X < ddScrollInset then
Inset := Inset or inRight;
// check top and bottom bounds
if Point.Y <= ddScrollInset then
Inset := Inset or inTop
else
if FMemo.ClientHeight - Point.Y < ddScrollInset then
Inset := Inset or inBottom;
// get standard key combinations
dwEffect := StandardEffect (KeysToShiftState (grfKeyState));
// check for scrolling inset region
if Inset <> inNone then
begin
// set scrolling flag in dwEffect
dwEffect := dwEffect or deScroll;
// check if only first call, if so get tick counter at which scrolling will occur
Tick := GetTickCount;
if FScrollTick = 0 then
FScrollTick := Tick + ddScrollDelay
else
// check if delay or interval timer expired
if Tick - FScrollTick > 0 then
begin
// scroll up or down?
if Inset and inTop <> 0 then
FMemo.Perform (WM_VSCROLL, SB_LINEUP, 0)
else
if Inset and inBottom <> 0 then
FMemo.Perform (WM_VSCROLL, SB_LINEDOWN, 0);
// scroll left or right?
if Inset and inLeft <> 0 then
FMemo.Perform (WM_HSCROLL, SB_LINELEFT, 0)
else
if Inset and inRight <> 0 then
FMemo.Perform (WM_HSCROLL, SB_LINERIGHT, 0);
// set tick counter for delay until next scroll
FScrollTick := Tick + ddScrollInterval
end
end
end
end else
dwEffect := deNone;
Result := NOERROR
end;
function TDropTarget.DragLeave: HResult;
begin
if FOk then
DestroyCaret;
Result := NOERROR
end;
function TDropTarget.Drop (const DataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult;
var
P : TSmallPoint;
begin
if FOk then
begin
DestroyCaret;
// Check if drop has occured onto a scrollbar
if (FInsertPos.X = -1) and (FInsertPos.Y = -1) then
dwEffect := deNone
else begin
// Convert drop point from screen coords to memo coords and to TSmallPoint
// to pass as an integer parameter to position the caret in the memo
P := PointToSmallPoint (FInsertPos);
// Do a down click
FMemo.Perform (WM_LBUTTONDOWN, 0, integer (P));
// Do an up click
FMemo.Perform (WM_LBUTTONUP, 0, integer (P));
// The memo's input caret is now nicely poised,
// Insert the drop text into the memo
with TEnumFormats.Create (DataObj) do
try
FMemo.SelText := Text
finally
Free
end;
// tell the drop source to delete the text if necessary
dwEffect := StandardEffect (KeysToShiftState (grfKeyState))
end
end else
dwEffect := deNone;
Result := NOERROR
end;
//=== FORM STUFF ===============================================================
procedure TDropEditForm.FormCreate(Sender: TObject);
var
Result : integer;
begin
Result := RegisterDragDrop (DropMemo.Handle, TDropTarget.Create (DropMemo));
Assert (Succeeded (Result), Format ('RegisterDragDrop failed ($%x)', [Result]))
end;
//=== INITIALIZATION ===========================================================
procedure Initialize;
var
Result : HRESULT;
begin
Result := OleInitialize (nil);
Assert (Result in [S_OK, S_FALSE], Format ('OleInitialize failed ($%x)', [Result]))
end;
initialization
Initialize
finalization
OleUninitialize
end.