It's an issue with UAC because your process is elevated with administrator rights while Windows Explorer is running under the context of the current user which is not elevated. It's a security design of Windows Vista, 7 and 8. You can read more about it
here.
There are workarounds for it, and I've tested it on Windows 7 and Windows 8. The thing with this workaround is that you'll have to rely on the legacy drag drop (using the message from WndProc) instead of the new COM interfaces used in AllowDrop which have the DragEnter and DragLeave events which the legacy doesn't have. Note that using it might be considered unsafe due to the WM_COPYDATA and WM_COPYGLOBALDATA messages used, but I guess it's not a big problem (it's not like someone is going to try to exploit an injector).
This is the class that I wrote to achieve what I wanted (note that it is converted from C#). Add a new class named ElevatedDragDropManager.vb into your project and replace everything with the following:
Code:
Imports System.Text
Imports System.Runtime.InteropServices
Namespace Utilities
Public Class ElevatedDragDropManager
Implements IMessageFilter
#Region "P/Invoke"
<DllImport("user32.dll")> _
Private Shared Function ChangeWindowMessageFilterEx(hWnd As IntPtr, msg As UInteger, action As ChangeWindowMessageFilterExAction, ByRef changeInfo As CHANGEFILTERSTRUCT) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("user32.dll")> _
Private Shared Function ChangeWindowMessageFilter(msg As UInteger, flags As ChangeWindowMessageFilterFlags) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("shell32.dll")> _
Private Shared Sub DragAcceptFiles(hwnd As IntPtr, fAccept As Boolean)
End Sub
<DllImport("shell32.dll")> _
Private Shared Function DragQueryFile(hDrop As IntPtr, iFile As UInteger, <Out()> lpszFile As StringBuilder, cch As UInteger) As UInteger
End Function
<DllImport("shell32.dll")> _
Private Shared Function DragQueryPoint(hDrop As IntPtr, ByRef lppt As POINT) As Boolean
End Function
<DllImport("shell32.dll")> _
Private Shared Sub DragFinish(hDrop As IntPtr)
End Sub
<StructLayout(LayoutKind.Sequential)> _
Private Structure POINT
Public X As Integer
Public Y As Integer
Public Sub New(newX As Integer, newY As Integer)
X = newX
Y = newY
End Sub
Public Shared Widening Operator CType(p As POINT) As Drawing.Point
Return New Drawing.Point(p.X, p.Y)
End Operator
Public Shared Widening Operator CType(p As Drawing.Point) As POINT
Return New POINT(p.X, p.Y)
End Operator
End Structure
Private Enum MessageFilterInfo As UInteger
None
AlreadyAllowed
AlreadyDisAllowed
AllowedHigher
End Enum
Private Enum ChangeWindowMessageFilterExAction As UInteger
Reset
Allow
Disallow
End Enum
Private Enum ChangeWindowMessageFilterFlags As UInteger
Add = 1
Remove = 2
End Enum
<StructLayout(LayoutKind.Sequential)> _
Private Structure CHANGEFILTERSTRUCT
Public cbSize As UInteger
Public ExtStatus As MessageFilterInfo
End Structure
#End Region
Public Shared Instance As New ElevatedDragDropManager()
Public Event ElevatedDragDrop As EventHandler(Of ElevatedDragDropArgs)
Private Const WM_DROPFILES As UInteger = &H233
Private Const WM_COPYDATA As UInteger = &H4A
Private Const WM_COPYGLOBALDATA As UInteger = &H49
Private ReadOnly IsVistaOrHigher As Boolean = Environment.OSVersion.Version.Major >= 6
Private ReadOnly Is7OrHigher As Boolean = (Environment.OSVersion.Version.Major = 6 AndAlso Environment.OSVersion.Version.Minor >= 1) OrElse Environment.OSVersion.Version.Major > 6
Protected Sub New()
Application.AddMessageFilter(Me)
End Sub
Public Sub EnableDragDrop(hWnd As IntPtr)
If Is7OrHigher Then
Dim changeStruct As New CHANGEFILTERSTRUCT()
changeStruct.cbSize = CUInt(Marshal.SizeOf(GetType(CHANGEFILTERSTRUCT)))
ChangeWindowMessageFilterEx(hWnd, WM_DROPFILES, ChangeWindowMessageFilterExAction.Allow, changeStruct)
ChangeWindowMessageFilterEx(hWnd, WM_COPYDATA, ChangeWindowMessageFilterExAction.Allow, changeStruct)
ChangeWindowMessageFilterEx(hWnd, WM_COPYGLOBALDATA, ChangeWindowMessageFilterExAction.Allow, changeStruct)
ElseIf IsVistaOrHigher Then
ChangeWindowMessageFilter(WM_DROPFILES, ChangeWindowMessageFilterFlags.Add)
ChangeWindowMessageFilter(WM_COPYDATA, ChangeWindowMessageFilterFlags.Add)
ChangeWindowMessageFilter(WM_COPYGLOBALDATA, ChangeWindowMessageFilterFlags.Add)
End If
DragAcceptFiles(hWnd, True)
End Sub
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
If m.Msg = WM_DROPFILES Then
HandleDragDropMessage(m)
Return True
End If
Return False
End Function
Private Sub HandleDragDropMessage(m As Message)
Dim sb = New StringBuilder(260)
Dim numFiles As UInteger = DragQueryFile(m.WParam, &HFFFFFFFFUI, sb, 0)
Dim list = New List(Of String)()
For i As UInteger = 0 To numFiles - 1
If DragQueryFile(m.WParam, i, sb, CUInt(sb.Capacity) * 2) > 0 Then
list.Add(sb.ToString())
End If
Next
Dim p As POINT
DragQueryPoint(m.WParam, p)
DragFinish(m.WParam)
Dim args = New ElevatedDragDropArgs()
args.HWnd = m.HWnd
args.Files = list
args.X = p.X
args.Y = p.Y
RaiseEvent ElevatedDragDrop(Me, args)
End Sub
End Class
Public Class ElevatedDragDropArgs
Inherits EventArgs
Public Property HWnd() As IntPtr
Get
Return m_HWnd
End Get
Set(value As IntPtr)
m_HWnd = value
End Set
End Property
Private m_HWnd As IntPtr
Public Property Files() As List(Of String)
Get
Return m_Files
End Get
Set(value As List(Of String))
m_Files = value
End Set
End Property
Private m_Files As List(Of String)
Public Property X() As Integer
Get
Return m_X
End Get
Set(value As Integer)
m_X = value
End Set
End Property
Private m_X As Integer
Public Property Y() As Integer
Get
Return m_Y
End Get
Set(value As Integer)
m_Y = value
End Set
End Property
Private m_Y As Integer
Public Sub New()
Files = New List(Of String)()
End Sub
End Class
End Namespace
To use the code, follow this example. I used DataGridView1 as the elevated-drag-drop target. You are free to add more controls if neccesary. Note that you do not need to enable the AllowDrop property, it will work without it.
Code:
Private Sub Form1_Load(sender As System.Object, e As EventArgs) Handles MyBase.Load
ElevatedDragDropManager.Instance.EnableDragDrop(DataGridView1.Handle) ' Enable elevated drag drop on DataGridView1. Note that I used the Handle property
AddHandler ElevatedDragDropManager.Instance.ElevatedDragDrop, AddressOf Form1_ElevatedDragDrop
End Sub
Private Sub Form1_ElevatedDragDrop(sender As System.Object, e As ElevatedDragDropArgs)
If e.HWnd = DataGridView1.Handle Then ' Check where the drag drop came from
For Each file In e.Files
MsgBox(file) ' Show all the files that were drag-dropped
Next
End If
End Sub
If you use the code, please leave credits. If you have any questions, please let me know.