初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user