question

ScottJordan-8204 avatar image
0 Votes"
ScottJordan-8204 asked ScottJordan-8204 commented

Can I programmatically do drag-and-drop from my local file system to a 3rd party application?

Hello!

I have a VB .NET WPF that searches a local directory structure to locate a few hundred files per month. These files need to be filed (drag and dropped) onto a 3rd party application that stores the files. The third party application has its own directory structure, and so the correct directory has to be picked before each file is dragged-and-dropped.

I am able to control the 3rd party program with SENDMESSAGEs. I can log in, select the correct directory, and read all the values from the 3rd party controls.

To manually do the upload, the user drags the file to the 3rd party control (which happens to be a ListView) and then the file gets copied to the 3rd party's file system.

Can I somehow simulate the drag-and-drop function after selecting the correct location in the 3rd party application? I tried WM_DROPFILES, but that didn't appear to do anything. However, I tried the same WM_DROPFILES into Notepad and it didn't do anything, either.

I found the Control.DoDragDrop method to initiate the drag-and-drop, but could not find a message to send to the 3rd party application to start the "drop" part of the process.

Thanks for any help!

windows-apidotnet-visual-basic
· 10
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Do you really need to simulate the drag-and-drop, or do you only need to simulate the drop? You might need to do that using the Windows API and if so then it is complicated and technical; see Drag and Drop.


1 Vote 1 ·

You are exactly right! I just need so simulate the Drop. I saw that route to get the iDropTarget from a handle, but I haven't yet successfully converted it to VB. I think it is because it is returning a pointer to a structure. I suppose that I could just use the C# code as-is and figure out how to call it from VB. Thanks for the reply!

0 Votes 0 ·

In theory, it should work with IDropTarget interface, like at Reading a contract from the other side: Simulating a drop
But I did some tests and it works with some applications (Notepad, but 1 file), but not with others (like Explorer or own apps which support Drag/Drop from files)

(or you could copy files to the clipboard, then simulate a Paste in the control, if the target app handles it)

1 Vote 1 ·

The link to GetUIObjectOfFile didn't work, but it looked like it returned the window, as opposed to a control (dropping a file on the 3rd party application is only allowed on the one control). The outline of the steps will definitely help. Thank you!

0 Votes 0 ·

@ScottJordan-8204 Here's the link to the code for GetUIObjectOfFile - How to host an IContextMenu, part 1 – Initial foray


0 Votes 0 ·

In fact, after many tests, it works with DoDragDrop, but you must set the app in foreground and the mouse exactly on the control (with mouse_event for example) (otherwise files are dropped on the foreground window...)
I tested first in C++ but I could convert it into VB if you cannot find some samples on Google...




0 Votes 0 ·
Show more comments

1 Answer

Castorix31 avatar image
1 Vote"
Castorix31 answered ScottJordan-8204 commented

From comments, a translation of C++ code which works with native DoDragDrop API (Control.DoDragDrop does not seem to work well)
To be adapted/improved =>
(I had to remove an 'e' from Sleep or I could not post ...)

 'Does not seem to work fine....
 'Dim filesList As System.Collections.Specialized.StringCollection = New System.Collections.Specialized.StringCollection()
 'filesList.Add("E:\test.txt")
 'filesList.Add("E:\hulk.jpg")
 'Dim dataObject As DataObject = New DataObject()
 'dataObject.SetData(DataFormats.FileDrop, filesList)
 'dataObject.SetFileDropList(filesList)
    
 OleInitialize(IntPtr.Zero)
    
 ' Test with 2 files, in a same directory for pidlParent
 Dim pItemIDL1 As IntPtr = ILCreateFromPath("E:\test.txt")
 Dim pItemIDL2 As IntPtr = ILCreateFromPath("E:\hulk.jpg")
 'Dim pItemIDL2 As IntPtr = ILCreateFromPath("E:\test2.txt")
 Dim arrayPidl = New IntPtr(255) {}
 Dim pidlParent As IntPtr = IntPtr.Zero, pidlItem As IntPtr = IntPtr.Zero
    
 Dim nPidlNumber As Integer = 0
 pidlItem = ILFindLastID(pItemIDL1)
 arrayPidl(0) = ILClone(pidlItem)
 nPidlNumber += 1
 ILRemoveLastID(pItemIDL1)
 pidlParent = ILClone(pItemIDL1)
 ILFree(pItemIDL1)
    
 pidlItem = ILFindLastID(pItemIDL2)
 arrayPidl(1) = ILClone(pidlItem)
 nPidlNumber += 1
 ILRemoveLastID(pItemIDL2)
 'pidlParent = ILClone(pItemIDL2)
 ILFree(pItemIDL2)
    
 Dim pDataObject As System.Runtime.InteropServices.ComTypes.IDataObject = Nothing
 Dim hr As HRESULT = SHCreateFileDataObject(pidlParent, nPidlNumber, arrayPidl, Nothing, pDataObject)
 If (hr = HRESULT.S_OK) Then
     ' Test with an IDropTarget app with window title = "Drop Target"
     Dim hWndDest As IntPtr = FindWindow(Nothing, "Drop Target")
     ' Notepad only works with 1 file
     'Dim hWndDest As IntPtr = FindWindow("Notepad", Nothing)
     If (hWndDest <> IntPtr.Zero) Then
         SwitchToThisWindow(hWndDest, True)
         System.Threading.Thread.Slep(500)
         Dim rcDest As New RECT()
         GetWindowRect(hWndDest, rcDest)
    
         ' Move mouse cursor in the middle of the window
         Dim nX As Integer = (rcDest.left + (rcDest.right - rcDest.left) / 2) * 65535 / GetSystemMetrics(SM_CXSCREEN)
         Dim nY As Integer = (rcDest.top + (rcDest.bottom - rcDest.top) / 2) * 65535 / GetSystemMetrics(SM_CYSCREEN)
    
         Dim mi() As INPUT = New INPUT(2) {}
         mi(0).type = INPUT_MOUSE
         mi(0).union.mi.dx = nX
         mi(0).union.mi.dy = nY
         mi(0).union.mi.dwFlags = MOUSEEVENTF_ABSOLUTE Or MOUSEEVENTF_MOVE
         'mi(1).union.mi.dwFlags = MOUSEEVENTF_LEFTDOWN
         mi(1).union.mi.dwFlags = MOUSEEVENTF_LEFTUP
         'mi(2).union.mi.dwFlags = MOUSEEVENTF_LEFTUP
         'SendInput(3, mi, Marshal.SizeOf(mi(0)))
         SendInput(2, mi, Marshal.SizeOf(mi(0)))
    
         Dim pDropSource As CDropSource = New CDropSource()
         Dim dwEffectDragDrop As UInteger = DragDropEffects.None
         hr = OleDoDragDrop(pDataObject, pDropSource, DragDropEffects.Link Or DragDropEffects.Copy, dwEffectDragDrop)
    
         Marshal.ReleaseComObject(pDataObject)
        'Dim dde As System.Windows.Forms.DragDropEffects = Me.DoDragDrop(dataObject, DragDropEffects.Copy Or DragDropEffects.Link)
       End If
 End If


