WildByDesign Posted yesterday at 10:33 AM Posted yesterday at 10:33 AM Just in case you guys want to test what I have implemented so far, I've created a branch shell-dodragdrop for the Files Au3 project for testing this stuff. If you want to use it as a testbed for playing with any of your drag and drop code, it can make for a good testbed. For the purpose of testing, I only have DROPEFFECT_COPY and DROPEFFECT_LINK enabled for the drag and drop. I have tested it with DROPEFFECT_MOVE as well and it works great since the OS makes the logical decisions on whether to copy, move, etc. But for the sake of no surprises, I have removed DROPEFFECT_MOVE for testing. Drag and drop from the TreeView and ListView are both working great to external programs like File Explorer. Drag and drop to (or within) Files Au3 is not working yet because I don't know how to do the drop target interfaces or RegisterDragDrop function. I am going to try to play around with RegisterDragDrop today though. MattyD 1
MattyD Posted yesterday at 12:20 PM Posted yesterday at 12:20 PM (edited) Ok, work in progress - here are the bones of a DropTarget Object. (for receiving files) I'm stashing the incoming pDataObject internally in our DropTarget when the mouse enters the listview. At some point we need to GetData() from that to extract the file names. If we want to drop items halfway down, I assume we're meant to LVM_HITTEST to determine which LV item we're currently hovering over. Then it'll be a LVM_SETINSERTMARK to display the insertion mark. Edit: In hindsight, I probably should've removed the WS_EX_ACCEPTFILES style for this demo! (and our WM_DROPFILES handler by association). Edit 2: snipped attachment to claw back space! Updated example further down... Edited 15 hours ago by MattyD WildByDesign 1
WildByDesign Posted yesterday at 01:34 PM Posted yesterday at 01:34 PM 1 hour ago, MattyD said: Ok, work in progress - here are the bones of a DropTarget Object. (for receiving files) This is really great progress, Matty. So much of the older code for drag and drop here in the forum was made for x86 at the time. Your work (and @jugador) is going to benefit many users by giving proper methods for modern Windows OS. This help thread has ended up being very beneficial. 1 hour ago, MattyD said: Edit: In hindsight, I probably should've removed the WS_EX_ACCEPTFILES style for this demo! (and our WM_DROPFILES handler by association). Good point. We wont be needing those older techniques anymore. This is really quite exciting. Regarding IDropTargetHelper interface and drag images: Quote The IDragSourceHelper and IDropTargetHelper interfaces are exposed by the drag-image manager object to allow the IDropTarget interface to use custom drag images. To use either of these interfaces, you must create an in-process server drag-image manager object by calling CoCreateInstance with a class identifier (CLSID) of CLSID_DragDropHelper. Get interface pointers using standard Component Object Model (COM) procedures. Hopefully this isn't something that would be limited by AutoIt lack of threads and such. Fingers crossed. 🤞 I like how we can control on our end whether to allow move, copy, etc.
jugador Posted yesterday at 07:41 PM Posted yesterday at 07:41 PM Updated version of GetDataObjectOfFile_C. Added IDragSourceHelper which solves the icon visual glitch. expandcollapse popup#include <WinAPIShellEx.au3> 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 $CLSID_DragDropHelper = "{4657278A-411B-11D2-839A-00C04FD918D0}" Local $IID_IDragSourceHelper = "{DE5BF786-477A-11D2-839D-00C04FD918D0}" Local $tag_IDragSourceHelper = "InitializeFromBitmap hresult(ptr;ptr);InitializeFromWindow hresult(hwnd;ptr;ptr);" Local $oDragHelper = ObjCreateInterface($CLSID_DragDropHelper, $IID_IDragSourceHelper, $tag_IDragSourceHelper) If Not IsObj($oDragHelper) Then _Release($pIDataObject) Return 0 Endif 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) Local $hBitmap = __GetFileIcon_as_Bitmap($sPath[0]) Local $tSHDRAGIMAGE = DllStructCreate("int sizeX; int sizeY; int ptX; int ptY; ptr hbmp; uint crColorKey") __CreateDragImageStruct($tSHDRAGIMAGE, $hBitmap, 96, 96) $oDragHelper.InitializeFromBitmap(DllStructGetPtr($tSHDRAGIMAGE), $pIDataObject) _AddRef($pIDataObject) Return $pIDataObject EndFunc Func __CreateDragImageStruct(Byref $tSHDRAGIMAGE, $hBitmap, $iWidth, $iHeight) DllStructSetData($tSHDRAGIMAGE, "sizeX", $iWidth) DllStructSetData($tSHDRAGIMAGE, "sizeY", $iHeight) DllStructSetData($tSHDRAGIMAGE, "ptX", 0) DllStructSetData($tSHDRAGIMAGE, "ptY", 0) DllStructSetData($tSHDRAGIMAGE, "hbmp", $hBitmap) DllStructSetData($tSHDRAGIMAGE, "crColorKey", 0x00FF00FF) EndFunc Func __GetFileIcon_as_Bitmap($sPath) Local $tSHFILEINFO = DllStructCreate($tagSHFILEINFO) _WinAPI_ShellGetFileInfo($sPath, BitOR($SHGFI_ICON, $SHGFI_USEFILEATTRIBUTES, $SHGFI_LARGEICON), 0, $tSHFILEINFO) Local $hIcon = DllStructGetData($tSHFILEINFO, 'hIcon') Local $hBitmap = __IconToBitmap($hIcon, 96) _WinAPI_DestroyIcon($hIcon) Return $hBitmap EndFunc Func __IconToBitmap($hIcon, $iSize = 32) Local $hDestDC = _WinAPI_GetDC(0) Local $hMemDC = _WinAPI_CreateCompatibleDC($hDestDC) Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDestDC, $iSize, $iSize) Local $hOldObj = _WinAPI_SelectObject($hMemDC, $hBitmap) _WinAPI_DrawIconEx($hMemDC, 0, 0, $hIcon, $iSize, $iSize) _WinAPI_SelectObject($hMemDC, $hOldObj) _WinAPI_DeleteDC($hMemDC) _WinAPI_ReleaseDC(0, $hDestDC) Return $hBitmap 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 WildByDesign and MattyD 2
WildByDesign Posted 22 hours ago Posted 22 hours ago 2 hours ago, jugador said: Updated version of GetDataObjectOfFile_C. Added IDragSourceHelper which solves the icon visual glitch. This works perfectly. Thank you.
MattyD Posted 17 hours ago Posted 17 hours ago (edited) Thanks @jugador , I'll add in the helper at some point. This is today's effort: A couple of fixups, and Drag/Drop between TreeViews and ListViews - featuring insertion marks. Edit: re-uploaded example - items weren't always being inserted at insertion mark Edit 2: afternoon update! - Included helper for the DropTarget, so we now get a fancy image going from Explorer -> Control. Edit 3: Using SHDoDragDrop instead of DoDragDrop seems to automatically create our drag images when going from control -> Wherever... and as mentioned earlier, it allows us to knock off our custom DropSource object too (unless @WildByDesign you're still planning to hook its methods?). I'll probably circle back to the multi-file thing next. I realise we have a method, but Ideally I'd like to generate a IDataObject for a list of files rather than manually setting up the hdrop. So I'll see how far I get with that. _Demo2.zip Edited 12 hours ago by MattyD argumentum 1
WildByDesign Posted 9 hours ago Posted 9 hours ago 7 hours ago, MattyD said: Edit 2: afternoon update! - Included helper for the DropTarget, so we now get a fancy image going from Explorer -> Control. This is really exciting. It works very well and the OS is showing the nice drag image (high DPI too) plus the tooltip that shows Copy, Move, etc. which is helpful. 7 hours ago, MattyD said: Edit 3: Using SHDoDragDrop instead of DoDragDrop seems to automatically create our drag images when going from control -> Wherever... and as mentioned earlier, it allows us to knock off our custom DropSource object too (unless @WildByDesign you're still planning to hook its methods?). I'm glad that you switched to SHDoDragDrop because it has worked much better in all of my testing regarding drag images. I was hooking into the GiveFeedback, DragEnterTarget and DragLeaveTarget of the DropSource with good success. But I am pretty confident that we can get rid of it and let the OS manage that part. Since you are able to use the new DropTarget for DoInsertMark successfully, I should actually be able to hook into that instead. In my use case, I would be doing drag and drop into directories. So instead of using InsertMark, I would be getting the TreeView or ListView item under the cursor and, if it is a directory, ensure that drophilite is applied to whichever directory is currently hot during drag. And only for our window handle. Any other target windows will have their own methods, of course. By the way, in the SHDoDragDrop function, you could do the following: Local $aCall = DllCall("Shell32.dll", "long", "SHDoDragDrop", "hwnd", Null, "ptr", $pDataObj, "ptr", Null, "dword", $iOKEffects, "ptr*", 0) Since our window is not providing the drag image (the OS is), we can just put Null for hWnd since it is not really getting used anyway. And same for putting Null for $pDropSource since the OS is doing it. It seems to work either way just the same. Issue: The one and only issue that I noticed that is a bit problematic is that the very first drag image always (almost always) seems to be blank. And every drag after that shows the correct drag image. It seems to be only when the drag originates from our window/control. MattyD 1
MattyD Posted 9 hours ago Posted 9 hours ago 4 minutes ago, WildByDesign said: first drag image always (almost always) seems to be blank yeah I've noticed this is a bit sporadic - I wouldn't say the "first one" specifically is more troublesome for me though. I found items from explorer randomly lose their image too (even when our app isn't involved), so not sure how fixable this will be. For explorer, I think I can mainly reproduce it by dragging items fast outside their window - but its a bit hit and miss.
WildByDesign Posted 8 hours ago Posted 8 hours ago (edited) I think I have an idea. Some Explorer++ source: link // DropTargetInternal // Note that, as described above, this window is registered as a drop target only so that drag // images are shown consistently. Dropping items isn't supported at all. DWORD MainWindow::DragEnter(IDataObject *dataObject, DWORD keyState, POINT pt, DWORD effect) { UNREFERENCED_PARAMETER(dataObject); UNREFERENCED_PARAMETER(keyState); UNREFERENCED_PARAMETER(pt); UNREFERENCED_PARAMETER(effect); return DROPEFFECT_NONE; They register the whole GUI likely to prevent this issue. And it seems they return DROPEFFECT_NONE for the main GUI handle, and likely return different effects for the ListView and TreeView. EDIT: Added code comment from their main window: // 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. Edited 8 hours ago by WildByDesign MattyD 1
MattyD Posted 8 hours ago Posted 8 hours ago (edited) worth a shot Edit - Wait, are they just eluding to the cursor turning into a "no" icon, rather than the pretty pic when outside a drop target (within the gui)? If its just that I probably wont bother! Edited 8 hours ago by MattyD WildByDesign 1
WildByDesign Posted 8 hours ago Posted 8 hours ago 10 minutes ago, MattyD said: Edit - Wait, are they just eluding to the cursor turning into a "no" icon, rather than the pretty pic when outside a drop target (within the gui)? If its just that I probably wont bother! // 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. It sounds like they are specifically referring to the drag images not showing consistently.
MattyD Posted 8 hours ago Posted 8 hours ago I read "consistently shown" as their image never disappears because they're always over a droptarget. It just gets a little "no symbol" added off the bottom right of the cursor. In ours, the image disappears entirely and we replace the cursor. I'm betting this probably won't help us if the image is broken at the initial drag - but I guess we should look at it anyway from an aethetics point of view...
WildByDesign Posted 8 hours ago Posted 8 hours ago I just found the initial commit related to it. It took me a bit because the code searches weren't coming up with it. It describes better what was happening and how it was fixed. His comments are always super thorough. Commit: link Quote Register main window as drop target The main window doesn't actually accept any drop items, but this has the advantage that a drag item is show consistently when a drag is taking place. Previously, if a drag item had an image, the image wouldn't always be shown. If the item was dragged over a window that wasn't registered as a drop target, the image would revert to just a cursor. This meant that, as you dragged an item over different child windows: - The image would be shown and the cursor would indicate that a drop was possible. - The image would be shown and the cursor would indicate that a drop wasn't possible. - The image wouldn't be shown and the cursor would indicate that a drop wasn't possible. By registering the main window as a drop target (and calling the appropriate IDropTargetHelper methods), the image is always shown, meaning that there's more consistency in the case where you can't actually perform a drop. MattyD 1
WildByDesign Posted 8 hours ago Posted 8 hours ago 6 minutes ago, MattyD said: I'm betting this probably won't help us if the image is broken at the initial drag - but I guess we should look at it anyway from an aethetics point of view... I am honestly not too sure. I mean, it only seems to happen to me on the first drag. Every day after that works consistently for me. So I'm not certain why that is.
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now