Script for automating a large assortment of AME related actions
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
9.0 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using System.Runtime.InteropServices.ComTypes;
  7. namespace amecs.Misc
  8. {
  9. public class FolderPicker
  10. {
  11. private readonly List<string> _resultPaths = new List<string>();
  12. private readonly List<string> _resultNames = new List<string>();
  13. public IReadOnlyList<string> ResultPaths => _resultPaths;
  14. public IReadOnlyList<string> ResultNames => _resultNames;
  15. public string ResultPath => ResultPaths.FirstOrDefault();
  16. public string ResultName => ResultNames.FirstOrDefault();
  17. public virtual string InputPath { get; set; }
  18. public virtual bool ForceFileSystem { get; set; }
  19. public virtual bool Multiselect { get; set; }
  20. public virtual string Title { get; set; }
  21. public virtual string OkButtonLabel { get; set; }
  22. public virtual string FileNameLabel { get; set; }
  23. protected virtual int SetOptions(int options)
  24. {
  25. if (ForceFileSystem)
  26. {
  27. options |= (int)FOS.FOS_FORCEFILESYSTEM;
  28. }
  29. if (Multiselect)
  30. {
  31. options |= (int)FOS.FOS_ALLOWMULTISELECT;
  32. }
  33. return options;
  34. }
  35. // for all .NET
  36. public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false)
  37. {
  38. var dialog = (IFileOpenDialog)new FileOpenDialog();
  39. if (!string.IsNullOrEmpty(InputPath))
  40. {
  41. if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out var item), throwOnError) != 0)
  42. return null;
  43. dialog.SetFolder(item);
  44. }
  45. var options = FOS.FOS_PICKFOLDERS;
  46. options = (FOS)SetOptions((int)options);
  47. dialog.SetOptions(options);
  48. if (Title != null)
  49. {
  50. dialog.SetTitle(Title);
  51. }
  52. if (OkButtonLabel != null)
  53. {
  54. dialog.SetOkButtonLabel(OkButtonLabel);
  55. }
  56. if (FileNameLabel != null)
  57. {
  58. dialog.SetFileName(FileNameLabel);
  59. }
  60. if (owner == IntPtr.Zero)
  61. {
  62. owner = Process.GetCurrentProcess().MainWindowHandle;
  63. if (owner == IntPtr.Zero)
  64. {
  65. owner = GetDesktopWindow();
  66. }
  67. }
  68. var hr = dialog.Show(owner);
  69. if (hr == ERROR_CANCELLED)
  70. return null;
  71. if (CheckHr(hr, throwOnError) != 0)
  72. return null;
  73. if (CheckHr(dialog.GetResults(out var items), throwOnError) != 0)
  74. return null;
  75. items.GetCount(out var count);
  76. for (var i = 0; i < count; i++)
  77. {
  78. items.GetItemAt(i, out var item);
  79. CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError);
  80. CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var name), throwOnError);
  81. if (path != null || name != null)
  82. {
  83. _resultPaths.Add(path);
  84. _resultNames.Add(name);
  85. }
  86. }
  87. return true;
  88. }
  89. private static int CheckHr(int hr, bool throwOnError)
  90. {
  91. if (hr != 0 && throwOnError) Marshal.ThrowExceptionForHR(hr);
  92. return hr;
  93. }
  94. [DllImport("shell32")]
  95. private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
  96. [DllImport("user32")]
  97. private static extern IntPtr GetDesktopWindow();
  98. #pragma warning disable IDE1006 // Naming Styles
  99. private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
  100. #pragma warning restore IDE1006 // Naming Styles
  101. [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog
  102. private class FileOpenDialog { }
  103. [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  104. private interface IFileOpenDialog
  105. {
  106. [PreserveSig] int Show(IntPtr parent); // IModalWindow
  107. [PreserveSig] int SetFileTypes(); // not fully defined
  108. [PreserveSig] int SetFileTypeIndex(int iFileType);
  109. [PreserveSig] int GetFileTypeIndex(out int piFileType);
  110. [PreserveSig] int Advise(); // not fully defined
  111. [PreserveSig] int Unadvise();
  112. [PreserveSig] int SetOptions(FOS fos);
  113. [PreserveSig] int GetOptions(out FOS pfos);
  114. [PreserveSig] int SetDefaultFolder(IShellItem psi);
  115. [PreserveSig] int SetFolder(IShellItem psi);
  116. [PreserveSig] int GetFolder(out IShellItem ppsi);
  117. [PreserveSig] int GetCurrentSelection(out IShellItem ppsi);
  118. [PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
  119. [PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
  120. [PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
  121. [PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
  122. [PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
  123. [PreserveSig] int GetResult(out IShellItem ppsi);
  124. [PreserveSig] int AddPlace(IShellItem psi, int alignment);
  125. [PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
  126. [PreserveSig] int Close(int hr);
  127. [PreserveSig] int SetClientGuid(); // not fully defined
  128. [PreserveSig] int ClearClientData();
  129. [PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter);
  130. [PreserveSig] int GetResults(out IShellItemArray ppenum);
  131. [PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai);
  132. }
  133. [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  134. private interface IShellItem
  135. {
  136. [PreserveSig] int BindToHandler(); // not fully defined
  137. [PreserveSig] int GetParent(); // not fully defined
  138. [PreserveSig] int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
  139. [PreserveSig] int GetAttributes(); // not fully defined
  140. [PreserveSig] int Compare(); // not fully defined
  141. }
  142. [ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  143. private interface IShellItemArray
  144. {
  145. [PreserveSig] int BindToHandler(); // not fully defined
  146. [PreserveSig] int GetPropertyStore(); // not fully defined
  147. [PreserveSig] int GetPropertyDescriptionList(); // not fully defined
  148. [PreserveSig] int GetAttributes(); // not fully defined
  149. [PreserveSig] int GetCount(out int pdwNumItems);
  150. [PreserveSig] int GetItemAt(int dwIndex, out IShellItem ppsi);
  151. [PreserveSig] int EnumItems(); // not fully defined
  152. }
  153. #pragma warning disable CA1712 // Do not prefix enum values with type name
  154. private enum SIGDN : uint
  155. {
  156. SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
  157. SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
  158. SIGDN_FILESYSPATH = 0x80058000,
  159. SIGDN_NORMALDISPLAY = 0,
  160. SIGDN_PARENTRELATIVE = 0x80080001,
  161. SIGDN_PARENTRELATIVEEDITING = 0x80031001,
  162. SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
  163. SIGDN_PARENTRELATIVEPARSING = 0x80018001,
  164. SIGDN_URL = 0x80068000
  165. }
  166. [Flags]
  167. private enum FOS
  168. {
  169. FOS_OVERWRITEPROMPT = 0x2,
  170. FOS_STRICTFILETYPES = 0x4,
  171. FOS_NOCHANGEDIR = 0x8,
  172. FOS_PICKFOLDERS = 0x20,
  173. FOS_FORCEFILESYSTEM = 0x40,
  174. FOS_ALLNONSTORAGEITEMS = 0x80,
  175. FOS_NOVALIDATE = 0x100,
  176. FOS_ALLOWMULTISELECT = 0x200,
  177. FOS_PATHMUSTEXIST = 0x800,
  178. FOS_FILEMUSTEXIST = 0x1000,
  179. FOS_CREATEPROMPT = 0x2000,
  180. FOS_SHAREAWARE = 0x4000,
  181. FOS_NOREADONLYRETURN = 0x8000,
  182. FOS_NOTESTFILECREATE = 0x10000,
  183. FOS_HIDEMRUPLACES = 0x20000,
  184. FOS_HIDEPINNEDPLACES = 0x40000,
  185. FOS_NODEREFERENCELINKS = 0x100000,
  186. FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
  187. FOS_DONTADDTORECENT = 0x2000000,
  188. FOS_FORCESHOWHIDDEN = 0x10000000,
  189. FOS_DEFAULTNOMINIMODE = 0x20000000,
  190. FOS_FORCEPREVIEWPANEON = 0x40000000,
  191. FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000)
  192. }
  193. #pragma warning restore CA1712 // Do not prefix enum values with type name
  194. }
  195. }