with declarations :

 Public Enum HRESULT As Integer
     S_OK = 0
     S_FALSE = 1
     E_NOINTERFACE = &H80004002
     E_NOTIMPL = &H80004001
     E_FAIL = &H80004005
     E_UNEXPECTED = &H8000FFFF
     E_OUTOFMEMORY = &H8007000E
     DRAGDROP_S_CANCEL = &H40101
     DRAGDROP_S_DROP = &H40100
     DRAGDROP_S_USEDEFAULTCURSORS = &H40102
 End Enum
    
 <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
 Public Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
 End Function
    
 <DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
 Public Shared Function SwitchToThisWindow(hWnd As IntPtr, fAltTab As Boolean) As Boolean
 End Function
    
 <DllImport("User32.dll", SetLastError:=True)>
 Public Shared Function GetSystemMetrics(ByVal nIndex As Integer) As Integer
 End Function
    
 Public Const SM_CXSCREEN As Integer = 0
 Public Const SM_CYSCREEN As Integer = 1
 Public Const SM_XVIRTUALSCREEN As Integer = 76
 Public Const SM_YVIRTUALSCREEN As Integer = 77
 Public Const SM_CXVIRTUALSCREEN As Integer = 78
 Public Const SM_CYVIRTUALSCREEN As Integer = 79
    
 Public Const MOUSEEVENTF_MOVE As Integer = &H1
 Public Const MOUSEEVENTF_LEFTDOWN As Integer = &H2
 Public Const MOUSEEVENTF_LEFTUP As Integer = &H4
 Public Const MOUSEEVENTF_ABSOLUTE As Integer = &H8000
    
 <StructLayout(LayoutKind.Sequential)>
 Public Structure MOUSEINPUT
     Public dx As Integer
     Public dy As Integer
     Public mouseData As Integer
     Public dwFlags As Integer
     Public time As Integer
     Public dwExtraInfo As IntPtr
 End Structure
    
 <StructLayout(LayoutKind.Sequential)>
 Public Structure KEYBDINPUT
     Public wVk As Short
     Public wScan As Short
     Public dwFlags As Integer
     Public time As Integer
     Public dwExtraInfo As IntPtr
 End Structure
    
 <StructLayout(LayoutKind.Sequential)>
 Public Structure INPUT
     Public type As Integer
     Public union As INPUTUNION
 End Structure
    
 <StructLayout(LayoutKind.Explicit)>
 Public Structure INPUTUNION
     <FieldOffset(0)>
     Public mi As MOUSEINPUT
     <FieldOffset(0)>
     Public ki As KEYBDINPUT
     '<FieldOffset(0)>
     'Public hi As HARDWAREINPUT
 End Structure
    
 Public Const INPUT_MOUSE As Integer = 0
 Public Const INPUT_KEYBOARD As Integer = 1
    
 <DllImport("User32.dll", SetLastError:=True)>
 Public Shared Function SendInput(ByVal nInputs As Integer, <MarshalAs(UnmanagedType.LPArray)> ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
 End Function
    
 <StructLayout(LayoutKind.Sequential)>
 Public Structure RECT
     Public left As Integer
     Public top As Integer
     Public right As Integer
     Public bottom As Integer
     Public Sub New(
               left As Integer,
               top As Integer,
               right As Integer,
               bottom As Integer)
         Me.left = left
         Me.top = top
         Me.right = right
         Me.bottom = bottom
     End Sub
 End Structure
    
 <DllImport("user32", SetLastError:=True)>
 Public Shared Function GetWindowRect(hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Function ILCreateFromPath(pszPath As String) As IntPtr
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, EntryPoint:="#740", CharSet:=CharSet.Unicode)>
 Public Shared Function SHCreateFileDataObject(ByVal pidlFolder As IntPtr, ByVal cidl As UInteger, ByVal apidl As IntPtr(), ByVal pdtInner As System.Runtime.InteropServices.ComTypes.IDataObject, <Out> ByRef ppdtobj As System.Runtime.InteropServices.ComTypes.IDataObject) As HRESULT
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Function ILFindLastID(pidl As IntPtr) As IntPtr
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Function ILClone(ByVal pidl As IntPtr) As IntPtr
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Function ILRemoveLastID(ByVal pidl As IntPtr) As Boolean
 End Function
    
 <DllImport("Shell32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Sub ILFree(pidl As IntPtr)
 End Sub
    
 <DllImport("Ole32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
 Public Shared Function OleInitialize(ByVal pvReserved As IntPtr) As HRESULT
 End Function
    
 <DllImport("Ole32.dll", EntryPoint:="DoDragDrop", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True)>
 Public Shared Function OleDoDragDrop(<MarshalAs(UnmanagedType.Interface)> pDataObj As System.Runtime.InteropServices.ComTypes.IDataObject, <MarshalAs(UnmanagedType.Interface)> pDropSource As IDropSource, dwOKEffects As UInteger, ByRef pdwEffect As UInteger) As HRESULT
 End Function
    
 <ComImport, Guid("00000121-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
 Public Interface IDropSource
     <PreserveSig>
     Function QueryContinueDrag(<MarshalAs(UnmanagedType.Bool)> fEscapePressed As Boolean, grfKeyState As UInteger) As HRESULT
     <PreserveSig>
     Function GiveFeedback(dwEffect As UInteger) As HRESULT
 End Interface
    
 Public Class CDropSource
     Implements IDropSource
     Private Function QueryContinueDrag(fEscapePressed As Boolean, grfKeyState As UInteger) As HRESULT Implements IDropSource.QueryContinueDrag
         If fEscapePressed Then
             Return HRESULT.DRAGDROP_S_CANCEL
         End If
         If Not (grfKeyState And MK_LBUTTON) Then
             Console.Beep(9000, 10) 'for test
             Return HRESULT.DRAGDROP_S_DROP
         End If
         Return HRESULT.S_OK
     End Function
    
     Public Function GiveFeedback(dwEffect As UInteger) As HRESULT Implements IDropSource.GiveFeedback
         Return HRESULT.DRAGDROP_S_USEDEFAULTCURSORS
     End Function
 End Class
    
 Public Const MK_LBUTTON = &H1
 Public Const MK_RBUTTON = &H2
 Public Const MK_SHIFT = &H4
 Public Const MK_CONTROL = &H8
 Public Const MK_MBUTTON = &H10
 Public Const MK_XBUTTON1 = &H20
 Public Const MK_XBUTTON2 = &H40





· 14
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

This is perfect! I wish I could do more than just say "thank you". Thank you!!!

0 Votes 0 ·

Thanks a lot, @Castorix31! You are a rock star! I sure appreciate your help!

0 Votes 0 ·

Hi!

I tested this in a Windows Form application, on the UI thread and it works great.

When I try it in a WPF in a thread, I receive errors. For example, when I try to OleInitialize, I get -2147417850 for the result which appears. to be a type of COM initialization failure. When I ignore that and try the OleDoDragDrop, I get a result of -2147221008.

I'm willing to convert this to a Windows Form application if that is what it takes. If I do that, do you know if I must keep these calls in the main UI thread?

Thanks a lot!

0 Votes 0 ·

When I try the call of the main UI thread of the WPF, the results appear to be okay (1 for the OleInitialize and 262400 for OleDoDragDrop), but the test file does not get "dropped". So, right now, it does not appear to work in WPF in either the main UI thread or a background thread.

Running it in a background thread of a Windows Form does not appear to work, either.

I was planning to run the OleDoDragDrop in a background thread because it can take several seconds to drop the file (and upload it to the 3rd party where the file gets stored), and was hoping to avoid the UI freezing.

Thanks again!

0 Votes 0 ·

The WPF UI is very complicated. You can probably use WPF but it requires more analysis than Windows Forms.

0 Votes 0 ·

Weird, I just tested in a WPF app (VB, .NET Framework 4.7.2, Windows 10 1909), and it works
(I just copied the code in a button click)

Test with Notepad :

128947-wpf-dodragdrop.gif


0 Votes 0 ·
wpf-dodragdrop.gif (59.6 KiB)
Show more comments