Hallo Leute!
Ich habe jetzt schon viele Fragen gelesen, wie man Dateien vom eigenen Fenster per Drag-and-Drop in eine andere Anwendung ziehen lassen kann. Deshalb erläutere ich das hier mal, zudem ist das ein ganz gutes Bespiel für die Verwendung von Interfaces.
Nan benötigt diese Funktion:
Delphi-Quelltext
1: 2:
| function DoDragDrop(dataObj: IDataObject; dropSource: IDropSource; dwOKEffects: Longint; var dwEffect: Longint): HResult; stdcall; |
Die befindet sich in der Ole32.dll.
Den Quelltext habe ich aus einer Komponente von Angus Johnson von 1997, diese kann bei mir heruntergeladen werden. (Damit kann man Dateitransfer in beide Richtungen per Drag-and-Drop machen, es gibt jeweils eine entsprechende Komponente, aber die Annahme von Dateien per Delphi-Programm wurde ja bereits von vielen erläutert...)
Quelltext der Komponente + Quelltext der Demo:
www.buchmanager-berl...mp;file=dragdrop.zip
oder über den Mirror
www.sj-berlin.de/dow...4pa9fds/dragdrop.zip
// EDIT: URLs aktualisiert. Die Downloads gehen wieder! Exe-Demo hinzugefügt.
Wer sich die Demo erstmal ansehen möchte ohne diese selbst kompilieren zu müssen, der kann sich die kompilierte ausführbare Datei herunterladen. Der Quelltext ist bei der Komponente dabei.
Ausführbare Exe-Datei der Demo:
www.buchmanager-berl...=Dragdrop%20Demo.zip
oder über den Mirror
www.sj-berlin.de/dow.../Dragdrop%20Demo.zip
Die Komponenten sollten von Delphi 2.01 bis Delphi 2006 funktionieren. (D2 von Angus selbst, D3, D7 und D2006 von mir getestet).
// EDIT: Delphi 2006 getestet.
Wer sich nicht für die Interna interessiert (was ich bei dem Thema niemandem verübeln kann, da es doch recht knifflig für Anfänger ist

), der kann sich einfach nur die Komponente herunterladen. Ich finde es aber doch sehr wichtig, sich einmal mit dem Thema auseinanderzusetzen.
Erstmal kurz etwas über Interfaces:
In Interfaces werden allgemeine Methoden zur Verfügung gestellt, die von Objekten, die von diesem Interface abgeleitet werden, implementiert werden müssen. (Ggf. nur mit dem Hinweis, dass die Methode nicht implementiert ist.)
Das Programm, das das Interface benutzt, muss dann gar nicht wissen, was genau das Interface macht, es benutzt einfach die zur Verfügung gestellten Methoden.
Na ja, das war seehr kurz, aber ich hoffe dennoch einigermaßen verständlich...
Zunächst: Was muss überhaupt gemacht werden?
Wir brauchen Objekte, die von den Interface-Typen abegeleitet sind, die der Funktion DoDragDrop übergeben werden müssen.
Eine Beschreibung der Interfaces und der also zu implementierenden Methoden finden sich (zufälligerweise

) in der Win32 SDK Reference.
Die möchte ich hier nicht komplett hineinkopieren, wer die also ansehen möchte, kann das auf meiner Homepage tun:
www.sj-berlin.de/htm...p_sdk/dodragdrop.htm
oder über den Mirror
www.buchmanager-berl...p_sdk/dodragdrop.htm
// EDIT: URLs aktualisiert. Die Links gehen wieder!
Die Informationen stammen aus der SDK Referenz von Microsoft und werden hier nur zu Lernzwecken zur Verfügung gestellt. Diese Informationen sind auch in Microsofts MSDN zu finden:
msdn.microsoft.com/l...9da-41a4e5a61315.asp
Da ich nicht sicher bin, ob diese Seite auch weiterhin immer unter diesem Link zu finden ist, habe ich die Informationen auch unter obigen Links auf meine Server gestellt.
Wir benötigen ein Interface vom Typ IDataObject und eins vom Typ IDropSource.
Die im SDK genannten Routinen müssen entsprechend implementiert werden.
Ich benutze die von Angus eingeführten Bezeichnungen für die von den Interfaces abgeleiteten Objekte.
Zunächst zu MyDataObject, abgeleitet von IDataObject:
Delphi-Quelltext
1:
| TMyDataObject = class(IDataObject) |
Im Konstruktor muss die Dateiliste übergeben und in eine interne Liste, die auch erzeugt werden muss, gefüttert werden.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| var i: integer; begin inherited Create; FileList := TStringList.create; FileList.assign(sl); FileListBytes := 1; for i := 1 to FileList.count do inc(FileListBytes,length(FileList[i-1])+1); |
Im Destruktor muss die interne Liste entfernt werden.
In QueryInterface wird überprüft, ob das angegebene Interface unterstützt wird und ggf. ein Pointer darauf gegeben. Die übergebene Interface-ID wird also mit den unterstützten Interfaces, also IUnknown und IDataObject verglichen. (IUnknown wird immer implementiert, es ist der Vorfahr jedes Interfaces.)
Die Methoden AddRef, Release erhöhen und vermindern den Referenzzähler.
Die folgenden Methoden geben nur zurück, dass sie von dem aktuellen Interface nicht implementiert werden:
GetDataHere, GetCanonicalFormatEtc, SetData, DAdvise, DUnadvise, EnumDAdvise
In EnumFormatEtc wird ein Datentyp angefordert, der Informationen über die von dem Interface benutzten Daten liefert. Mit dem Parameter DATADIR_GET wird dieser Datentyp für die Methode GetData, mit DATADIR_SET für SET_DATA für SetData angefordert.
Es sollen aber keine Daten gesetzt werden, daher gibt es auch SetData nicht, daher wird da auch zurückgegeben, dass es nicht implementiert ist.
Nun aber zu den beiden wichtigsten Methoden dieses Interfaces:
QueryGetData und GetData
Mit QueryGetData fragt ein Prozess an, ob ein Aufruf von GetData mit den Angaben erfolgreich wäre.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| with formatetc do begin if cfFormat <> CF_HDROP then Result := DV_E_FORMATETC else if (tymed and TYMED_HGLOBAL) = 0 then Result := DV_E_TYMED else Result := S_OK; end; |
Mit GetData werden dann erst tatsächlich die Daten abgefragt:
(keine Angst, ist nicht so kompliziert, wie es erstmal aussieht)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
| function TMyDataObject.GetData(var formatetcIn: TFormatEtc; var medium: TStgMedium): HResult; stdcall; var h: HGlobal; i, offset: integer; begin Result := DV_E_FORMATETC; if not Failed(QueryGetData(formatetcIn)) then begin h := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT, FileListBytes+sizeof(tdropfiles)); if h = 0 then begin Result:= E_OUTOFMEMORY; Exit; end; ptrdropfile:=globallock(h); with ptrdropfile^ do begin pfiles:=sizeof(Tdropfiles); pt.x:=0; pt.y:=0; longint(fnc) := 0; longint(Fwide) := 0; end; offset := sizeof(tdropfiles); for i := 1 to FileList.count do begin if i = FileList.count then strPcopy( pchar(longint(ptrdropfile)+offset), FileList[i-1]+#0#0) else strPcopy( pchar(longint(ptrdropfile)+offset), FileList[i-1]+#0); offset := offset + length(FileList[i-1])+1; end;
globalunlock(h); with medium do begin tymed:=TYMED_HGLOBAL; hGlobal := h; unkForRelease := nil; end; result:=S_OK; end; end; |
Jetzt ist es wieder zu einfach? Ok, dann machen wir gleich weiter, es fehlt noch das zweite Interface sowie das IEnumFormatEtc Interface, das wir zur Implementierung des gerade betrachteten Interfaces brauchen.
Fangen wir doch mit letzterem an:
Delphi-Quelltext
1:
| TMyEnum = class(IEnumFormatEtc) |
Dieses Objekt ist für den Zugriff auf unser FormatEtc da. Dabei zeigt die interne Variable Index den Index des gerade zu betrachtenden Elements an.
QueryInterface, AddRef und Release wieder wie eben bei TMyDataObject.
Skip überspringt die angegebene Anzahl von Einträgen.
Reset setzt den Index auf das erste Element zurück, sodass man von vorne durch das Array gehen kann.
Clone erzeugt ein identisches Objekt zu dem eigenen.
Nun fehlt noch next:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| function TMyEnum.Next(celt: Longint; var elt; pceltFetched: PLongint): HResult; stdcall; begin Result := S_FALSE; if (Index = 0) and (celt > 0) then begin Inc(Index); with TFormatEtc(elt) do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end;
if pceltFetched <> nil then pceltFetched^ := 1; if celt = 1 then Result := S_OK; end else begin if pceltFetched <> nil then pceltFetched^ := 0; end; end; |
Die übergebene Variable elt bekommt die Werte des Elements des Arrays.
Wichtig ist jetzt noch TMyDropSource, abgeleitet von IDropSource:
Delphi-Quelltext
1:
| TMyDropSource = class(IDropSource) |
QueryInterface, AddRef und Release wieder wie oben bei TMyDataObject.
Im Konstruktor wird die Instanz von der Komponente TDragFilesSrc übergeben, die benutzt wird.
Fehlen noch zwei Methoden:
QueryContinueDrag und GiveFeedback
GiveFeedback ist schnell erklärt: Damit wird für den Benutzer ein visuelles Feedback über den aktuellen Operationsstatus angegeben, die dann vom Programm dem Benutzer angezeigt werden sollte.
In einer internen Variable merkt sich das Objekt den Status.
Nun aber zu QueryContinueDrag:
Darin wird die Fortsetzung des Drag-Vorgangs festgelegt oder festgestellt, dass die Dateien abgelegt wurden.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
| function TMyDropSource.QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: Longint): HResult; stdcall; var drpEffect: integer; begin if srcDFS.DropEffect = deCopy then drpEffect := DROPEFFECT_COPY else drpEffect := DROPEFFECT_MOVE; if fEscapePressed then Result := DRAGDROP_S_CANCEL else if (grfKeyState and MK_LBUTTON) = 0 then begin if (srcDropEffect = drpEffect) and assigned( srcDFS.OnDropping ) then srcDFS.OnDropping(srcDFS); if srcDFS.FileCount = 0 then Result := DRAGDROP_S_CANCEL else Result := DRAGDROP_S_DROP; end else Result := S_OK; end; |
Nun ja, und die Komponente ist relativ einfach zu verstehen. Im Konstruktor werden die Standardwerte gesetzt und die Dateiliste initialisiert.
Im Destruktor wird die Dateiliste wieder aus dem Speicher entfernt.
Außerdem kann eine oder mehrere Dateien hinzugefügt werden:
AddFile / AddFiles
Auch kann man die Anzahl der Dateien in der Liste erfragen:
GetFileCount
Am wichtigsten ist aber Execute:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
| function TDragFilesSrc.Execute: TDragResult; var i: integer; dwEffect: Longint; DropSource : TMyDropSource; Dataobject : TMyDataObject; begin Result := drInvalid; if (fFileList.count = 0) or (fFileList[0] = '') then exit; if fVerifyFiles then for i := 1 to fFileList.count do if not fileexists(fFileList[i-1]) then exit; try DataObject:=TMyDataObject.create(fFileList); DataObject.AddRef; try DropSource:=TMyDropSource.create(self); DropSource.AddRef; if (DoDragDrop(dataobject, dropsource, byte(fDropEffect)+1, dwEffect) = DRAGDROP_S_DROP) and (dwEffect = byte(fDropEffect)+1) then Result := drDropped else Result := drCancelled; DropSource.release; finally DataObject.release; end; except end; end; |
Nicht vergessen darf man OleInitialize und OleUninitialize vor bzw. nach einem Aufruf der OLE-Funktionen aufzurufen, da sonst ein Fehler auftritt!
Ich hoffe das hilft euch und die Komponenten sind euch nützlich.
Danke auch noch an Angus, der die Komponente entwickelt und zur Verfügung gestellt hat.
(Ich hab ja nur die Funktionsweise erläutert...)
Sebastian Jänicke
P.S.: Seid nachsichtig, das ist erst mein drittes Tutorial, vielleicht hab ich mich manchmal etwas unklar ausgedrückt...
Bei Fragen, fragt...