210 lines
7.5 KiB
C#
210 lines
7.5 KiB
C#
using AOT;
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace Kirurobo
|
|
{
|
|
/// <summary>
|
|
/// 提供打开原生文件对话框的静态方法
|
|
/// </summary>
|
|
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<IntPtr>() * 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 对话框的设置标志
|
|
/// </summary>
|
|
[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,
|
|
}
|
|
|
|
/// <summary>
|
|
/// 文件对话框的参数
|
|
/// </summary>
|
|
public struct Settings
|
|
{
|
|
public string title;
|
|
public Filter[] filters;
|
|
public string initialDirectory;
|
|
public string initialFile;
|
|
public string defaultExtension; // 未实现
|
|
public Flag flags;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 文件过滤器
|
|
/// </summary>
|
|
public class Filter
|
|
{
|
|
protected string title;
|
|
protected string[] extensions;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="title">过滤器标题(macOS 上暂不可用)</param>
|
|
/// <param name="extensions">扩展名数组,如 ["png", "jpg", "txt"]</param>
|
|
public Filter(string title, params string[] extensions)
|
|
{
|
|
this.title = title;
|
|
this.extensions = extensions;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return title + "\t" + String.Join("\t", extensions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 返回由 Filter 数组转换后的字符串
|
|
/// </summary>
|
|
/// <param name="filters"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 用于传递文件或文件夹路径的 UTF-16 缓冲区字符数
|
|
/// 因为多个路径以换行符分隔,260 字符不够用。
|
|
/// </summary>
|
|
private const int pathBufferSize = 2560;
|
|
|
|
|
|
/// <summary>
|
|
/// 打开文件选择对话框
|
|
/// </summary>
|
|
/// <param name="settings"></param>
|
|
/// <param name="action"></param>
|
|
public static void OpenFilePanel(Settings settings, Action<string[]> 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 的构造函数分配了内存,因此需要释放
|
|
}
|
|
|
|
/// <summary>
|
|
/// 打开保存文件选择对话框
|
|
/// </summary>
|
|
/// <param name="settings"></param>
|
|
/// <param name="action"></param>
|
|
public static void SaveFilePanel(Settings settings, Action<string[]> 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 的构造函数分配了内存,因此需要释放
|
|
}
|
|
}
|
|
}
|