Jump to content

Recommended Posts

Posted
3 hours ago, WildByDesign said:

Matty, may I please have your permission to use this code in the Files Au3 file manager project?

For curiosity sake, I tested it out there briefly. It allowed me to drag and drop nicely from the ListView but also the TreeView as well once I modified the code slightly.

@MattyD Sorry to load you up on comments here. The wheels of thought keep on spinning.

One thing that I've found to be problematic about this whole DoDragDrop functionality is that it doesn't allow AutoIt to perform any other functions until the drop is complete. I assume that this has something to do with AutoIt not having multiple thread support.

For example, in Files Au3 in the previous drag and drop code, I have some code that takes care of bringing up the drop highlightiing of the TreeView items and ListView items as the cursor hovers over them to give the user a better idea of where they are dropping. But this doesn't seem to work with the DoDragDrop method. Nothing from the main While loop executes either. This drag and drop stuff is certainly complicated. 

Posted
3 minutes ago, MattyD said:

Supposedly it's meant to make working with drag images easier too. (if we want more than that cursor with the little plus-in-a-box).

Speaking of this, I have never had any success in creating any drag images with treeview or listview. There are only about 10-12 previous examples for this in the forum and none of them seem to work either. Nor the examples from the AutoIt help files. I wanted to create a drag image of the file type, for example, but was never successful with that unfortunately.

Posted
5 minutes ago, WildByDesign said:

it doesn't allow AutoIt to perform any other functions until the drop is complete

Yep, we're in a loop until DoDragDrop returns.  But, the GiveFeedback method is called as part of that loop - so maybe just hook into that for the short term  ;)

I think the proper way (from a very superficial look) is to setup a DropTarget object which is supposed to help with insertion marks for listviews. Not sure how that looks for a treeview, but its probably worth reading up on.

12 minutes ago, WildByDesign said:

I have never had any success in creating any drag images with treeview or listview

Ok, I'll add that to the list of things to investigate. 

Posted (edited)
8 hours ago, MattyD said:

I think the proper way (from a very superficial look) is to setup a DropTarget object which is supposed to help with insertion marks for listviews.

I spent some time looking into this today but I didn't fully understand how to create the DropTarget.

8 hours ago, MattyD said:

But, the GiveFeedback method is called as part of that loop - so maybe just hook into that for the short term  ;).

This does work pretty good for now. You've created some great structure with the __Mthd_GiveFeedback, __Mthd_DragEnterTarget and __Mthd_DragLeaveTarget functions. I'm using a combination of those right now to determine the handle of which ListView or TreeView I am currently hovering over and making the necessary drophilite changes. It may not be the proper way to do it but it seems to be working well enough.

Edited by WildByDesign
Posted

@MattyD

Will this work for creating an IDataObject? I don't know, as I haven't tried it.
Create a blank IDataObject using SHCreateDataObject, and then fill it using SetData.

Func __SHCreateDataObject($tIID_IDataObject, $ppidlFolder = 0, $cidl = 0, $papidl = 0, $pdtInner = 0)
    Local $aRes = DllCall("shell32.dll", "long", "SHCreateDataObject", _
                                         "ptr", $ppidlFolder, _          
                                         "uint", $cidl, _
                                         "ptr", $papidl, _ 
                                         "ptr", $pdtInner, _
                                         "struct*", $tIID_IDataObject, _
                                         "ptr*", 0)
    If @error Then Return SetError(1, 0, $aRes[0])
    Return $aRes[6]
EndFunc

Local $sIID_IDataObject = "{0000010E-0000-0000-C000-000000000046}"
Local $triid = _WinAPI_GUIDFromString($sIID_IDataObject)
Local $pIDataObject = __SHCreateDataObject($triid, 0, 0, 0, 0)      ;~ blank IDataObject

