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
' ========================================================================================