Option Explicit
Private Enum E_TYMED
TYMED_NULL = 0
TYMED_HGLOBAL = 1
TYMED_FILE = 2
TYMED_ISTREAM = 4
TYMED_ISTORAGE = 8
TYMED_GDI = 16
TYMED_MFPICT = 32
TYMED_ENHMF = 64
End Enum
Private Enum DVASPECT
DVASPECT_CONTENT = 1
DVASPECT_THUMBNAIL = 2
DVASPECT_ICON = 4
DVASPECT_DOCPRINT = 8
End Enum
Private Type FORMATETC
cfFormat As Long
#If Win64 Then
ptd As LongLong
#Else
ptd As Long
#End If
dwAspect As DVASPECT
lindex As Long
tymed As E_TYMED
End Type
Private Type STGMEDIUM
tymed As E_TYMED
#If Win64 Then
pData As LongLong
#Else
pData As Long
#End If
pUnkForRelease As Object
End Type
Private Type DRAG_DROP
#If Win64 Then
pVtable As LongLong
Func(6) As LongLong
hwnd As LongLong
CallBackAddr As LongLong
#Else
pVtable As Long
Func(6) As Long
hwnd As Long
CallBackAddr As Long
#End If
FilePath As String
End Type
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
#If VBA7 Then
#If Win64 Then
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongPtrA" (ByVal hwnd As LongLong, ByVal nIndex As Long, ByVal dwNewLong As LongLong) As Long
Private Declare PtrSafe Function GetWindowLong Lib "user32.dll" Alias "GetWindowLongPtrA" (ByVal hwnd As LongLong, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal Point As LongLong) As LongLong
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongLong)
#Else
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare PtrSafe Function GetWindowLong Lib "user32.dll" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
#End If
Private Declare PtrSafe Function RegisterDragDrop Lib "ole32" (ByVal hwnd As LongPtr, ByVal pDropTarget As LongPtr) As Long
Private Declare PtrSafe Function RevokeDragDrop Lib "ole32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function DispCallFunc Lib "oleaut32.dll" (ByVal pvInstance As LongPtr, ByVal offsetinVft As LongPtr, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As LongPtr, ByRef retVAR As Variant) As Long
Private Declare PtrSafe Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
Private Declare PtrSafe Sub DragAcceptFiles Lib "shell32.dll" (ByVal hwnd As LongPtr, ByVal fAccept As Long)
Private Declare PtrSafe Function DragQueryFileW Lib "shell32.dll" (ByVal hDrop As LongPtr, ByVal iFile As Long, Optional ByVal lpszFile As LongPtr, Optional ByVal cch As Long) As Long
Private Declare PtrSafe Function SysReAllocStringLen Lib "oleaut32.dll" (ByVal pBSTR As LongPtr, Optional ByVal pszStrPtr As LongPtr, Optional ByVal Length As Long) As Long
Private Declare PtrSafe Function ReleaseStgMedium Lib "ole32.dll" (pMedium As STGMEDIUM) As Long
Private Declare PtrSafe Function IsWindow Lib "user32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function DrawMenuBar Lib "user32.dll" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As LongPtr) As Long
Private Declare PtrSafe Function DrawText Lib "user32" Alias "DrawTextA" (ByVal hdc As LongPtr, ByVal lpStr As String, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long
Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hdc As LongPtr, ByVal hObject As LongPtr) As LongPtr
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function ReleaseDC Lib "user32" (ByVal hwnd As LongPtr, ByVal hdc As LongPtr) As Long
Private Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hdc As LongPtr, ByVal nIndex As Long) As Long
Private Declare PtrSafe Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Declare PtrSafe Function GetLongPathNameW Lib "kernel32" (ByVal lpszShortPath As LongPtr, ByVal lpszLongPath As LongPtr, ByVal cchBuffer As Long) As Long
#Else
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA " (ByVal hwnd As Long , ByVal nIndex As Long, ByVal dwNewLong As Long ) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function RegisterDragDrop Lib "ole32" (ByVal hwnd As Long, ByVal pDropTarget As Long) As Long
Private Declare Function RevokeDragDrop Lib "ole32" (ByVal hwnd As Long) As Long
Private Declare Function DispCallFunc Lib "oleaut32.dll" (ByVal pvInstance As Long, ByVal offsetinVft As Long, ByVal CallConv As Long, ByVal retTYP As Integer, ByVal paCNT As Long, ByRef paTypes As Integer, ByRef paValues As Long, ByRef retVAR As Variant) As Long
Private Declare Sub SetLastError Lib "kernel32.dll" (ByVal dwErrCode As Long)
Private Declare Sub DragAcceptFiles Lib "shell32.dll" (ByVal hwnd As Long, ByVal fAccept As Long)
Private Declare Function DragQueryFileW Lib "shell32.dll" (ByVal hDrop As Long, ByVal iFile As Long, Optional ByVal lpszFile As Long, Optional ByVal cch As Long) As Long
Private Declare Function SysReAllocStringLen Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long, Optional ByVal Length As Long) As Long
Private Declare Function ReleaseStgMedium Lib "ole32.dll" (pMedium As STGMEDIUM) As Long
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function DrawMenuBar Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function IUnknown_GetWindow Lib "shlwapi" Alias "#172" (ByVal pIUnk As IUnknown, ByVal hUf As Long) As Long
Private Declare Function DrawText Lib "user32" Alias "DrawTextA" (ByVal hdc As Long, ByVal lpStr As String, ByVal nCount As Long, lpRect As RECT, ByVal wFormat As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
Private Declare Function GetLongPathNameW Lib "kernel32" (ByVal lpszShortPath As Long, ByVal lpszLongPath As Long, ByVal cchBuffer As Long) As Long
#End If
Private DropTargetCollection As Collection
Private tDragDrop As DRAG_DROP
Private oForm As Object, sPublicEventHandlerName As String
'____________________________________________________ PUBLIC SUBS _______________________________________________________
Public Sub EnableDragAndDrop( _
ByVal DropTargetObject As Object, _
Optional ByVal PublicEventHandlerName As String, _
Optional bEnableDrop As Boolean = True _
)
#If Win64 Then
Dim hwnd As LongLong
#Else
Dim hwnd As Long
#End If
Call SetControlFocus(DropTargetObject)
Call IUnknown_GetWindow(DropTargetObject, VarPtr(hwnd))
If bEnableDrop Then
sPublicEventHandlerName = PublicEventHandlerName
Set oForm = GetParentForm(DropTargetObject)
Call AddMinimizeMenu(oForm)
Call AddToRemoveFromCollection(DropTargetObject)
If Len(PublicEventHandlerName) = 0 Then Exit Sub
If IsWindow(hwnd) Then
With tDragDrop
.pVtable = VarPtr(.Func(0))
.hwnd = hwnd
#If Win64 Then
.CallBackAddr = VBA.CLngLng(AddressOf OnDrop)
.Func(0) = VBA.CLngLng(AddressOf QueryInterface)
.Func(1) = VBA.CLngLng(AddressOf AddRef)
.Func(2) = VBA.CLngLng(AddressOf Release)
.Func(3) = VBA.CLngLng(AddressOf DragEnter)
.Func(4) = VBA.CLngLng(AddressOf DragOver)
.Func(5) = VBA.CLngLng(AddressOf DragLeave)
.Func(6) = VBA.CLngLng(AddressOf Drop)
#Else
.CallBackAddr = VBA.CLng(AddressOf OnDrop)
.Func(0) = VBA.CLng(AddressOf QueryInterface)
.Func(1) = VBA.CLng(AddressOf AddRef)
.Func(2) = VBA.CLng(AddressOf Release)
.Func(3) = VBA.CLng(AddressOf DragEnter)
.Func(4) = VBA.CLng(AddressOf DragOver)
.Func(5) = VBA.CLng(AddressOf DragLeave)
.Func(6) = VBA.CLng(AddressOf Drop)
#End If
Call RevokeDragDrop(.hwnd)
Call RegisterDragDrop(.hwnd, VarPtr(.pVtable))
Call DragAcceptFiles(.hwnd, True)
End With
End If
Else
Call AddToRemoveFromCollection(DropTargetObject, False)
Call RevokeDragDrop(hwnd)
End If
End Sub
'____________________________________________________ IDropTarget Funcs ____________________________________________________
#If Win64 Then
Private Function QueryInterface(This As DRAG_DROP, ByVal riid As LongLong, ByRef pObj As LongLong) As Long
#Else
Private Function QueryInterface(This As DRAG_DROP, ByVal riid As Long, ByRef pObj As Long) As Long
#End If
Const E_NOINTERFACE = &H80004002
QueryInterface = E_NOINTERFACE
End Function
Private Function AddRef(This As DRAG_DROP) As Long
'
End Function
Private Function Release(This As DRAG_DROP) As Long
'
End Function
#If Win64 Then
Private Function DragEnter(This As DRAG_DROP, ByVal pDataObj As IUnknown, ByVal KeyState As Long, ByVal pt As LongLong, ByRef pdwEffect As Long) As Long
#Else
Private Function DragEnter(This As DRAG_DROP, ByVal pDataObj As IUnknown, ByVal KeyState As Long, ByVal X As Long, ByVal Y As Long, ByRef pdwEffect As Long) As Long
#End If
'
End Function
Private Function DragLeave(This As DRAG_DROP) As Long
'
End Function
#If Win64 Then
Private Function DragOver(This As DRAG_DROP, ByVal KeyState As Long, ByVal pt As LongLong, ByRef pdwEffect As Long) As Long
#Else
Private Function DragOver(This As DRAG_DROP, ByVal KeyState As Long, ByVal X As Long, ByVal Y As Long, ByRef pdwEffect As Long) As Long
#End If
'
End Function
#If Win64 Then
Private Function Drop( _
This As DRAG_DROP, _
ByVal pDataObj As IUnknown, _
ByVal KeyState As Long, _
ByVal pt As LongLong, _
ByRef pdwEffect As Long _
) As Long
Const PTR_LEN = 8
Dim Ptr As LongLong, hWinFromPt As LongLong
#Else
Private Function Drop( _
This As DRAG_DROP, _
ByVal pDataObj As Long, _
ByVal KeyState As Long, _
ByVal X As Long, _
ByVal Y As Long, _
ByRef pdwEffect As Long _
) As Long
Const PTR_LEN = 4
Dim hWinFromPt As Long
#End If
Const CC_STDCALL = 4
Const CF_HDROP = 15
Const SM_CXVSCROLL = 2
Const SM_CXHTHUMB = 10
Dim uGUID(0 To 3) As Long
Dim tFmtc As FORMATETC
Dim tStg As STGMEDIUM
Dim tTextRect As RECT, XOffset As Long
Dim sFileNames() As String, KeyStates() As Integer
Dim hRes As Long, lFilesCount As Long, sBuffer As String, i As Long
Dim sShortPathName As String, sLongPathName As String, Ret As Long
Dim oCurrentListBox As Object
#If Win64 Then
Call CopyMemory(Ptr, pt, LenB(pt))
hWinFromPt = WindowFromPoint(Ptr)
#Else
hWinFromPt = WindowFromPoint(X, Y)
#End If
With tFmtc
.cfFormat = CF_HDROP
.ptd = 0
.dwAspect = DVASPECT_CONTENT
.lindex = -1
.tymed = TYMED_HGLOBAL
End With
hRes = CallDispFuncCOM(ObjPtr(pDataObj), 3 * PTR_LEN, vbLong, CC_STDCALL, VarPtr(tFmtc), VarPtr(tStg))
lFilesCount = DragQueryFileW(tStg.pData, &HFFFFFFFF, 0, 0)
ReDim sFileNames(lFilesCount - 1)
ReDim KeyStates(lFilesCount - 1)
'XOffset may need adjusting dependng on the Listbox Font.
XOffset = 2 * GetSystemMetrics(SM_CXVSCROLL) + 2 * GetSystemMetrics(SM_CXHTHUMB)
For i = 0 To lFilesCount - 1
sShortPathName = Space(1024) & vbNullChar
Ret = DragQueryFileW(tStg.pData, i, StrPtr(sShortPathName), Len(sShortPathName))
sShortPathName = Left(sShortPathName, Ret)
sLongPathName = Space(1024) & vbNullChar
Ret = GetLongPathNameW(StrPtr(sShortPathName), StrPtr(sLongPathName), Len(sLongPathName))
sFileNames(i) = Left(sLongPathName, Ret)
KeyStates(i) = KeyState
Set oCurrentListBox = DropTargetCollection(CStr(hWinFromPt))
If Not oCurrentListBox Is Nothing Then
With oCurrentListBox
tTextRect = GetTextRect(oCurrentListBox, Left(sLongPathName, Ret))
If Len(.Tag) = 0 Then
.Tag = 0
End If
If CSng(.Tag) < tTextRect.Right - tTextRect.Left Then
.Tag = PXtoPT((tTextRect.Right - tTextRect.Left) + XOffset, False) * 100 / oForm.Zoom
End If
End With
End If
Next
oCurrentListBox.ColumnWidths = oCurrentListBox.Tag
Call OnDrop(oCurrentListBox, sFileNames, KeyStates)
Call ReleaseStgMedium(tStg)
End Function
'____________________________________________________ Helper Routines ____________________________________________________
#If Win64 Then
Private Function CallDispFuncCOM( _
ByVal InterfacePointer As LongLong, _
ByVal VTableOffset As Long, _
ByVal FunctionReturnType As Long, _
ByVal CallConvention As Long, _
ParamArray FunctionParameters() As Variant _
) As Variant
Dim vParamPtr() As LongLong
#Else
Private Function CallDispFuncCOM( _
ByVal InterfacePointer As Long, _
ByVal VTableOffset As Long, _
ByVal FunctionReturnType As Long, _
ByVal CallConvention As Long, _
ParamArray FunctionParameters() As Variant _
) As Variant
Dim vParamPtr() As Long
#End If
If InterfacePointer = 0& Or VTableOffset < 0& Then Exit Function
If Not (FunctionReturnType And &HFFFF0000) = 0& Then Exit Function
Dim pIndex As Long, pCount As Long
Dim vParamType() As Integer
Dim vRtn As Variant, vParams() As Variant
vParams() = FunctionParameters()
pCount = Abs(UBound(vParams) - LBound(vParams) + 1&)
If pCount = 0& Then
ReDim vParamPtr(0 To 0)
ReDim vParamType(0 To 0)
Else
ReDim vParamPtr(0 To pCount - 1&)
ReDim vParamType(0 To pCount - 1&)
For pIndex = 0& To pCount - 1&
vParamPtr(pIndex) = VarPtr(vParams(pIndex))
vParamType(pIndex) = VarType(vParams(pIndex))
Next
End If
pIndex = DispCallFunc(InterfacePointer, VTableOffset, CallConvention, _
FunctionReturnType, pCount, vParamType(0), vParamPtr(0), vRtn)
If pIndex = 0& Then
CallDispFuncCOM = vRtn
Else
Call SetLastError(pIndex)
End If
End Function
Private Function GetParentForm(ByVal obj As Object) As Object
Do While TypeOf obj Is MSForms.Control
Set obj = obj.Parent
Loop
Set GetParentForm = obj
End Function
Private Sub SetControlFocus(ByVal obj As Object)
On Error Resume Next
CallByName obj, "SetFocus", VbMethod
End Sub
Private Sub AddToRemoveFromCollection(ByVal DropTarget As Object, Optional ByVal bAdd As Boolean = True)
#If Win64 Then
Dim hwnd As LongLong
#Else
Dim hwnd As Long
#End If
On Error Resume Next
Call IUnknown_GetWindow(DropTarget, VarPtr(hwnd))
If DropTargetCollection Is Nothing Then
Set DropTargetCollection = New Collection
End If
If bAdd Then
DropTargetCollection.Add DropTarget, CStr(hwnd)
Else
DropTargetCollection.Remove CStr(hwnd)
End If
If DropTargetCollection.Count = 0 Then
Debug.Print "all cleared."
Set DropTargetCollection = Nothing
End If
End Sub
Private Sub OnDrop(ByVal TargetObject As Object, FileNames() As String, KeyStates() As Integer)
Dim i As Long
On Error Resume Next
For i = LBound(FileNames) To UBound(FileNames)
CallByName oForm, sPublicEventHandlerName, VbMethod, TargetObject, FileNames(i), KeyStates(i)
If Err.Number = 438 Then
On Error GoTo 0
Err.Raise Number:=vbObjectError + 438, _
Description:="Can't find the Pseudo-Event Handler associated with " & TargetObject.Name & "." & vbNewLine & _
vbNewLine & "Please, check that the name of the Public Psseudo-Event handler located in the userform is correct."
End If
Next i
End Sub
Private Sub AddMinimizeMenu(ByVal Form As MSForms.UserForm, Optional ByVal bMin As Boolean = True)
Const GWL_STYLE As Long = (-16)
Const WS_MINIMIZEBOX = &H20000
#If Win64 Then
Dim hwnd As LongLong
#Else
Dim hwnd As Long
#End If
If bMin Then
Call IUnknown_GetWindow(Form, VarPtr(hwnd))
Call SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) Or WS_MINIMIZEBOX)
Call DrawMenuBar(hwnd)
End If
End Sub
Private Function GetTextRect(ByVal DropTarget As Object, ByVal sText As String) As RECT
Const DT_CALCRECT = &H400
#If Win64 Then
Dim hwnd As LongLong, hdc As LongLong, hPrevFont As LongLong
#Else
Dim hwnd As Long, hdc As Long, hPrevFont As Long
#End If
Dim tTextRect As RECT, IFont As stdole.IFont
Call IUnknown_GetWindow(DropTarget, VarPtr(hwnd))
hdc = GetDC(hwnd)
Set IFont = DropTarget.Font
hPrevFont = SelectObject(hdc, IFont.hFont)
Call DrawText(hdc, sText, Len(sText), tTextRect, DT_CALCRECT)
Call SelectObject(hdc, IFont.hFont)
Call ReleaseDC(hwnd, hdc)
tTextRect.Right = tTextRect.Right + 20
GetTextRect = tTextRect
End Function
Private Function ScreenDPI(ByVal bVert As Boolean) As Long
Const LOGPIXELSX As Long = 88
Const LOGPIXELSY As Long = 90
Static lDPI(1), hdc
If lDPI(0) = 0 Then
hdc = GetDC(0)
lDPI(0) = GetDeviceCaps(hdc, LOGPIXELSX)
lDPI(1) = GetDeviceCaps(hdc, LOGPIXELSY)
hdc = ReleaseDC(0, hdc)
End If
ScreenDPI = lDPI(Abs(bVert))
End Function
Private Function PXtoPT(ByVal Pixels As Single, ByVal bVert As Boolean) As Single
Const POINTSPERINCH As Long = 72
PXtoPT = (Pixels / (ScreenDPI(bVert) / POINTSPERINCH))
End Function