using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; namespace PCL.Core.Utils.OS; // ReSharper disable InconsistentNaming public partial class DragHelper { public event EventHandler? DragDrop; public string[]? DropFilePaths { get; private set; } public Point DropDragPoint { get; private set; } public HwndSource? HwndSource { get; set; } #region Public API public void AddHook() { if (HwndSource is null) throw new InvalidOperationException("HwndSource 未设置"); RemoveHook(); HwndSource.AddHook(WndProc); IntPtr hwnd = HwndSource.Handle; if (IsUserAnAdmin()) RevokeDragDrop(hwnd); DragAcceptFiles(hwnd, true); ChangeMessageFilter(hwnd); } public void RemoveHook() { if (HwndSource is null) return; HwndSource.RemoveHook(WndProc); DragAcceptFiles(HwndSource.Handle, false); } #endregion #region WndProc private IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (TryGetDropInfo(msg, wParam, out var files, out var pt)) { DropFilePaths = files; DropDragPoint = new Point(pt.X, pt.Y); DragDrop?.Invoke(this, EventArgs.Empty); handled = true; } return IntPtr.Zero; } #endregion #region Message filter (UAC) private static unsafe void ChangeMessageFilter(IntPtr hwnd) { var ver = Environment.OSVersion.Version; if (ver < new Version(6, 0)) return; var win7OrHigher = ver >= new Version(6, 1); var filter = new CHANGEFILTERSTRUCT { cbSize = (uint)sizeof(CHANGEFILTERSTRUCT) }; uint[] messages = [ WM_DROPFILES, WM_COPYGLOBALDATA, WM_COPYDATA ]; foreach (var msg in messages) { var ok = win7OrHigher ? ChangeWindowMessageFilterEx(hwnd, msg, MSGFLT_ALLOW, ref filter) : ChangeWindowMessageFilter(msg, MSGFLT_ADD); if (!ok) throw new Win32Exception(Marshal.GetLastWin32Error()); } } #endregion #region Drop parsing private static bool TryGetDropInfo( int msg, IntPtr hDrop, out string[]? filePaths, out DragPoint dropPoint) { filePaths = null; dropPoint = default; if (msg != WM_DROPFILES) return false; var count = DragQueryFile(hDrop, uint.MaxValue, IntPtr.Zero, 0); filePaths = new string[count]; const int maxPath = 32768, smallerMaxPath = 1024; Span gBuffer = stackalloc char[smallerMaxPath]; for (uint i = 0; i < count; i++) { var len = DragQueryFile(hDrop, i, IntPtr.Zero, 0) + 1; if (len > maxPath) len = maxPath; var buffer = len <= smallerMaxPath ? gBuffer[..(int)len] : new char[len]; _ = DragQueryFile(hDrop, i, buffer, len); filePaths[i] = new string(buffer[..(int)(len - 1)]); } DragFinish(hDrop); return true; } #endregion #region Win32 private const uint WM_COPYGLOBALDATA = 0x0049; private const uint WM_COPYDATA = 0x004A; private const uint WM_DROPFILES = 0x0233; private const uint MSGFLT_ALLOW = 1; private const uint MSGFLT_ADD = 1; [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool ChangeWindowMessageFilter( uint msg, uint flags); [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool ChangeWindowMessageFilterEx( IntPtr hwnd, uint msg, uint action, ref CHANGEFILTERSTRUCT filter); [LibraryImport("shell32.dll")] private static partial void DragAcceptFiles( IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool accept); [LibraryImport("shell32.dll", EntryPoint = "DragQueryFileW", StringMarshalling = StringMarshalling.Utf16)] private static partial uint DragQueryFile(IntPtr hDrop, uint iFile, Span lpszFile, uint cch); [LibraryImport("shell32.dll", EntryPoint = "DragQueryFileW")] private static partial uint DragQueryFile(IntPtr hDrop, uint iFile, IntPtr lpszFile, uint cch); [LibraryImport("shell32.dll")] private static partial void DragFinish(IntPtr hDrop); [LibraryImport("ole32.dll")] private static partial int RevokeDragDrop(IntPtr hwnd); [LibraryImport("shell32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool IsUserAnAdmin(); #endregion #region Structs [StructLayout(LayoutKind.Sequential)] private struct DragPoint { public int X; public int Y; } [StructLayout(LayoutKind.Sequential)] private struct CHANGEFILTERSTRUCT { public uint cbSize; public uint ExtStatus; } #endregion }