Buchempfehlung
Mikrocomputertechnik mit Controllern der Atmel AVR-RISC-Familie
Mikrocomputertechnik mit Controllern der Atmel AVR-RISC-Familie
Umfassend, aber leicht verständlich führt dieses Buch in die Programmierung von ATMEL AVR Mikrocontrollern ein. [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 4 von 12

Das IDropTarget Interface

Nach dem Vorgeplänkel wird es ernst: wir erzeugen das IDropTarget Interface als Kind von IUnknown:

#Undef IDropTarget
#Undef RegisterDragDrop

Type IDropTarget EXTENDS IUnknown   'Custom IDropTarget Interface

        Declare Constructor(ByVal hWin As HWND, fmtetc () As FORMATETC)
        Declare Destructor()

        'Methods:
        'IUnknown Interface:
        Declare Virtual Function QueryInterface (ByVal iid As REFIID, ByVal ppvObject As Any Ptr Ptr) As HRESULT
        Declare Virtual Function AddRef () As ULong
        Declare Virtual Function Release () As ULong

        'IDropTarget Interface:
        Declare Virtual Function DragEnter (ByVal pDataObject As IDataObject Ptr, ByVal grfKeyState As DWORD,ByVal pt As POINTL,ByVal pdwEffect As DWORD Ptr) As HRESULT
        Declare Virtual Function DragOver (ByVal grfKeyState As DWORD,ByVal pt As POINTL, ByVal pdwEffect As DWORD Ptr) As HRESULT
        Declare Virtual Function DragLeave () As HRESULT
        Declare Virtual Function Drop (ByVal pDataObject As IDataObject Ptr,ByVal grfKeyState As DWORD,ByVal pt As POINTL,ByVal pdwEffect As DWORD Ptr) As HRESULT

        'helper Function:
        Declare Function RegisterSelf (ByVal pDataObject As IDataObject Ptr) As Integer

        'member variables:
        As HWND          m_hwnd
        As Integer       m_fAllowDrop
        As FORMATETC     m_fmtetc
        As Integer        m_nNumFormats
        As FORMATETC Ptr m_pfmtetc
        As IDataObject Ptr m_dataself
End Type

Declare Function DataDrop (ByVal hWin As HWND, ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr) As Integer

Wie auf der vorherigen Seite erwähnt, wird eine eigene Version der IUnknown-Methoden erzeugt, die sich aber nur marginal unterscheiden. Außerdem gibt es einen eigenen Constructor und Destructor, denn wir haben auch ein paar neue Member in unserem TYPE:

Constructor IDropTarget( ByVal hWin As HWND, fmtetc () As FORMATETC)

    'Initialize Members:
    m_fAllowDrop = FALSE
    m_hwnd = hWin

    'Get Formats we accept:
    Dim index As ULong = UBound(fmtetc)+1
    m_nNumFormats = index
    m_pfmtetc = New FORMATETC[index]
    Dim i As Integer
    For i = 0 To index - 1
        m_pfmtetc[i] = fmtetc(i)
    Next i
    Print "IDropTarget::Constructor [";m_nNumFormats; " Formats]"
End Constructor

Destructor IDropTarget()
    Print "IDropTarget::Destructor"
    If m_pfmtetc Then Delete [] m_pfmtetc
End Destructor

'IUnknown::QueryInterface
Function IDropTarget.QueryInterface (ByVal iid As REFIID, ByVal ppvObject As Any Ptr Ptr) As HRESULT
    Print "IDropTarget::QueryInterface"
    'if it's the right format return a pointer to the interface
    If IsEqualIID ( iid, @IID_IUnknown) Or IsEqualIID( iid, @IID_IDropTarget) Then
        AddRef()
        *ppvObject = @this
        Return S_OK
    Else
        *ppvObject = NULL
        Return E_NOINTERFACE
    End If

End Function

'IUnknown::AddRef
Function IDropTarget.AddRef () As ULong
    'register a new access
    m_lRefCount += 1
    Print "IDropTarget::AddRef - Count: "; m_lRefCount
    Return m_lRefCount
End Function

'IUnknown::Release
Function IDropTarget.Release () As ULong
    'unregister an access and destroy if not needed anymore
    m_lRefCount -= 1
    Print "IDropTarget::Release - Count: "; m_lRefCount
    If(m_lRefCount = 0) Then
        Delete @this
        Return 0
    Else
        Return m_lRefCount
    End If
End Function

An der Stelle muss ich etwas ausholen über den Mechanismus des Datatransfers über OLE, was für Drag&Drop genauso gilt wie für die Zwischenablage. Jedes Format, das wir akzeptieren wollen, kommt in Form einer FORMATETC Struktur. Weil die ja programmspezifisch ist, füllen wir die in unserem Programm und übergeben sie dem Constructor als Array, denn wir können ja jede Menge verschiedener Formate akzeptieren. Das DROPFILES-Format HDROP sieht in dieser Struktur so aus:

        Dim fmtetc (0 To 0) As FORMATETC
        With fmtetc(0)
                    .cfFormat   = CF_HDROP
                    .tymed      = TYMED_HGLOBAL
                    .dwAspect   = DVASPECT_CONTENT
                    .ptd        = NULL
                    .lindex     = -1
        End With

Wichtig ist das Format - CF_HDROP - und wie es gespeichert wird, nämlich als GlobalMemory, also TYMED_HGLOBAL. Die weiteren Member sind erstmal uninteressant. Wenn wir Text akzepieren wollten, wäre das Format CF_TEXT und so weiter - siehe dazu in der MSDN: Externer Link!Transferring Shell Objects with Drag-and-Drop and the Clipboard. Im Constructor speichern wir jetzt das Array der Formate - mit NEW als Pointer erzeugt -, ein Flag ob der Drop erlaubt ist und ein Handle unseres Dropziels, denn wenn die Drop-Funktion aufgerufen wird, wollen wir ja wissen, wo etwas gedropt ist. Das Array muss dann im Destructor auch wieder freigegeben werden.


Das Interface selbst hat die Methoden DragEnter, DragOver, DragLeave und Drop, die im Verlauf einer Drag&Drop-Aktion von Windows aufgerufen werden:

Genau wie im IUnknown Interface habe ich Print - Mitteilungen in den Code eingebaut, die uns über die Konsole über die Aufrufe der Methoden informieren (bis auf DragOver, da wird man zugespammt).

'IDropTarget::DragEnter
Function IDropTarget.DragEnter (    ByVal pDataObject As IDataObject Ptr, ByVal grfKeyState As DWORD, ByVal pt As POINTL, ByVal pdwEffect As DWORD Ptr) As HRESULT
    Print "IDropTarget::DragEnter"
    'set the Cursor effect if the drag contains a format we accept, save in flag
    Dim index As Integer
    m_fAllowDrop = FALSE

    'is the data object our own?
    If pDataObject = m_dataself Then
        *pdwEffect = DROPEFFECT_NONE
        Return S_OK
    Else
    'does the dataobject contain data we want?
        For index = 0 To m_nNumFormats -1
            If pDataObject->QueryGetData(@m_pfmtetc[index]) = S_OK Then
                m_fAllowDrop = TRUE
                *pdwEffect = DROPEFFECT_COPY
                Return S_OK
            End If
        Next index
    End If
    *pdwEffect = DROPEFFECT_NONE
    Return S_OK
End Function

'IDropTarget::DragOver
Function IDropTarget.DragOver ( ByVal grfKeyState As DWORD, ByVal pt As POINTL, ByVal pdwEffect As DWORD Ptr) As HRESULT
    'Print "IDropTarget::DragOver"
    'Set cursor effect for flag
    If m_fAllowDrop Then
        *pdwEffect = DROPEFFECT_COPY
    Else
        *pdwEffect = DROPEFFECT_NONE
    End If

    Return S_OK

End Function

'IDropTarget::DragLeave
Function IDropTarget.DragLeave () As HRESULT
    Print "IDropTarget::DragLeave"
    'reset the AllowDrop member:
    m_fAllowDrop = FALSE
    Return S_OK
End Function

'IDropTarget::Drop
Function IDropTarget.Drop ( ByVal pDataObject As IDataObject Ptr, ByVal grfKeyState As DWORD, ByVal pt As POINTL, ByVal pdwEffect As DWORD Ptr) As HRESULT
    Print "IDropTarget::Drop"
    Dim stgmed As STGMEDIUM
    Dim index As Integer

    'if we actually allow the drop, get the STGMEDIUM
    If m_fAllowDrop Then
        For index = 0 To m_nNumFormats -1
            If pDataObject->GetData(@m_pfmtetc[index], @stgmed) = S_OK Then
                'Call the drop extraction function
                DataDrop(m_hwnd, @m_pfmtetc[index], @stgmed)
                *pdwEffect = DROPEFFECT_COPY
                'release the data using the COM API
                ReleaseStgMedium(@stgmed)
                m_fAllowDrop = FALSE
                Return S_OK
            End If
        Next index
    End If
    'If we don't want a drop, do nothing and set the appropriate cursor:
    *pdwEffect = DROPEFFECT_NONE
    m_fAllowDrop = FALSE
    Return S_OK
End Function

DragEnter, DragOver und DragLeave machen im Prinzip erstmal das selbe, nämlich eine Rückmeldung geben ob wir einen Drop erlauben - was in erster Linie vom Inhalt abhängt, außerdem wird das in einem Flag gespeichert. Die Abfrage nach pDataself ist für den Fall enthalten, dass wir selbst einen Drag starten und wir nicht wollen, dass wir auf uns selbst droppen. Ich hatte das zuerst als shared - Variable, aber die muss man vorher definieren. Um das zu vermeiden, habe ich eine helper-Function definiert, mit der man den Pointer zum eigenen Dataobject im Type ablegen kann:

'helper Function:
Function IDropTarget.RegisterSelf (ByVal pDataObject As IDataObject Ptr) As Integer
    m_dataself=pDataObject
    Return TRUE
End Function

Da wir die Standardimplementaion der Interfaces neu definieren, ändern sich damit auch die damit verbundenen Funktionen. Damit es keine Compilerfehler gibt, wurde ganz am Anfang die RegisterDragDrop ebenfalls entdefiniert und muss jetzt mit dem neuen Type wieder neu deklariert werden:

Declare Function RegisterDragDrop Alias "RegisterDragDrop" (ByVal As HWND, ByVal As IDropTarget Ptr ) As HRESULT

Die Daten liegen also in Form eines IDataObject Interfaces vor (das wir im Detail später behandeln werden). Fürs erste brauchen wir nur den richtigen TYPE dafür (denn der vorhandene ist ja alte Version). Wenn wir nur einen Drop akzeptieren wollen, reicht eine Abstracte Variante um die VTABLE und die Struktur zu erstellen, mit der wir auf das von Windows bereitgestellte Objekt zugreifen können:

#Undef IDataObject

Type IDataObject EXTENDS IUnknown 'custom IDataObject Interface, requires Buxfix for IsEqualIID!

    'IDataObject Interface:
    Declare Abstract Function GetData ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr) As HRESULT
    Declare Abstract Function GetDataHere ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr) As HRESULT
    Declare Abstract Function QueryGetData ( ByVal pfmtetc As FORMATETC Ptr) As HRESULT
    Declare Abstract Function GetCanonicalFormatEtc ( ByVal pfmtetc As FORMATETC Ptr, ByVal pfmtetc2 As FORMATETC Ptr) As HRESULT
    Declare Abstract Function SetData ( ByVal pfmtetc As FORMATETC Ptr, ByVal pstgmed As STGMEDIUM Ptr, ByVal As BOOL) As HRESULT
    Declare Abstract Function EnumFormatEtc ( ByVal dwDirection As DWORD, ByVal ppEnumFmtetc As IEnumFORMATETC Ptr Ptr) As HRESULT
    Declare Abstract Function DAdvise ( ByVal pfmtetc As FORMATETC Ptr, ByVal advf As DWORD, ByVal pAdvSink As IAdviseSink Ptr, ByVal pDwConnection As PDWORD) As HRESULT
    Declare Abstract Function DUnadvise ( ByVal  dwConnection As DWORD) As HRESULT
    Declare Abstract Function EnumDAdvise ( ByVal ppEnumAdvise As IEnumSTATDATA Ptr Ptr) As HRESULT

