Tutorial
Windows Drag und Drop Tutorial
von stephanbrunker | Seite 10 von 12 |
Drag&Drop mit virtuellen Dateien
Als letzte Variante behandeln wir noch den Umgang mit virtuellen Dateien, d.h. welche von denen wir keinen aktuellen Pfad angeben können, weil sie zum Beispiel in einer Datenbank sind oder zuerst noch entschlüsselt werden müssen. Das Clipboardformat dafür ist CFSTR_FILEDESCRIPTOR/CFSTR_FILECONTENTS und tritt immer als solches Paar auf. Da es nicht vordefiniert ist, müssen wir uns eine ID dafür holen:
With divfmtetc(2)
.cfFormat = RegisterClipboardFormat(@"Filegroupdescriptor") 'CFSTR_FILEDESCRIPTOR
.tymed = TYMED_HGLOBAL
.dwAspect = DVASPECT_CONTENT
.ptd = NULL
.lindex = -1
End With
With divfmtetc(3)
.cfFormat = RegisterClipboardFormat(@"Filecontents") 'CFSTR_FILECONTENTS
.tymed = TYMED_HGLOBAL
.dwAspect = DVASPECT_CONTENT
.ptd = NULL
End With
With divfmtetc(4)
.cfFormat = divfmtetc(3).cfFormat
.tymed = TYMED_ISTREAM
.dwAspect = DVASPECT_CONTENT
.ptd = NULL
End With
An der Stelle ist ein wenig Verwechselungsgefahr: Zu registrieren ist Filegroupdescriptor, was auch der TYPE ist, den wir dafür in HGLOBAL kopieren, das Format heißt in der MSDN aber CFSTR_FILEDESCRIPTOR. Das hier ist ein Teil meines Arrays der unterstützten Formate in meiner *.bi, damit ich diese Definitionen nicht immer wieder neu tippen muss. Der Filegroupdescriptor besteht wiederum aus zwei Strukturen: Einmal einem Filegroupdescriptor - der eigentlich nur ein UINT mit der Anzahl der enthaltenen Dateien ist - gefolgt von einem Filedescriptor für jede Datei. Dieser Filedescriptor enthält die ganzen Informationen über die Datei wie Dateiname, Attribute, die Erstellungs- und Änderungsdaten usw. Damit bekommt dann auch das lindex-Member von FORMATETC eine Bedeutung: In der Reihenfolge der Filedescriptoren werden auch die FORMATETC-Strukturen damit durchnummeriert, die erste Datei hat also .lindex = 0, die zweite .lindex=1 ... - wir haben damit in unserem Dataobject für jede Datei ein Format plus den Beschreiber. Ein ganz nützliches Tool ist übrigens der Dataobjectviewer, den es hier gibt:
dataobjview.zip
Dieser zeigt einem an, welche Datenobjekte in einem Drag&Drop enthalten sind - von dieser Seite stammt auch ein guter Teil der Informationen, die hier eingeflossen sind.
Für die ganze Struktur gibt es leider keinen richtigen TYPE auch wenn einer in der shlobj.bi definiert ist, denn der würde ein variables Array erfordern das mit Windows kompatibel ist und das geht mit FB leider nicht. Man kann sich das aber einfach manuell zusammenbasteln indem man den UINT und die FILEDESCRIPTOR Strukturen einfach nacheinander in Global Memory kopiert.
Dann gibt es noch zwei Möglichkeiten, den Inhalt der virtuellen Datei zu übertragen: einmal als HGLOBAL, was aber nur bis zu einer gewissen Dateigröße praktikabel ist, da wir den Dateiinhalt Byte für Byte in den globalen Speicher kopieren müssen. Dann gibt es das ISTREAM-Interface, das vereinfacht gesagt sich den Dateiinhalt scheibchenweise in einen vom Zielprogramm angegebenen Pointer kopieren lässt.
Doch zunächst füllen wir das CFSTR_FILEDESCRIPTOR-Format:
Dim stats(0 To nFiles-1) As FILEDESCRIPTOR
Dim pGlob2 As HGLOBAL
If (cFlags And C_CONTGLOBAL) Or (cFlags And C_CONTSTREAM) Then
numFormats += nFiles + 1
'get FileAttributes and copy
Dim fstats As WIN32_FILE_ATTRIBUTE_DATA
Dim filename As ZString * MAX_PATH
numFormats += nFiles + 1 'one each for the content plus one for the descriptor
filename = filepath(index) & filetitle(index)
GetFileAttributesEx(@filename, GetFileExInfoStandard, @fstats )
stats(index).dwFileAttributes = fstats.dwFileAttributes
stats(index).ftCreationTime = fstats.ftCreationTime
stats(index).ftLastAccessTime = fstats.ftLastAccessTime
stats(index).ftLastWriteTime = fstats.ftLastWriteTime
stats(index).nFileSizeHigh = fstats.nFileSizeHigh
stats(index).nFileSizeLow = fstats.nFileSizeLow
stats(index).cFileName = filetitle(index)
stats(index).dwFlags = FD_ATTRIBUTES Or FD_CREATETIME Or FD_ACCESSTIME Or FD_WRITESTIME Or FD_FILESIZE
Next index
'Create FILEGROUPDESCRIPTOR = (UINT) nFiles & FILEDESCRIPTOR(1 to nfiles)
'and copy to global
Dim pFGD As UINT Ptr
Dim pFD As FILEDESCRIPTOR Ptr
pGlob2 = GlobalAlloc(GPTR_ ,SizeOf(UINT) + SizeOf(FILEDESCRIPTOR)*(nFiles) )
pFGD = GlobalLock(pGlob2)
*pFGD = nFiles
pFD = Cast(FILEDESCRIPTOR Ptr,pFGD+1)
For index = 0 To nFiles-1
pFD[index] = stats(index)
Next index
GlobalUnlock(pGlob2)
EndIf
Der Code macht jetzt genau das: von jeder Datei - vorher schon ausgelesen mit filepath und filetitle - mit GetFileAttributesEX die Informationen holen, in FILEDESCRIPTOR umwandeln und diese im Global Memory speichern, wobei am Anfang dieses Speicherbereichs zuerst die Anzahl der Beschreiber steht, fertig ist das Format. Die Datei speichern wir komplett in einem anderen Global Memory:
Dim pContent(0 To nFiles-1) As HGLOBAL
If (cFlags And C_TEXT) Or (CFlags And C_CONTGLOBAL) Then
Dim filecontent As String
For index = 0 To nFiles - 1
Open (filepath(index) & filetitle(index)) For Binary As #1
filecontent = String(Lof(1)+1, 0) '0-terminated String für CF_TEXT
Get #1,,filecontent
Close #1
pContent(index) = GlobalAlloc(GPTR_,Len(filecontent))
memcpy(GlobalLock(pContent(index)),StrPtr(filecontent),Len(filecontent))
GlobalUnlock(pContent(index))
'for CF_TEXT, copy only the first file
If (cFlags And C_TEXT) And Not (cFlags And C_CONTGLOBAL) Then
numFormats += 1
Exit For
EndIf
Next index
EndIf
Wobei das das der gleiche Code wie für CF_TEXT bei ganzen Dateien ist, da wird nämlich die ganze Datei in einen Textstring umgewandelt und ebenso gespeichert. Dann das FORMATETC/STGMEDIUM Paar für das Formatpaar erstellen:
If (cFlags And C_CONTGLOBAL) Or (cFlags And C_CONTSTREAM) Then
'Format FILEDESCRIPTOR
fmtetc(index) = divfmtetc(2)
With stgmed(index)
.tymed = TYMED_HGLOBAL
.hGlobal = pGlob2
.pUnkForRelease = NULL
End With
index += 1
EndIf
If (cFlags And C_CONTGLOBAL) Then
'Format FILECONTENT als HGLOBAL
Dim i As Integer
For i=0 To nFiles-1
fmtetc(index+i) = divfmtetc(3)
fmtetc(index+i).lindex = i
With stgmed(index+i)
.tymed = TYMED_HGLOBAL
.hGlobal = pContent(i)
.pUnkForRelease = NULL
End With
Next i
EndIf
Damit wäre der Teil schon fertig. Der Code in dem Beispielprojekt ist so geschrieben, dass man der DataTransfer - Funktion mit den Flags übergibt, welche Format(e) man will, die IF-Schleifen erstellen diese dann und übergeben ein Array mit den gewünschen Formaten dann an unser IDataObject. Wir machen aber jetzt gleich weiter und nehmen die ISTREAM-Variante des Formats. Das IStream-Interface ermöglicht es uns, die Datei als Datenstrom und nicht als kompletten GlobalMemory-Block zu übergeben, es werden also keine Resourcen belegt.
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|