Local Const $tag_IDataObject = _
                    "GetData hresult(ptr;ptr*);" & _
                    "GetDataHere hresult(ptr;ptr*);" & _
                    "QueryGetData hresult(ptr);" & _
                    "GetCanonicalFormatEtc hresult(ptr;ptr*);" & _
                    "SetData hresult(ptr;ptr;bool);" & _
                    "EnumFormatEtc hresult(dword;ptr*);" & _
                    "DAdvise hresult(ptr;dword;ptr;dword*);" & _
                    "DUnadvise hresult(dword);" & _
                    "EnumDAdvise hresult(ptr*);"
Local $oIDataObject = ObjCreateInterface($pIDataObject, $sIID_IDataObject, $tag_IDataObject)

$tFORMATETC = _Create_FORMATETC()
$tSTGMEDIUM = _Create_STGMEDIUM_FileData($s_FilePath)

$oIDataObject.SetData(DllStructGetPtr($tFORMATETC), DllStructGetPtr($tSTGMEDIUM), 1)    ;~ fill IDataObject

 

Posted (edited)
On 1/23/2026 at 10:52 PM, MattyD said:

There seems to be a drag-drop mechanism baked into AutoIt's message handling

I noticed that you had to comment out Opt("GUICloseOnESC", 0) in your example. That baked in drag-drop mechanism is definitely problematic. You would typically want that Esc key available to cancel the operation.

Since I use OnEventMode for the majority of my projects, I decided to test Opt("GUIOnEventMode", 1) with your Demo example. I was expecting it to fail anyway. As expected, it also fails which is unfortunate. I think it's safe to assume that any of the Opt("GUI...") options will not work.

If you do end up thinking of a way to disable that baked in AutoIt drag-drop functionality without losing out on the Opt("GUI...") options or if you find a way to stop the built in drag-drop functionality from getting left in drag mode as Nine mentioned as well, please let me know. :)

Obviously, your subclassing fixes all of that. But I'm not certain that losing the Opt("GUI...") options is worth it.

EDIT: I think that I may have figured it out. Or at least a workaround.

Case $LVN_BEGINDRAG
    Return 1 ; or Return 0

As we know, $LVN_BEGINDRAG is not supposed to have a return value. However, I've just tested and it does cancel the built in drag operation while the DoDragDrop operation continues.

I don't fully understand all of the ramifications from this. However, in my own testing, both Return 0 and Return 1 work beautifully and solves the issue on my end. 

EDIT2: The best way to test this is with @MattyD's first Demo example where he had not yet subclassed the GUI.

Return      ; fails, 
Return 0    ; works
Return 1    ; works

 

Edited by WildByDesign
Posted (edited)
10 hours ago, jugador said:

Will this work for creating an IDataObject?

yep, seems like that should work. Thank you - that's going to save a bunch of time!

8 hours ago, WildByDesign said:
Return      ; fails, 
Return 0    ; works
Return 1    ; works

Also great pickup, yes this makes more sense than subclassing:

Return 0 from WM_DROPFILES
Return 0 from WM_NOTIFY when its a $LVN_BEGINDRAG or $LVN_BEGINRDRAG, otherwise return $GUI_RUNDEFMSG..

Edit: 

8 hours ago, WildByDesign said:

I noticed that you had to comment out Opt("GUICloseOnESC", 0) in your example

nah, I didn't have to - It just doesn't serve a purpose when we're bypassing all the autoIt handlers in the window proc :). The esc key can't close the window if we break the mechanism!

Edited by MattyD
Posted (edited)

@MattyD I have a question for you regarding the cursor from DRAGDROP_S_USEDEFAULTCURSORS.

Outside of this DoDragDrop function, do you know of any way to obtain the handle for that drag/drop cursor that is returned by DRAGDROP_S_USEDEFAULTCURSORS?

I am just curious about trying some things with _WinAPI_SetSystemCursor function or other similar functions. Thanks. :)

No longer needed. :)

Edited by WildByDesign
Posted

After a lot of trial and error, I finally got the proper OS drag image working and it has the built-in tooltip. Part of the problem was that it doesn't work when the script runs as x86. It seems to be 64-bit only.

I used the function from @ProgAndy's older script and modified it to work with your Demo example.

