Buchempfehlung
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
"Der" Petzold, das über 1000 Seiten starke Standardwerk zum Win32-API - besonders nützlich u. a. bei der GUI-Programmierung in FreeBASIC! [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Tutorial

Windows Drag und Drop Tutorial

von MitgliedstephanbrunkerSeite 8 von 12

Listview mit Drag und Drop

Der erste Schritt beim Zusammenbauen unseres Codes ist, die ganzen Interfaces umzudefinieren und zu laden in unserer Include-Sektion:

#Include "windows-fix.bi"

#Undef DoDragDrop
#Include "IUnknown.bas"
#Include "IEnumFormatEtc.bas"
#Include "IDataObject.bas"
#Include "IDropTarget.bas"
#Include "IDropSource.bas"
Declare Function DoDragDrop Alias "DoDragDrop" (ByVal As IDataObject Ptr, ByVal As IDropSource Ptr, ByVal As DWORD, ByVal As PDWORD) As HRESULT

'Include our own *.bi
#Include "Dialog.bi"

Aus der DragDrop2 - Variante bleibt der DropTarget bzw. DataDrop-Teil unverändert, hierfür wird lediglich das vorbereitete Element aktiviert, dass einen Drop auf sich selbst erkennt. Ansonsten kommt neu eine DataTransfer-Funktion hinzu, die ich am besten in Einzelteilen erkläre:

Function DataTransfer(ByVal hLSV1 As HWND, ByVal cFlags As UInteger, ByVal cAction As UInteger) As Integer

    Dim As UInteger nFiles,index,j
    Dim numFormats As UInteger

    'get filenames and paths
    nFiles=ListView_GetSelectedCount(hLSV1)
    If nFiles = 0 Then Return FALSE
    Dim filetitle(0 To nFiles -1) As String
    Dim filepath (0 To nFiles -1) As String
    Dim getzstring As ZString * MAX_PATH
    Dim As Integer selected = -1
    Print "Drag Files:"
    For index=0 To nFiles-1
        selected = ListView_GetNextItem(hLSV1,selected,LVNI_SELECTED)
        ListView_GetItemText(hLSV1,selected,1,@getzstring,SizeOf(getzstring))
        filepath(index) = getzstring
        ListView_GetItemText(hLSV1,selected,0,@getzstring,SizeOf(getzstring))
        filetitle(index) = getzstring
        Print "     ",filepath(index) & filetitle(index)
    Next index

der erste Teil holt die selektierten Zeilen aus dem ListView-Element und speichert sie in zwei Arrays - das hat erstmal nix mit Drag und Drop zu tun. Wenn wir uns an den Drop und das CF_HDROP-Format erinnern, ist das ein 0-separierter und mit 00-terminierter String, den wir als nächstes zusammenbauen:

    'hGlobal pointers
    Dim pGlob1 As hglobal

    '=======================
    '   CF_HDROP - Format
    '=======================
    If cFlags And C_HDROP Then
        'paths of the selected files in a 0-seperated and 00-terminated String
        Dim pdrop As DROPFILES Ptr
        Dim filelist As String
        numFormats += 1
        For index=0 To nFiles-1
            filelist &= filepath(index) & filetitle(index) & Chr(0)
        Next index
        filelist &= Chr(0)

        'copy to GLOBAL
        pGlob1 = GlobalAlloc(GPTR_ ,SizeOf(DROPFILES)+Len(filelist))
        pdrop = GlobalLock(pGlob1)

        'fill DROPFILES
        pdrop->pFiles = SizeOf(DROPFILES)
        pdrop->fWide = FALSE

        'copy filelist after DROPFILES
        memcpy(pdrop+1,StrPtr(filelist),Len(filelist))

        GlobalUnlock(pGlob1)
    End If

Außer diesem String besteht das Format noch aus einem DROPFILES-Typ, der aber eigentlich nichts wichtiges enthält, danach folgt der String. Wir reservieren (GlobalAlloc) ausreichend GLOBAL Memory für beides zusammen und kopieren es dann mit memcpy dort hin, nachdem wir den Speicher mit GlobalLock für uns gesichert haben.

Der nächste Schritt: FORMATETC und STGMEDIUM erstellen. Das haben wir soweit auch schon gehabt, nur dass wir die zwei Strukturen jetzt selbst füllen, FormatEtc ist in diesem Fall CF_HDROP als TYMED_HGLOBAL und das STGMEDIUM enthält den Pointer zu dem GlobalMemory, wo wir die Daten abgelegt haben:

    '=====================================
    '   generate FORMATETC and STGMEDIUM
    '=====================================
    Dim fmtetc (0 To numFormats -1) As FORMATETC
    Dim stgmed (0 To numFormats -1) As STGMEDIUM

    index = 0

    If (cFlags And C_HDROP) Then
        'Format HDROP
        With fmtetc(index)
            .cfFormat   = CF_HDROP
            .tymed      = TYMED_HGLOBAL
            .dwAspect   = DVASPECT_CONTENT
            .ptd        = NULL
            .lindex     = -1
        End With

        With stgmed(index)
            .tymed          = TYMED_HGLOBAL
            .hGlobal        = pGlob1
            .pUnkForRelease = NULL
        End With

        index +=1
    EndIf

Das ganze hängt in IF-Schleifen weil wir wieder modular programmieren wollen und verschiedene Datentransferszenarien mit einer einzigen Funktion abdecken wollen, das sind die cFlags- und cAction-Attribute beim Funktionsaufruf. Deshalb sind fmtetc und stgmed auch Arrays, denn wir können ja beliebig viele verschiedene Formate anbieten - wobei wir aber dann keinen Einfluss mehr darauf haben, welches Format der Empfänger dann nimmt. Der Rest geht dann ganz schnell: um unsere zwei Strukturen definieren wir jetzt das IDataObject, erzeugen eine DropSource, sagen unserem immer noch aktiven DropTarget, was unser eigenes Objekt ist und übergeben an Windows mit DoDragDrop.

    Dim pDataObject As IDATAOBJECT Ptr = New IDATAOBJECT(fmtetc(),stgmed())


    'Drag&Drop operation
    If cAction And DRAGDROP Then
        Dim dweffect As DWORD
        Dim pDropSource As IDROPSOURCE Ptr = New IDROPSOURCE()
        pDropTarget->RegisterSelf(pDataObject)           'register our own Data at our DropTarget
        DoDragDrop(pDataObject,pDropSource,DROPEFFECT_COPY,@dweffect)
        Print "Dragdrop complete"
        pDropSource->Release()
        pDropTarget->RegisterSelf(NULL)
    EndIf
        pDataObject->Release()
    Return -1

End Function

Nach Beendigung der Aktion geben wir die Interfaces wieder frei, die sich damit selbst löschen. Wir erkennen, dass der User versucht etwas zu draggen durch die LVN_BEGINDRAG - Message, die als WM_NOTIFY versendet wird:

                Case LVN_BEGINDRAG
                    'Begin a drag and drop operation
                    DataTransfer(hLSV1,C_HDROP, DRAGDROP)

Und damit sollte das ganze (eigentlich) schon funktionieren. Der Teufel steckt wie gehabt hier extrem im Detail, da während der ganzen Aktion hauptsächlich Windows die Funktionen aufruft und das extrem schwer zu debuggen ist, da kann man leicht Tage mit der Fehlersuche verbringen.

Als kleine Hilfe kann ich nur auf das Zeug verweisen, was unsere Print-Anweisungen mitteilen, das sollte mit Beginn der Dragoperation ungefähr so aussehen:

Drag Files:
                  (ein oder mehrere Dateipfade)
IUnknown::Constructor
IDataObject::Constructor [ 1 Formats ]
IUnknown::Constructor
IDropSource::Constructor
IDropSource::QueryInterface
IDropSource::QueryInterface
IDataObject::QueryInterface
IDataObject::QueryInterface
IDataObject::QueryInterface
IDataObject::AddRef - Count : 2
IDataObject::AddRef - Count : 3
IDataObject::QueryInterface
IDataObject::QueryInterface
IDataObject::QueryInterface
IDataObject::Release - Count : 2
IDataObject::QueryInterface
IDataObject::AddRef - Count : 3
IDataObject::QueryInterface
IDataObject::AddRef - Count : 4
IDataObject::EnumFormatEtc
IUnknown::Constructor
IEnumFormatEtc::Constructor [ 1 Formats ]
IEnumFormatEtc::Next [copied: 1 ]
IEnumFormatEtc::Next [copied: 0 ]
IEnumFormatEtc::Reset
IEnumFormatEtc::Next [copied: 1 ]
IEnumFormatEtc::Next [copied: 0 ]
IEnumFormatEtc::Reset
IEnumFormatEtc::Release - Count: 0
IEnumFormatEtc::Destructor
IDataObject::QueryGetData-> LookupFormatEtc: MATCH @ 0
IDataObject::AddRef - Count : 5
IDropTarget::DragEnter
DATASELF
...

Die Erklärung, soweit ich sie mir zusammenreimen konnte: Die Constructoren sind erst einmal klar, die haben wir ja selbst veranlasst. Dann gibt es von DoDragDrop aus einige QueryInterface-Anfragen an IDragSource und IDataObject, die aber meistens nach anderen Interfaces fragen und deshalb keine neue Instanz erzeugen, bis dann die Funktion sich mit unserem Dataobjekt dann doch einigt und nach dem Pilgerschritt-Verfahren einige Instanzen erzeugt und wieder freigibt. Danach wird der Enumerator aufgerufen, der aus welchem Grund auch immer zweimal durchläuft. Dann kommt die erste Anfrage nach dem Inhalt unseres Datenobjekts und der Rest klingt dann schon nach unserem eigenen IDropTarget, was da anfragt. Wenn man aber so weit gekommen ist, müsste es auch funktionieren.

Dieses Beispielprogramm funktioniert jedenfalls:
Externer Link!DragDrop3.zip

 

Gehe zu Seite Gehe zu Seite  1  2  3  4  5  6  7  8  9  10  11  12  
Zusätzliche Informationen und Funktionen
  Bearbeiten Bearbeiten  

  Versionen Versionen