using AOT; using System; using System.Runtime.InteropServices; using System.Text; namespace Kirurobo { /// /// 提供打开原生文件对话框的静态方法 /// public class FilePanel { protected class LibUniWinC { [DllImport("LibUniWinC", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool OpenFilePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize); [DllImport("LibUniWinC", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool OpenSavePanel(in PanelSettings settings, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder buffer, UInt32 bufferSize); [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PanelSettings : IDisposable { public Int32 structSize; public Int32 flags; public IntPtr lpszTitle; public IntPtr lpszFilter; public IntPtr lpszInitialFile; public IntPtr lpszInitialDir; public IntPtr lpszDefaultExt; public PanelSettings(Settings settings) { this.structSize = 0; //this.structSize = 4 * 2 + Marshal.SizeOf() * 3; this.flags = (Int32)settings.flags; //this.lpTitleText = IntPtr.Zero; //this.lpFilterText = IntPtr.Zero; //this.lpDefaultPath = IntPtr.Zero; this.lpszTitle = Marshal.StringToHGlobalUni(settings.title); this.lpszFilter = Marshal.StringToHGlobalUni(Filter.Join(settings.filters)); this.lpszInitialFile = Marshal.StringToHGlobalUni(settings.initialFile); this.lpszInitialDir = Marshal.StringToHGlobalUni(settings.initialDirectory); //this.lpszDefaultExt = Marshal.StringToHGlobalUni(settings.defaultExtension); this.lpszDefaultExt = IntPtr.Zero; //this.structSize = Marshal.SizeOf(this); this.structSize = Marshal.SizeOf(this); } public void Dispose() { if (this.lpszTitle != IntPtr.Zero) { Marshal.FreeHGlobal(lpszTitle); this.lpszTitle = IntPtr.Zero; } if (this.lpszFilter!= IntPtr.Zero) { Marshal.FreeHGlobal(lpszFilter); this.lpszFilter= IntPtr.Zero; } if (this.lpszInitialFile!= IntPtr.Zero) { Marshal.FreeHGlobal(lpszInitialFile); this.lpszInitialFile= IntPtr.Zero; } if (this.lpszInitialDir != IntPtr.Zero) { Marshal.FreeHGlobal(lpszInitialDir); this.lpszInitialDir = IntPtr.Zero; } if (this.lpszDefaultExt != IntPtr.Zero) { Marshal.FreeHGlobal(lpszDefaultExt); this.lpszDefaultExt = IntPtr.Zero; } } } } /// /// 对话框的设置标志 /// [Flags] public enum Flag { None = 0, FileMustExist = 1, // 仅 Windows FolderMustExist = 2, // 仅 Windows AllowMultipleSelection = 4, CanCreateDirectories = 16, OverwritePrompt = 256, // macOS 上始终启用 CreatePrompt = 512, // macOS 上始终启用 ShowHiddenFiles = 4096, RetrieveLink = 8192, } /// /// 文件对话框的参数 /// public struct Settings { public string title; public Filter[] filters; public string initialDirectory; public string initialFile; public string defaultExtension; // 未实现 public Flag flags; } /// /// 文件过滤器 /// public class Filter { protected string title; protected string[] extensions; /// /// /// /// 过滤器标题(macOS 上暂不可用) /// 扩展名数组,如 ["png", "jpg", "txt"] public Filter(string title, params string[] extensions) { this.title = title; this.extensions = extensions; } public override string ToString() { return title + "\t" + String.Join("\t", extensions); } /// /// 返回由 Filter 数组转换后的字符串 /// /// /// public static string Join(Filter[] filters) { if (filters == null) return ""; string result = ""; bool isFirstItem = true; foreach (var filter in filters) { if (!isFirstItem) result += "\n"; result += filter.ToString(); isFirstItem = false; } return result; } } /// /// 用于传递文件或文件夹路径的 UTF-16 缓冲区字符数 /// 因为多个路径以换行符分隔,260 字符不够用。 /// private const int pathBufferSize = 2560; /// /// 打开文件选择对话框 /// /// /// public static void OpenFilePanel(Settings settings, Action action) { LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings); StringBuilder sb = new StringBuilder(pathBufferSize); if (LibUniWinC.OpenFilePanel(in ps, sb, (uint)sb.Capacity)) { string[] files = UniWinCore.parsePaths(sb.ToString()); action.Invoke(files); } ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放 } /// /// 打开保存文件选择对话框 /// /// /// public static void SaveFilePanel(Settings settings, Action action) { LibUniWinC.PanelSettings ps = new LibUniWinC.PanelSettings(settings); StringBuilder sb = new StringBuilder(pathBufferSize); if (LibUniWinC.OpenSavePanel(in ps, sb, (uint)sb.Capacity)) { string[] files = UniWinCore.parsePaths(sb.ToString()); action.Invoke(files); } ps.Dispose(); // 通过传入 Settings 的构造函数分配了内存,因此需要释放 } } }