; Call function with:
; _SHDoDragDrop($hGUI, $pDataObj, $pDropSource,  BitOR($DROPEFFECT_COPY,$DROPEFFECT_LINK))

Func _SHDoDragDrop($hWnd, ByRef $pDataObj, ByRef $pDropSource, $dwDropEffects)
    Local $result = DllCall("shell32.dll","lresult","SHDoDragDrop", "hwnd", Null, "ptr", $pDataObj,"ptr", Null, "dword", BitOR($DROPEFFECT_COPY,$DROPEFFECT_LINK), "dword*", 0)
EndFunc

The real trick that ended up making it work was the Null for hWnd and the Null for drop source. Since with SHDoDragDrop, the OS will take care of both of those. In the end, we get the same beautiful drag image (with tooltip) that File Explorer uses that shows the file type icon. The tooltip shows whether the file is being copied or moved and to which directory the cursor is hovering over. This makes everything so much easier. 

Posted (edited)
9 hours ago, Netol said:

This code working fine only drag and drop element to outside but not permit drag and drop files inside of the listview

We can always handle the WM_DROPFILES messages for that.

However from my understanding, it would be better to register the window (and specific controls?) as drop targets with the RegisterDragDrop and handle the various methods by return certain effects.

The developer for Explorer++ suggests:

(from source and source)

Quote

    // The main window is registered as a drop target only so that the drag image will be
    // consistently shown when an item is being dragged. For the drag image to be shown, the
    // relevant IDropTargetHelper methods need to be called during the drag. To do that, the window
    // under the mouse needs to be registered as a drop target.
    // Rather than having to register every window, the top-level window can simply be registered
    // instead. That way, it will act as a fallback if there isn't a more specific child window
    // registered.

So that explains why we cannot see the drag image on our own window. The problem where I am right now is that I don't know how to setup an IDropTarget interface.

EDIT: Looks like we really need an IDropTargetHelper interface if we want to show the drag image over our own window.

Edited by WildByDesign
Posted
On 1/26/2026 at 6:38 AM, jugador said:

Will this work for creating an IDataObject? I don't know, as I haven't tried it.
Create a blank IDataObject using SHCreateDataObject, and then fill it using SetData.

Will this method work for multiple files?

For example, selecting multiple files from the ListView to drag and drop onto File Explorer.

Posted

Apologies, I haven't touched this for a few days - I'll hopefully have some more time/answers through the weekend.

1 hour ago, WildByDesign said:

Will this method work for multiple files?

I think so - it gives you a blank IDataObject, then the setup should resemble something like this (untested):

Global Const $tagFORMATETC = "struct;int cfFormat;ptr ptd;dword aspect;long index;dword tymed;endstruct"
Global Const $tagSTGMEDIUM = "struct;dword tymed;ptr handle;ptr pUnkForRelease;endstruct"
Global Const $tagDROPFILE = "struct;dword pFiles;long pt[2];bool fNC;bool fWide;endstruct"

$tFormatEtc = DllStructCreate($tagFORMATETC)
$tFormatEtc.cfFormat = $CF_HDROP
$tFormatEtc.ptd = 0
$tFormatEtc.aspect = $DVASPECT_CONTENT
$tFormatEtc.index = -1
$tFormatEtc.tymed = $TYMED_HGLOBAL

$hGblMem = _MemGlobalAlloc(20+(2* (StringLen($sFilename) + 2)), $GPTR)
$pDropFiles = _MemGlobalLock($hGblMem)
$tDropFiles = DllStructCreate($tagDROPFILE & StringFormat(";wchar filename[%d]", StringLen($sFilename)), $pDropFiles)
$tDropFiles.pFiles = 20 ;offset to filename/s
$tDropFiles.fNC = 0
$tDropFiles.fWide = True
$tDropFiles.filename = $sFilename
_MemGlobalUnlock($hGblMem)

$tStgMedium = DllStructCreate($tagSTGMEDIUM)
$tStgMedium.tymed = $TYMED_HGLOBAL
$tStgMedium.handle = $hGblMem
$tStgMedium.pUnkForRelease = 0

