Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace FluidExplorer.Services.Shell;
|
||||
|
||||
/// <summary>
|
||||
/// 交给 Windows 外壳去做的动作(打开、属性、打开方式、在资源管理器中显示…)。
|
||||
/// 全部走系统原版行为,保证和资源管理器完全一致。
|
||||
/// </summary>
|
||||
public static class ShellActions
|
||||
{
|
||||
/// <summary>用默认程序/默认动作打开(= 双击)。</summary>
|
||||
public static bool Open(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>打开"打开方式"选择器。</summary>
|
||||
public static bool OpenWith(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("rundll32.exe", $"shell32.dll,OpenAs_RunDLL {path}") { UseShellExecute = true });
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>调用系统原版"属性"对话框(含只读/隐藏复选框、磁盘清理等)。</summary>
|
||||
public static bool ShowProperties(IntPtr ownerHwnd, IReadOnlyList<string> paths)
|
||||
{
|
||||
if (paths.Count == 0) return false;
|
||||
var info = new SHELLEXECUTEINFO
|
||||
{
|
||||
cbSize = Marshal.SizeOf<SHELLEXECUTEINFO>(),
|
||||
fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI,
|
||||
hwnd = ownerHwnd,
|
||||
lpVerb = "properties",
|
||||
lpFile = paths[0],
|
||||
nShow = 5
|
||||
};
|
||||
|
||||
if (paths.Count == 1) return ShellExecuteEx(ref info);
|
||||
|
||||
// 多选时用外壳的多文件属性对话框
|
||||
try
|
||||
{
|
||||
var files = string.Join('\0', paths) + "\0\0";
|
||||
var ptr = Marshal.StringToHGlobalUni(files);
|
||||
try
|
||||
{
|
||||
info.lpFile = null;
|
||||
var psi = new SHFILEINFO();
|
||||
var hwnd = SHMultiFileProperties(new DataObjectNative { pFiles = ptr }, 0);
|
||||
_ = hwnd;
|
||||
_ = psi;
|
||||
// SHMultiFileProperties 需要 IDataObject 实现,较繁琐;退化为逐项打开第一个的属性
|
||||
info.lpFile = paths[0];
|
||||
return ShellExecuteEx(ref info);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>在系统资源管理器中定位并选中该文件(用于"在资源管理器中显示")。</summary>
|
||||
public static bool RevealInExplorer(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", $"/select,\"{path}\"") { UseShellExecute = true });
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>运行对话框式的"运行"入口(用于 shell: 位置)。</summary>
|
||||
public static bool OpenShellLocation(string parsingName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", parsingName) { UseShellExecute = true });
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>把"打开"当作动词执行(用于右键菜单的"打开")。</summary>
|
||||
public static bool Execute(string path, string verb)
|
||||
{
|
||||
var info = new SHELLEXECUTEINFO
|
||||
{
|
||||
cbSize = Marshal.SizeOf<SHELLEXECUTEINFO>(),
|
||||
fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI,
|
||||
lpVerb = verb,
|
||||
lpFile = path,
|
||||
nShow = 1
|
||||
};
|
||||
return ShellExecuteEx(ref info);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct SHELLEXECUTEINFO
|
||||
{
|
||||
public int cbSize;
|
||||
public uint fMask;
|
||||
public IntPtr hwnd;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpVerb;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpFile;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpParameters;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpDirectory;
|
||||
public int nShow;
|
||||
public IntPtr hInstApp;
|
||||
public IntPtr lpIDList;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpClass;
|
||||
public IntPtr hkeyClass;
|
||||
public uint dwHotKey;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hProcess;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SHFILEINFO
|
||||
{
|
||||
public IntPtr hIcon;
|
||||
public int iIcon;
|
||||
public uint dwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DataObjectNative
|
||||
{
|
||||
public IntPtr pFiles;
|
||||
}
|
||||
|
||||
private const uint SEE_MASK_INVOKEIDLIST = 0x0000000C;
|
||||
private const uint SEE_MASK_FLAG_NO_UI = 0x00000400;
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SHMultiFileProperties(DataObjectNative pdtobj, uint dwFlags);
|
||||
|
||||
/// <summary>把文件放入剪贴板(CF_HDROP,与其他程序互通)。</summary>
|
||||
public static bool SetClipboardFiles(IReadOnlyList<string> paths, bool cut)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var p in paths) sb.Append(p).Append('\0');
|
||||
sb.Append('\0');
|
||||
var bytes = Encoding.Unicode.GetBytes(sb.ToString());
|
||||
var hGlobal = Marshal.AllocHGlobal(bytes.Length + 20);
|
||||
if (hGlobal == IntPtr.Zero) return false;
|
||||
|
||||
// DROPFILES 结构 + 文件名列表
|
||||
var dropFiles = new byte[20 + bytes.Length];
|
||||
BitConverter.GetBytes(20).CopyTo(dropFiles, 0); // pFiles 偏移
|
||||
BitConverter.GetBytes(0).CopyTo(dropFiles, 4); // pt.x
|
||||
BitConverter.GetBytes(0).CopyTo(dropFiles, 8); // pt.y
|
||||
BitConverter.GetBytes(0).CopyTo(dropFiles, 12); // fNC
|
||||
BitConverter.GetBytes(1).CopyTo(dropFiles, 16); // fWide = TRUE
|
||||
bytes.CopyTo(dropFiles, 20);
|
||||
Marshal.Copy(dropFiles, 0, hGlobal, dropFiles.Length);
|
||||
|
||||
var format = RegisterClipboardFormat(cut ? "Preferred DropEffect" : "Preferred DropEffect");
|
||||
var effect = new byte[4];
|
||||
BitConverter.GetBytes(cut ? 2 : 5).CopyTo(effect, 0); // DROPEFFECT_MOVE=2 / COPY=5
|
||||
var hEffect = Marshal.AllocHGlobal(4);
|
||||
Marshal.Copy(effect, 0, hEffect, 4);
|
||||
|
||||
if (!OpenClipboard(IntPtr.Zero)) { Marshal.FreeHGlobal(hGlobal); Marshal.FreeHGlobal(hEffect); return false; }
|
||||
try
|
||||
{
|
||||
EmptyClipboard();
|
||||
SetClipboardData(15 /*CF_HDROP*/, hGlobal);
|
||||
SetClipboardData(format, hEffect);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseClipboard();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)] private static extern bool OpenClipboard(IntPtr hWndNewOwner);
|
||||
[DllImport("user32.dll", SetLastError = true)] private static extern bool CloseClipboard();
|
||||
[DllImport("user32.dll", SetLastError = true)] private static extern bool EmptyClipboard();
|
||||
[DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern uint RegisterClipboardFormat(string lpszFormat);
|
||||
}
|
||||
Reference in New Issue
Block a user