203 lines
5.1 KiB
C#
203 lines
5.1 KiB
C#
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<char> 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<char> 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
|
||
|
|
}
|