$pDataObj = SHCreateDataObject()
$oDataObj = ObjCreateInterface($pDataObj, $sIID_IDataObject, $tagIDataObject)
$oDataObj.SetData($tFormatEtc, $tStgMedium, 1)

Func SHCreateDataObject($ppidlFolder = 0, $cidl = 0, $papidl = 0, $pdtInner = 0)
    Local $aCall = DllCall("shell32.dll", "long", "SHCreateDataObject", _
        "ptr", $ppidlFolder, "uint", $cidl, "ptr", $papidl, "ptr", $pdtInner, _
        "struct*", _WinAPI_GUIDFromString($sIID_IDataObject), "ptr*", 0)
    If @error Then Return SetError(TranslateDllError())
    Return SetError($aCall[0], 0, $aCall[6])
EndFunc

Multi-file drops are the same, but $sFilename needs to hold multiple filenames delimited by null characters. This filename array is terminated with its own null wchar too - so you should finish with a double-null at the end.  https://learn.microsoft.com/en-us/windows/win32/shell/clipboard#cf_hdrop

PS. There's a chance that this snippet wont handle $sFilename correctly with nulls in the middle, but that'll be something we find out I guess!

Posted

yes, this method work for multiple files
To make it work, you need to put the selected items in an array.

Local $aMultipleFiles = [$sItemText_A, $sItemText_B, ....]  ; put the selected items in an array.

;$pDataObj = GetDataObjectOfFile($hGUI, $sItemText)
$pDataObj = GetDataObjectOfFile_C($aMultipleFiles)

Func GetDataObjectOfFile_C(ByRef $sPath)
    If UBound($sPath) = 0 Then Return 0

    Local $tIID_IDataObject = _WinAPI_GUIDFromString($sIID_IDataObject)
    Local $pIDataObject = __SHCreateDataObject($tIID_IDataObject, 0, 0, 0, 0)
    If Not $pIDataObject Then Return 0

    Local Const $tag_IDataObject = _
                        "GetData hresult(ptr;ptr*);" & _
                        "GetDataHere hresult(ptr;ptr*);" & _
                        "QueryGetData hresult(ptr);" & _
                        "GetCanonicalFormatEtc hresult(ptr;ptr*);" & _
                        "SetData hresult(ptr;ptr;bool);" & _
                        "EnumFormatEtc hresult(dword;ptr*);" & _
                        "DAdvise hresult(ptr;dword;ptr;dword*);" & _
                        "DUnadvise hresult(dword);" & _
                        "EnumDAdvise hresult(ptr*);"
    Local $oIDataObject = ObjCreateInterface($pIDataObject, $sIID_IDataObject, $tag_IDataObject)
    If Not IsObj($oIDataObject) Then
        _Release($pIDataObject)
        Return 0
    Endif

    Local $tFORMATETC, $tSTGMEDIUM
    __Fill_tag_FORMATETC($tFORMATETC)
    __Fill_tag_STGMEDIUM($tSTGMEDIUM, $sPath)

    $oIDataObject.SetData(DllStructGetPtr($tFORMATETC), DllStructGetPtr($tSTGMEDIUM), 1)
    _AddRef($pIDataObject)

    Return $pIDataObject
EndFunc

Func __Fill_tag_FORMATETC(Byref $tFORMATETC)
    Local Const $CF_HDROP = 15
    Local Const $TYMED_HGLOBAL = 1

    $tFORMATETC = DllStructCreate("ushort cfFormat; ptr ptd; uint dwAspect; int lindex; uint tymed")
    DllStructSetData($tFORMATETC, "cfFormat", $CF_HDROP)
    DllStructSetData($tFORMATETC, "dwAspect", 1)
    DllStructSetData($tFORMATETC, "lindex", -1)
    DllStructSetData($tFORMATETC, "tymed", $TYMED_HGLOBAL)
EndFunc