End Type

Wir rufen daraus die Funktionen QueryGetData und GetData auf, die uns einfach nur sagen, ob die Dropquelle ein bestimmtes Format anbietet und bei letzterer Funktion auch den zweiten Teil des Formats anbietet, nämlich das STGMEDIUM. In unserem Fall ist das einfach der tatsächliche Pointer im HGlobal, wo unsere Daten liegen. Das übergeben wir einer Funktion DataDrop, die dann in unserem Hauptprogramm angesiedelt ist und die Daten extrahiert. Weil bei diesen Datentransfergeschichten meistens der Empfänger für den Verbleib des reservierten Speichers zuständig ist, müssen wir das STGMEDIUM auch noch freigeben. Wie wir sehen, ist der Code für das Interface ziemlich universell, denn wir können beliebig viele Formate akzeptieren und extrahieren und die tatsächliche Extraktion findet außerhalb des Interfaces statt. Was ich hier jetzt nicht implementiert habe ist die Rückmeldung auf verschiedene Tastenkombinationen - die in GrfKeyState übermittelt werden. Zum Beispiel könnte man bei gedrückter STRG-Taste statt dem Kopieren-Cursor einen Verschieben-Cursor erzeugen, es gibt verschiedene DROPEFFECTs dafür. Ebenfalls steckt da die Maustaste (links oder rechts) drin, der Explorer öffnet ja zum Beispiel ein Kontextmenü nach dem Drop, wenn mit der rechten Maustaste gezogen wurde. Wenn gewünscht, müsste man diese Variablen ebenfalls an die DataDrop-Funktion übergeben.

 

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