How to implement the IDropTarget interface

Started by José Roca, September 05, 2011, 07:55:58 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

José Roca

 
The action of using the mouse to transfer data from one place to another is called drag-and-drop.

To made an application the target of a drag-and-drop operation we need to implement the IDropTarget interface and register the application window as drop target with a call to the function RegisterDragDrop.

A drop-target application is responsible for:


  • Determining the effect of the drop on the target application.
  • Incorporating any valid dropped data when the drop occurs.
  • Communicating target feedback to the source so the source application can provide appropriate visual feedback such as setting the cursor.
  • Implementing drag scrolling.
  • Registering and revoking its application windows as drop targets.

Applications that use drag-and-drop functionality must call the API function OleInitialize before calling any other function of the COM library. Because OLE operations aren't thread safe, OleInitialize specifies the concurrency model as single-thread apartment (STA).

When your application ends, you must call the API function OleUninitialize as the last COM call to close the COM library.

Here is the WinMain function of the attached example, showing the call to OleInitialize at the beginning of the function and the call to OleUninitialize at the end:


' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS DWORD, BYVAL hPrevInstance AS DWORD, BYVAL lpszCmdLine AS WSTRINGZ PTR, BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
'   SetProcessDPIAware

   ' Initialize the COM library
   OleInitialize %NULL

   ' // Create an instance of the class
   LOCAL pWindow AS IWindow
   pWindow = CLASS "CWindow"
   IF ISNOTHING(pWindow) THEN EXIT FUNCTION

   ' // Create the main window
   ' // Note: CW_USEDEFAULT is used as the default value When passing 0's as the width and height
   pWindow.CreateWindow(%NULL, "IDropTarget Demo", 0, 0, 0, 0, 0, %WS_EX_TOPMOST, CODEPTR(WindowProc))
   ' // Set the client size
   pWindow.SetClientSize 400, 80
   ' // Center the window
   pWindow.CenterWindow

   ' // Default message pump (you can replace it with your own)
   pWindow.DoEvents(nCmdShow)

   ' Uninitialize the COM library
   OleUninitialize

END FUNCTION
' ========================================================================================


The API function RegisterDragDrop registers the specified window as one that can be the target of an OLE drag-and-drop operation and specifies the IDropTarget instance to use for drop operations.

In the example, during the processing of the WM_CREATE message in the main window callback function we add a label control, create an instance of our implemented IDropTarget interface and register the label control as a candidate target of an OLE drag-and-drop operation with a call to RegisterDragDrop.


      CASE %WM_CREATE
         ' // Get a reference to the IWindow interface from the CREATESTRUCT structure
         pWindow = CWindow_GetObjectFromCreateStruct(lParam)
         ' // Add a label
         hLabel = pWindow.AddLabel(hwnd, %IDC_LABEL, "Drop a link here...", 20, 30, 360, 20, %WS_VISIBLE OR %WS_CHILD OR %WS_BORDER)
         IF hLabel THEN
            ' Create a new instance of our implemented IDropTarget interface
            pDropTarget = CLASS "CDropTarget"
            IF ISOBJECT(pDropTarget) THEN
               ' Sets the handle of the label
               pDropTarget.SetHwnd hLabel
               ' Locks the object to ensure that it stays in memory
               hr = CoLockObjectExternal(pDropTarget, %TRUE, %FALSE)
               ' Registers the specified window as one that can be the target
               ' of an OLE drag-and-drop operation and specifies the IDropTarget
               ' instance to use for drop operations.
               hr = RegisterDragDrop(hLabel, pDropTarget)
            END IF
         END IF
         EXIT FUNCTION


RevokeDragDrop revokes the registration of the specified application window as a potential target for OLE drag-and-drop operations.