Func __Fill_tag_STGMEDIUM(Byref $tSTGMEDIUM, Byref $aFiles)
    Local Const $CF_HDROP = 15
    Local Const $TYMED_HGLOBAL = 1

    Local $sFileList = ""
    For $i = 0 To UBound($aFiles) - 1
        $sFileList &= $aFiles[$i] & Chr(0)
    Next
    $sFileList &= Chr(0)

    Local $iSize = 20 + (StringLen($sFileList) * 2)

    Local $hGlobal = DllCall("kernel32.dll", "ptr", "GlobalAlloc", "uint", 0x2042, "ulong_ptr", $iSize)[0]
    Local $pLock = DllCall("kernel32.dll", "ptr", "GlobalLock", "ptr", $hGlobal)[0]

    Local $tDROPFILES = DllStructCreate("dword pFiles; int x; int y; bool fNC; bool fWide", $pLock)
    DllStructSetData($tDROPFILES, "pFiles", 20) 
    DllStructSetData($tDROPFILES, "fWide", True)

    Local $tPaths = DllStructCreate("wchar[" & StringLen($sFileList) & "]", $pLock + 20)
    DllStructSetData($tPaths, 1, $sFileList)

    DllCall("kernel32.dll", "bool", "GlobalUnlock", "ptr", $hGlobal)

    $tSTGMEDIUM = DllStructCreate("uint tymed; ptr hGlobal; ptr pUnkForRelease")
    DllStructSetData($tSTGMEDIUM, "tymed", $TYMED_HGLOBAL)
    DllStructSetData($tSTGMEDIUM, "hGlobal", $hGlobal)
    DllStructSetData($tSTGMEDIUM, "pUnkForRelease", 0)
EndFunc

Func __SHCreateDataObject($tIID_IDataObject, $ppidlFolder = 0, $cidl = 0, $papidl = 0, $pdtInner = 0)
    Local $aRes = DllCall("shell32.dll", "long", "SHCreateDataObject", _
                                         "ptr", $ppidlFolder, _          
                                         "uint", $cidl, _
                                         "ptr", $papidl, _ 
                                         "ptr", $pdtInner, _
                                         "struct*", $tIID_IDataObject, _
                                         "ptr*", 0)
    If @error Then Return SetError(1, 0, $aRes[0])
    Return $aRes[6]
EndFunc

 

and for SHDoDragDrop do this, it worked.

Func DoDragDrop($pDataObj, $pDropSource, $iOKEffects)
    ;We must pass a IID_IDropSource ptr.
    $pDropSource = _QueryInterface($pDropSource, $sIID_IDropSource)
    _Release($pDropSource)

;   Local $aCall = DllCall("ole32.dll", "long", "DoDragDrop", "ptr", $pDataObj, "ptr", $pDropSource, "dword", $iOKEffects, "ptr*", 0)
    Local $aCall = DllCall("shell32.dll", "long", "SHDoDragDrop", "hwnd", $hGUI, "ptr", $pDataObj, "ptr", $pDropSource, "dword", $iOKEffects, "ptr*", 0)
    If @error Then Return SetError(@error, @extended, $aCall)
    ConsoleWrite("DragDrop: " & Hex($aCall[0]) & " " & _WinAPI_GetErrorMessage($aCall[0]) & @CRLF)
    Return SetError($aCall[0], 0, $aCall[4])
EndFunc   ;==>DoDragDrop

 

Posted (edited)
3 hours ago, jugador said:

yes, this method work for multiple files
To make it work, you need to put the selected items in an array.

Thank you so much. :)

I have implemented your solution. It works very good for multiple file selection drag and drop from the ListView. The selected files copy to where they are supposed to go and works well.

There was only one problem and I'm not sure exactly why. There is a visual glitch that makes the icon not show on the drag image and the tooltip part is garbled up a bit and cutting off at the end. Also some black coloring showing that wasn't showing before in my single file SHDoDragDrop function.

drag.png

I have tried a few things to see why that might be happening. But so far I cannot figure it out.

EDIT: The interesting thing is that if I hit the Printscreen button which brings up the snipping tool in Win11, I can briefly see the icon(s). So they are there. Just not completely visible.

EDIT2: It happens even when dragging a single file as well from ListView.