In the example, during the processing of the WM_DESTROY message we revoke the registration of the label with a call to RevokeDragDrop and release the instance of our implemented IDropTarget interface.


      CASE %WM_DESTROY
         ' // Revokes the registration of the specified application window as a
         ' // potential target for OLE drag-and-drop operations.
         IF hLabel THEN RevokeDragDrop hLabel
         IF ISOBJECT(pDropTarget) THEN
            ' // Unlocks our IDropTarget interface
            hr = CoLockObjectExternal(pDropTarget, %FALSE, %FALSE)
            ' // Frees the memory used by our IDropTarget interface
            pDropTarget = NOTHING
         END IF
         ' // End the application
         PostQuitMessage 0
         EXIT FUNCTION


The DragEnter method of the IDropTarget interface determines whether a drop can be accepted and its effect if it is accepted. To determine it, we will call the QueryDataObject and DropEffect methods. QueryDataObject checks if the data object contains the kind of data wanted, and DropEffect determines the allowed effect based on the state of the keyboard.


      ' ----------------------------------------------------------------------------------
      ' Determines whether a drop can be accepted and its effect if it is accepted
      ' ----------------------------------------------------------------------------------
      METHOD DragEnter ( _                      ' VTable offset = 12
        BYVAL pDataObject AS IDataObject _      ' // Pointer to the interface of the source data object
      , BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         pdwEffect = %DROPEFFECT_NONE
         IF ISFALSE ISOBJECT(pDataObject) THEN
            METHOD = %E_FAIL
            EXIT METHOD
         END IF

         ' Check if the data object contains the data we want
         bAllowDrop = ME.QueryDataObject(pDataObject)
         IF bAllowDrop THEN
            ' Get the dropeffect based on keyboard state
            pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
            ' Bring the window to the foregroung
            IF hwnd THEN SetForegroundWindow hwnd
         END IF

         ' Return success
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------


This is our implementation of the QueryDataObject method:


      ' ----------------------------------------------------------------------------------
      ' Checks if the data object contains the data we want.
      ' In this example, asks for some CF_TEXT data, stored as a HGLOBAL in the clipboard
      ' ----------------------------------------------------------------------------------
      METHOD QueryDataObject (BYVAL pDataObject AS IDataObject) AS LONG

         LOCAL hr AS LONG
         LOCAL fmtc AS FORMATETC
         LOCAL stgmed AS STGMEDIUM

         fmtc.cfFormat = %CF_TEXT
         fmtc.ptd = %NULL
         fmtc.dwAspect = %DVASPECT_CONTENT
         fmtc.lindex = -1
         fmtc.tymed = %TYMED_HGLOBAL
         hr = pDataObject.GetData(fmtc, stgmed)
         IF hr = %S_OK THEN
            IF stgmed.hGlobal THEN METHOD = %TRUE
            ReleaseStgMedium stgmed
         END IF

      END METHOD
      ' ----------------------------------------------------------------------------------


And this is our implementation of the DropEffect method:


      ' ----------------------------------------------------------------------------------
      ' Retrieves the allowed drop effect
      ' ----------------------------------------------------------------------------------
      METHOD DropEffect (BYVAL grfKeyState AS DWORD, BYVAL pt AS POINTL, BYVAL dwAllowed AS DWORD) AS DWORD

         LOCAL dwEffect  AS DWORD

         ' 1. Check "pt" -> Is a  drop allowed at the specified coordinates?
         ' 2. Work out that the drop-effect should be based on grfKeyState
         IF (grfKeyState AND %MK_CONTROL) THEN
            dwEffect = dwAllowed AND %DROPEFFECT_COPY
         ELSEIF (grfKeyState AND %MK_SHIFT) THEN
            dwEffect = dwAllowed AND %DROPEFFECT_MOVE
         END IF

         ' 3. No key-modifiers were specified (or drop effect not allowed), so
         '    base the effect on those allowed by the dropsource
         IF dwEffect = 0 THEN
            IF (dwAllowed AND %DROPEFFECT_COPY) THEN dwEffect = %DROPEFFECT_COPY
            IF (dwAllowed AND %DROPEFFECT_MOVE) THEN dwEffect = %DROPEFFECT_MOVE
            IF (dwAllowed AND %DROPEFFECT_LINK) THEN dwEffect = %DROPEFFECT_LINK
         END IF
         METHOD = dwEffect

      END METHOD
      ' ----------------------------------------------------------------------------------


The DragOver method is called whenever the state of the keyboard modifiers change or when the mouse moves. In our example, we call the DropEffect function to determine which drop effect is allowed and communicate it to the caller though the pdwEffect parameter.


      ' ----------------------------------------------------------------------------------
      ' Provides target feedback to the user through the DoDragDrop function
      ' ----------------------------------------------------------------------------------
      METHOD DragOver ( _                       ' VTable offset = 16
        BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         IF bAllowDrop THEN
            ' Get the dropeffect based on keyboard state
            pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
         ELSE
            pdwEffect = %DROPEFFECT_NONE
         END IF
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------


The DragLeave function is called whenever the mouse cursor is moved outside of our drop-target window, or the Escape key is pressed which cancels the drag-drop operation. In our example, we will simply return %S_OK.


      ' ----------------------------------------------------------------------------------
      ' Causes the drop target to suspend its feedback actions
      ' ----------------------------------------------------------------------------------
      METHOD DragLeave ( _                      ' VTable offset = 20
      ) AS LONG                                 ' HRESULT

         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------


Finally, in the Drop method we call the GetData method of the caller implementation of the IDataObject interface through the passed pDataObject pointer to retrieve data from the clipboard. In our example, we get the text and show it in the label. The way it has been implemented allows both to drop selected text or an hyperlink.


      ' ----------------------------------------------------------------------------------
      ' Drops the data into the target window
      ' ----------------------------------------------------------------------------------
      METHOD Drop ( _                           ' VTable offset = 24
        BYVAL pDataObject AS IDataObject _      ' // Pointer to the interface of the source data object
      , BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         pdwEffect = %DROPEFFECT_NONE
         IF ISFALSE ISOBJECT(pDataObject) THEN
            METHOD = %E_FAIL
            EXIT METHOD
         END IF

         ' Get the dropeffect based on keyboard state
         pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
         '  Ask IDataObject for some CF_TEXT data, stored as a HGLOBAL in the clipboard
         IF bAllowDrop THEN
            fmtc.cfFormat = %CF_TEXT
            fmtc.ptd = %NULL
            fmtc.dwAspect = %DVASPECT_CONTENT
            fmtc.lindex = -1
            fmtc.tymed = %TYMED_HGLOBAL
            hr = pDataObject.GetData(fmtc, stgmed)
            IF hr = %S_OK THEN
               IF stgmed.hGlobal THEN
                  ' Lock the hGlobal handle just in case isn't fixed memory
                  pData = GlobalLock(stgmed.hGlobal)
                  ' Store the data in a string variable
                  strData = @pData
                  ' Show the data in the window
                  IF hwnd THEN SetWindowText hwnd, @pData
                  ' Unlock the global data
                  GlobalUnlock stgmed.hGlobal
               END IF
               ' Free the memory used by the STGMEDIUM structure
               ReleaseStgMedium stgmed
            END IF
         END IF

         ' Return success
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------


Full example code


' ########################################################################################
' This example demostrates how to implement the IDropTarget interface with PowerBASIC
' and make a label the target of a drag and drop operation.
' Note: Instead of a label you can use any other kind of window.
' ########################################################################################

' CSED_PBWIN - Use the PBWIN compiler
#COMPILE EXE
#DIM ALL
'%UNICODE = 1

' // Include files for external files
#INCLUDE ONCE "CWindow.inc"   ' // CWindow class
#INCLUDE ONCE "oleidl.inc"

' ########################################################################################
' *** Custom implementation of the IDropTarget interface.
' The IDropTarget interface is one of the interfaces you implement to provide
' drag-and-drop operations in your application. It contains methods used in any
' application that can be a target for data during a drag-and-drop operation. A
' drop-target application is responsible for:

'  * Determining the effect of the drop on the target application.
'  * Incorporating any valid dropped data when the drop occurs.
'  * Communicating target feedback to the source so the source application can provide
'    appropriate visual feedback such as setting the cursor.
'  * Implementing drag scrolling.
'  * Registering and revoking its application windows as drop targets.

' The IDropTarget interface contains methods that handle all these responsibilities except
' registering and revoking the application window as a drop target, for which you must
' call the RegisterDragDrop and the RevokeDragDrop functions.
' You do not call the methods of IDropTarget directly. The DoDragDrop function calls the
' IDropTarget methods during the drag-and-drop operation.
' ########################################################################################

$CLSID_CDropTarget = GUID$("{F9E4BF70-EFA8-411E-A142-F4B02D89D619}")
$IID_IDropTarget = GUID$("{00000122-0000-0000-C000-000000000046}")

' // Need to declare it as common to avoid removal of methods
CLASS CDropTarget $CLSID_CDropTarget AS COMMON

   INSTANCE hr AS LONG
   INSTANCE hwnd AS DWORD
   INSTANCE bAllowDrop AS LONG
   INSTANCE fmtc AS FORMATETC
   INSTANCE stgmed AS STGMEDIUM
   INSTANCE pData AS ASCIIZ PTR
   INSTANCE strData AS STRING

   INTERFACE IDropTargetImpl $IID_IDropTarget

      INHERIT IUnknown

      ' ----------------------------------------------------------------------------------
      ' Determines whether a drop can be accepted and its effect if it is accepted
      ' ----------------------------------------------------------------------------------
      METHOD DragEnter ( _                      ' VTable offset = 12
        BYVAL pDataObject AS IDataObject _      ' // Pointer to the interface of the source data object
      , BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         pdwEffect = %DROPEFFECT_NONE
         IF ISFALSE ISOBJECT(pDataObject) THEN
            METHOD = %E_FAIL
            EXIT METHOD
         END IF

         ' Check if the data object contains the data we want
         bAllowDrop = ME.QueryDataObject(pDataObject)
         IF bAllowDrop THEN
            ' Get the dropeffect based on keyboard state
            pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
            ' Bring the window to the foregroung
            IF hwnd THEN SetForegroundWindow hwnd
         END IF

         ' Return success
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Provides target feedback to the user through the DoDragDrop function
      ' ----------------------------------------------------------------------------------
      METHOD DragOver ( _                       ' VTable offset = 16
        BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         IF bAllowDrop THEN
            ' Get the dropeffect based on keyboard state
            pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
         ELSE
            pdwEffect = %DROPEFFECT_NONE
         END IF
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Causes the drop target to suspend its feedback actions
      ' ----------------------------------------------------------------------------------
      METHOD DragLeave ( _                      ' VTable offset = 20
      ) AS LONG                                 ' HRESULT

         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Drops the data into the target window
      ' ----------------------------------------------------------------------------------
      METHOD Drop ( _                           ' VTable offset = 24
        BYVAL pDataObject AS IDataObject _      ' // Pointer to the interface of the source data object
      , BYVAL grfKeyState AS DWORD _            ' // Current state of keyboard modifier keys
      , BYVAL pt AS POINTL _                    ' // Current cursor coordinates (Must be BYVAL)
      , BYREF pdwEffect AS DWORD _              ' // Pointer to the effect of the drag-and-drop operation
      ) AS LONG                                 ' HRESULT

         pdwEffect = %DROPEFFECT_NONE
         IF ISFALSE ISOBJECT(pDataObject) THEN
            METHOD = %E_FAIL
            EXIT METHOD
         END IF

         ' Get the dropeffect based on keyboard state
         pdwEffect = ME.DropEffect(grfKeyState, pt, pdwEffect)
         '  Ask IDataObject for some CF_TEXT data, stored as a HGLOBAL in the clipboard
         IF bAllowDrop THEN
            fmtc.cfFormat = %CF_TEXT
            fmtc.ptd = %NULL
            fmtc.dwAspect = %DVASPECT_CONTENT
            fmtc.lindex = -1
            fmtc.tymed = %TYMED_HGLOBAL
            hr = pDataObject.GetData(fmtc, stgmed)
            IF hr = %S_OK THEN
               IF stgmed.hGlobal THEN
                  ' Lock the hGlobal handle just in case isn't fixed memory
                  pData = GlobalLock(stgmed.hGlobal)
                  ' Store the data in a string variable
                  strData = @pData
                  ' Show the data in the window
                  IF hwnd THEN SetWindowText hwnd, @pData
                  ' Unlock the global data
                  GlobalUnlock stgmed.hGlobal
               END IF
               ' Free the memory used by the STGMEDIUM structure
               ReleaseStgMedium stgmed
            END IF
         END IF

         ' Return success
         METHOD = %S_OK

      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ==================================================================================
      ' *** We can add custom methods and properties here ***
      ' ==================================================================================

      ' ----------------------------------------------------------------------------------
      ' Window handle of the control that has been registered for drag and drop operations
      ' ----------------------------------------------------------------------------------
      METHOD SetHwnd (BYVAL hndl AS DWORD) AS LONG
         hwnd = hndl
         METHOD = %S_OK
      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Returns an string containing the text of the dropped link or text
      ' ----------------------------------------------------------------------------------
      METHOD GetData (BYREF pstrData AS STRING) AS LONG
         pstrData = strData
         METHOD = %S_OK
      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Retrieves the allowed drop effect
      ' ----------------------------------------------------------------------------------
      METHOD DropEffect (BYVAL grfKeyState AS DWORD, BYVAL pt AS POINTL, BYVAL dwAllowed AS DWORD) AS DWORD

         LOCAL dwEffect  AS DWORD

         ' 1. Check "pt" -> Is a  drop allowed at the specified coordinates?
         ' 2. Work out that the drop-effect should be based on grfKeyState
         IF (grfKeyState AND %MK_CONTROL) THEN
            dwEffect = dwAllowed AND %DROPEFFECT_COPY
         ELSEIF (grfKeyState AND %MK_SHIFT) THEN
            dwEffect = dwAllowed AND %DROPEFFECT_MOVE
         END IF

         ' 3. No key-modifiers were specified (or drop effect not allowed), so
         '    base the effect on those allowed by the dropsource
         IF dwEffect = 0 THEN
            IF (dwAllowed AND %DROPEFFECT_COPY) THEN dwEffect = %DROPEFFECT_COPY
            IF (dwAllowed AND %DROPEFFECT_MOVE) THEN dwEffect = %DROPEFFECT_MOVE
            IF (dwAllowed AND %DROPEFFECT_LINK) THEN dwEffect = %DROPEFFECT_LINK
         END IF
         METHOD = dwEffect

      END METHOD
      ' ----------------------------------------------------------------------------------

      ' ----------------------------------------------------------------------------------
      ' Checks if the data object contains the data we want.
      ' In this example, asks for some CF_TEXT data, stored as a HGLOBAL in the clipboard
      ' ----------------------------------------------------------------------------------
      METHOD QueryDataObject (BYVAL pDataObject AS IDataObject) AS LONG

         LOCAL hr AS LONG
         LOCAL fmtc AS FORMATETC
         LOCAL stgmed AS STGMEDIUM

         fmtc.cfFormat = %CF_TEXT
         fmtc.ptd = %NULL
         fmtc.dwAspect = %DVASPECT_CONTENT
         fmtc.lindex = -1
         fmtc.tymed = %TYMED_HGLOBAL
         hr = pDataObject.GetData(fmtc, stgmed)
         IF hr = %S_OK THEN
            IF stgmed.hGlobal THEN METHOD = %TRUE
            ReleaseStgMedium stgmed
         END IF

      END METHOD
      ' ----------------------------------------------------------------------------------

   END INTERFACE

END CLASS
' ########################################################################################


' ########################################################################################
' Testing code
' ########################################################################################

%IDC_LABEL = 1001

' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS DWORD, BYVAL hPrevInstance AS DWORD, BYVAL lpszCmdLine AS WSTRINGZ PTR, BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
'   SetProcessDPIAware

   ' Initialize the COM library
   OleInitialize %NULL

   ' // Create an instance of the class
   LOCAL pWindow AS IWindow
   pWindow = CLASS "CWindow"
   IF ISNOTHING(pWindow) THEN EXIT FUNCTION

   ' // Create the main window
   ' // Note: CW_USEDEFAULT is used as the default value When passing 0's as the width and height
   pWindow.CreateWindow(%NULL, "IDropTarget Demo", 0, 0, 0, 0, 0, %WS_EX_TOPMOST, CODEPTR(WindowProc))
   ' // Set the client size
   pWindow.SetClientSize 400, 80
   ' // Center the window
   pWindow.CenterWindow

   ' // Default message pump (you can replace it with your own)
   pWindow.DoEvents(nCmdShow)

   ' Uninitialize the COM library
   OleUninitialize

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main callback function.
' ========================================================================================
FUNCTION WindowProc (BYVAL hwnd AS DWORD, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG

   LOCAL hr AS LONG
   STATIC hLabel AS DWORD
   STATIC pDropTarget AS IDropTargetImpl
   STATIC pWindow AS IWindow

   SELECT CASE uMsg

      CASE %WM_CREATE
         ' // Get a reference to the IWindow interface from the CREATESTRUCT structure
         pWindow = CWindow_GetObjectFromCreateStruct(lParam)
         ' // Add a label
         hLabel = pWindow.AddLabel(hwnd, %IDC_LABEL, "Drop a link here...", 20, 30, 360, 20, %WS_VISIBLE OR %WS_CHILD OR %WS_BORDER)
         IF hLabel THEN
            ' Create a new instance of our implemented IDropTarget interface
            pDropTarget = CLASS "CDropTarget"
            IF ISOBJECT(pDropTarget) THEN
               ' Sets the handle of the label
               pDropTarget.SetHwnd hLabel
               ' Locks the object to ensure that it stays in memory
               hr = CoLockObjectExternal(pDropTarget, %TRUE, %FALSE)
               ' Registers the specified window as one that can be the target
               ' of an OLE drag-and-drop operation and specifies the IDropTarget
               ' instance to use for drop operations.
               hr = RegisterDragDrop(hLabel, pDropTarget)
            END IF
         END IF
         EXIT FUNCTION

      CASE %WM_COMMAND
         SELECT CASE LO(WORD, wParam)
            CASE %IDCANCEL
               ' // If the Escape key has been pressed...
               IF HI(WORD, wParam) = %BN_CLICKED THEN
                  ' // ... close the application by sending a WM_CLOSE message
                  SendMessage hwnd, %WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE %WM_DESTROY
         ' // Revokes the registration of the specified application window as a
         ' // potential target for OLE drag-and-drop operations.
         IF hLabel THEN RevokeDragDrop hLabel
         IF ISOBJECT(pDropTarget) THEN
            ' // Unlocks our IDropTarget interface
            hr = CoLockObjectExternal(pDropTarget, %FALSE, %FALSE)
            ' // Frees the memory used by our IDropTarget interface
            pDropTarget = NOTHING
         END IF
         ' // End the application
         PostQuitMessage 0
         EXIT FUNCTION

   END SELECT

   ' // Pass unprocessed messages to Windows
   FUNCTION = DefWindowProc(hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================