My TreeView still uses the original GetDataObjectOfFile function since my TreeView only allows single selection for drag and drop. This original GetDataObjectOfFile shows the drag icon properly. 

So the problem seems to only affect the newer GetDataObjectOfFile_C function with single or multiple selection drag.

Edited by WildByDesign
Posted

I am wondering if the drag image issue is somehow related to memory allocation.

I remember 100% of my attempts with 32-bit script and SHDoDragDrop had the same issue with the drag image being all white and print screen would show it briefly as well.

But this issue is now happening on 64-bit script as well but only with the new GetDataObjectOfFile_C function. I feel like it might be related to memory allocations but that is an unfamiliar area for me.

Posted

I could be wrong, but to get the thumbnail going I suspect our IDataObject needs to contain a CFSTR_SHELLIDLIST entry (in addition to the CF_HDROP).

FYI, when the shell does the work for a single file (as in the first example), we end up with a DataObject containing entries for the following formats:

  • CF_HDROP
  • CFSTR_SHELLIDLIST
  • CFSTR_FILECONTENTS
  • CFSTR_FILENAMEA
  • CFSTR_FILENAMEW
  • CFSTR_FILEDESCRIPTORW
Posted (edited)

only CF_HDROP is set. so to solve the visual glitch we need more code.you can try adding IDragSourceHelper.
meanwhile try this code, see if it solves your issue.

Local $aMultipleFiles = [$sItemText_A, $sItemText_B, ....]  ; put the selected items in an array.
$pDataObj = GetDataObjectOfFile_B($aMultipleFiles)

Func GetDataObjectOfFile_B(ByRef $sPath)
    Local $iCount = UBound($sPath)
    If $iCount = 0 Then Return 0

    Local $sParentPath = StringLeft($sPath[0], StringInStr($sPath[0], "\", 0, -1) - 1)
    Local $pParentPidl = _WinAPI_ShellILCreateFromPath($sParentPath)

    Local $tPidls = DllStructCreate("ptr[" & $iCount & "]")

    Local $pFullPidl, $pRelativePidl, $last_SHITEMID
    For $i = 0 To $iCount - 1
        $pFullPidl = _WinAPI_ShellILCreateFromPath($sPath[$i])
        $last_SHITEMID = DllCall("shell32.dll", "ptr", "ILFindLastID", "ptr", $pFullPidl)[0]
        $pRelativePidl = DllCall("shell32.dll", "ptr", "ILClone", "ptr", $last_SHITEMID)[0]
        DllStructSetData($tPidls, 1, $pRelativePidl, $i + 1)
        DllCall("shell32.dll", "none", "ILFree", "ptr", $pFullPidl)
    Next

    Local $tIID_IDataObject = _WinAPI_GUIDFromString($sIID_IDataObject)
    Local $pIDataObject = __SHCreateDataObject($tIID_IDataObject, $pParentPidl, $iCount, DllStructGetPtr($tPidls), 0)

    DllCall("shell32.dll", "none", "ILFree", "ptr", $pParentPidl)
    For $i = 1 To $iCount
        DllCall("shell32.dll", "none", "ILFree", "ptr", DllStructGetData($tPidls, 1, $i))
    Next

    If Not $pIDataObject Then Return 0
    Return $pIDataObject
EndFunc

 

Edited by jugador
Posted
1 hour ago, jugador said:

you can try adding IDragSourceHelper.

I would like to try it but I really don't understand all of various interfaces or how to create them.

I believe the only thing missing now is being able to show the drag image over our own window. From my limited understanding, that involves RegisterDragDrop, IDropTarget interface and IDropTargetHelper interface. Then we would be able to handle the various methods such as IDropTargetHelper::DragEnter and others. This should allow drop onto our window(s) without needing to use WM_DROPFILES.

1 hour ago, jugador said:

meanwhile try this code, see if it solves your issue.

Indeed, yes. 100%. This solved the visual glitches perfectly. It even shows the multiple file type icons when dragging multiple items from the ListView.

Thank you. :)

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...