diff --git a/build/common.props b/build/common.props
index 4bc6c1ef..8fb2cbb3 100644
--- a/build/common.props
+++ b/build/common.props
@@ -3,7 +3,7 @@
1.0.0
- 5.8.1
+ 5.8.2
2.2.2
2.8.8
2.8.0
diff --git a/changelog.txt b/changelog.txt
index 41aee7c7..f2487033 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,4 +1,8 @@
---------------------------------------------------------------------------------------------------
+Version: 5.8.2
+Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1,v1.1.2,v1.1.3,v1.1.4,v1.1.5,v1.2.0,v1.2.1
+* Added Preset Import/Export/Deleting
+---------------------------------------------------------------------------------------------------
Version: 5.8.1
Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1,v1.1.2,v1.1.3,v1.1.4,v1.1.5,v1.2.0,v1.2.1
* Fixed missing Preset saving
diff --git a/src/MCM.Abstractions/GameFeatures/IFileSystemProvider.cs b/src/MCM.Abstractions/GameFeatures/IFileSystemProvider.cs
index 64ec8fdd..03079c33 100644
--- a/src/MCM.Abstractions/GameFeatures/IFileSystemProvider.cs
+++ b/src/MCM.Abstractions/GameFeatures/IFileSystemProvider.cs
@@ -40,7 +40,8 @@ interface IFileSystemProvider
GameFile[] GetFiles(GameDirectory directory, string searchPattern);
GameFile? GetFile(GameDirectory directory, string fileName);
GameFile GetOrCreateFile(GameDirectory directory, string fileName);
- bool WriteData(GameFile file, byte[] data);
+ bool WriteData(GameFile file, byte[]? data);
byte[]? ReadData(GameFile file);
+ string? GetSystemPath(GameFile file);
}
}
\ No newline at end of file
diff --git a/src/MCM.Abstractions/Presets/JsonSettingsPreset.cs b/src/MCM.Abstractions/Presets/JsonSettingsPreset.cs
index 5ebd1b22..5525d7cd 100644
--- a/src/MCM.Abstractions/Presets/JsonSettingsPreset.cs
+++ b/src/MCM.Abstractions/Presets/JsonSettingsPreset.cs
@@ -40,6 +40,14 @@ private sealed class PresetContainer : PresetContainerDefinition
public BaseSettings? Settings { get; set; }
}
+ public static string? GetPresetId(string content)
+ {
+ var container = JsonConvert.DeserializeObject(content);
+ if (container is null) return null;
+
+ return container.Id;
+ }
+
public static JsonSettingsPreset? FromFile(BaseSettings settings, GameFile file) => FromFile(settings.Id, file, settings.CreateNew);
public static JsonSettingsPreset? FromFile(string settingsId, GameFile file, Func getNewSettings)
{
diff --git a/src/MCM.Bannerlord/GameFeatures/FileSystemProvider.cs b/src/MCM.Bannerlord/GameFeatures/FileSystemProvider.cs
index 57134e70..ed210961 100644
--- a/src/MCM.Bannerlord/GameFeatures/FileSystemProvider.cs
+++ b/src/MCM.Bannerlord/GameFeatures/FileSystemProvider.cs
@@ -64,9 +64,13 @@ public GameFile GetOrCreateFile(GameDirectory directory, string fileName)
return new GameFile(directory, fileName);
}
- public bool WriteData(GameFile file, byte[] data)
+ public bool WriteData(GameFile file, byte[]? data)
{
var baseFile = new TWPlatformFilePath(new TWPlatformDirectoryPath((PlatformFileType) file.Owner.Type, file.Owner.Path), file.Name);
+
+ if (data is null)
+ return PlatformFileHelper.DeleteFile(baseFile);
+
return PlatformFileHelper.SaveFile(baseFile, data) == SaveResult.Success;
}
@@ -75,5 +79,11 @@ public bool WriteData(GameFile file, byte[] data)
var baseFile = new TWPlatformFilePath(new TWPlatformDirectoryPath((PlatformFileType) file.Owner.Type, file.Owner.Path), file.Name);
return !PlatformFileHelper.FileExists(baseFile) ? null : PlatformFileHelper.GetFileContent(baseFile);
}
+
+ public string? GetSystemPath(GameFile file)
+ {
+ var baseFile = new TWPlatformFilePath(new TWPlatformDirectoryPath((PlatformFileType) file.Owner.Type, file.Owner.Path), file.Name);
+ return PlatformFileHelper.GetFileFullPath(baseFile);
+ }
}
}
\ No newline at end of file
diff --git a/src/MCM.Publish/_Module/ModuleData/Languages/EN/sta_strings.xml b/src/MCM.Publish/_Module/ModuleData/Languages/EN/sta_strings.xml
index 58f5be95..16ed5b17 100644
--- a/src/MCM.Publish/_Module/ModuleData/Languages/EN/sta_strings.xml
+++ b/src/MCM.Publish/_Module/ModuleData/Languages/EN/sta_strings.xml
@@ -43,5 +43,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/MCM.UI/GUI/Brushes/SettingsBrush.xml b/src/MCM.UI/GUI/Brushes/SettingsBrush.xml
index 6ca6ec5a..294011df 100644
--- a/src/MCM.UI/GUI/Brushes/SettingsBrush.xml
+++ b/src/MCM.UI/GUI/Brushes/SettingsBrush.xml
@@ -1,4 +1,10 @@
+
+
+
+
+
+
diff --git a/src/MCM.UI/GUI/Prefabs/ModOptionsPageView.xml b/src/MCM.UI/GUI/Prefabs/ModOptionsPageView.xml
index caa8ad5d..5b4255b0 100644
--- a/src/MCM.UI/GUI/Prefabs/ModOptionsPageView.xml
+++ b/src/MCM.UI/GUI/Prefabs/ModOptionsPageView.xml
@@ -82,6 +82,11 @@
+
+
+
+
@@ -98,24 +103,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/MCM.UI/GUI/Prefabs/ModOptionsView.xml b/src/MCM.UI/GUI/Prefabs/ModOptionsView.xml
index 38dfb998..5554e170 100644
--- a/src/MCM.UI/GUI/Prefabs/ModOptionsView.xml
+++ b/src/MCM.UI/GUI/Prefabs/ModOptionsView.xml
@@ -112,6 +112,11 @@
+
+
+
+
@@ -128,24 +133,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/MCM.UI/GUI/ViewModels/ModOptionsVM.cs b/src/MCM.UI/GUI/ViewModels/ModOptionsVM.cs
index 136f6fa2..e93e0c88 100644
--- a/src/MCM.UI/GUI/ViewModels/ModOptionsVM.cs
+++ b/src/MCM.UI/GUI/ViewModels/ModOptionsVM.cs
@@ -18,11 +18,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using TaleWorlds.Core;
using TaleWorlds.Engine;
using TaleWorlds.Library;
using TaleWorlds.Localization;
@@ -372,64 +374,226 @@ public void ExecuteSelect(SettingsVM? viewModel)
}
}
- public void ExecuteSaveAsPreset()
+ private void RefreshPresetList()
{
- if (SelectedMod?.SettingsInstance is not { } settings) return;
- var settingsSnapshot = settings.CopyAsNew();
+ if (SelectedMod is null) return;
- var fileSystem = GenericServiceProvider.GetService();
- if (fileSystem is null) return;
+ SelectedMod.ReloadPresetList();
+ DoPresetsSelectorCopyWithoutEvents(() =>
+ {
+ PresetsSelectorCopy.Refresh(SelectedMod.PresetsSelector.ItemList.Select(x => x.OriginalItem), SelectedMod.PresetsSelector.SelectedIndex);
+ });
+ }
- InformationManager.ShowTextInquiry(InquiryDataUtils.CreateTextTranslatable(
- "{=ModOptionsVM_SaveAsPreset}Save As Preset",
- "{=ModOptionsVM_SaveAsPresetDesc}Choose the name of the preset",
+ private void OverridePreset(Action onOverride)
+ {
+ InformationManager.ShowInquiry(InquiryDataUtils.CreateTranslatable(
+ "{=ModOptionsVM_OverridePreset}Preset Already Exists",
+ "{=ModOptionsVM_OverridePresetDesc}Preset already exists! Do you want to override it?",
true, true,
- "{=5Unqsx3N}Confirm",
+ "{=aeouhelq}Yes",
"{=3CpNUnVl}Cancel",
- input =>
- {
- var hasSet = new HashSet(System.IO.Path.GetInvalidFileNameChars());
- var sb = new StringBuilder();
- foreach (var c in input) sb.Append(hasSet.Contains(c) ? '_' : c);
- var id = sb.ToString();
+ onOverride, () => { }));
+ }
+
+ public void ExecuteManagePresets()
+ {
+ const string savePreset = "save_preset";
+ const string importPreset = "import_preset";
+ const string exportPreset = "export_preset";
+ const string deletePreset = "delete_preset";
- var presetsDirectory = fileSystem.GetOrCreateDirectory(fileSystem.GetModSettingsDirectory(), "Presets");
- var settingsDirectory = fileSystem.GetOrCreateDirectory(presetsDirectory, settingsSnapshot.Id);
+ if (SelectedMod?.SettingsInstance is not { } settings) return;
+
+ var fileSystem = GenericServiceProvider.GetService();
+ if (fileSystem is null) return;
+
+ if (PresetsSelectorCopy.SelectedItem?.OriginalItem is null) return;
- var filename = $"{id}.json";
+ void SaveAsPreset(GameDirectory settingsDirectory)
+ {
+ var settingsSnapshot = settings.CopyAsNew();
- void SavePreset()
+ InformationManager.ShowTextInquiry(InquiryDataUtils.CreateTextTranslatable(
+ "{=ModOptionsVM_SaveAsPreset}Save As Preset",
+ "{=ModOptionsVM_SaveAsPresetDesc}Choose the name of the preset",
+ true, true,
+ "{=5Unqsx3N}Confirm",
+ "{=3CpNUnVl}Cancel",
+ input =>
{
- var presetFile = fileSystem.GetOrCreateFile(settingsDirectory, filename);
+ var hasSet = new HashSet(System.IO.Path.GetInvalidFileNameChars());
+ var sb = new StringBuilder();
+ foreach (var c in input) sb.Append(hasSet.Contains(c) ? '_' : c);
+ var id = sb.ToString();
- var preset = new JsonSettingsPreset(settingsSnapshot.Id, id, input, presetFile, () => null!);
- preset.SavePreset(settingsSnapshot);
+ var filename = $"{id}.json";
- SelectedMod.ReloadPresetList();
- DoPresetsSelectorCopyWithoutEvents(() =>
+ void SavePreset()
{
- PresetsSelectorCopy.Refresh(SelectedMod.PresetsSelector.ItemList.Select(x => x.OriginalItem), SelectedMod.PresetsSelector.SelectedIndex);
- });
+ var presetFile = fileSystem.GetOrCreateFile(settingsDirectory, filename);
+
+ var preset = new JsonSettingsPreset(settingsSnapshot.Id, id, input, presetFile, () => null!);
+ preset.SavePreset(settingsSnapshot);
+
+ RefreshPresetList();
+ }
+
+ if (fileSystem.GetFile(settingsDirectory, filename) is not null)
+ {
+ // Override file?
+ OverridePreset(SavePreset);
+ return;
+ }
+
+ SavePreset();
+ }, () => { }));
+ }
+
+ void ImportNewPreset(GameDirectory settingsDirectory)
+ {
+ var dialog = new OpenFileDialog
+ {
+ Title = "Import Preset",
+ Filter = "MCM Preset (.json)|*.json",
+ CheckFileExists = true,
+ CheckPathExists = true,
+ ReadOnlyChecked = true,
+ Multiselect = false,
+ ValidateNames = true,
+ };
+ if (dialog.ShowDialog())
+ {
+ var content = File.ReadAllText(dialog.FileName);
+ var presetId = JsonSettingsPreset.GetPresetId(content);
+
+ void CopyFile()
+ {
+ var presetFile = fileSystem.GetOrCreateFile(settingsDirectory, $"{presetId}.json");
+ var path = fileSystem.GetSystemPath(presetFile);
+ if (path is null) return;
+ try
+ {
+ File.Copy(dialog.FileName, path, true);
+ }
+ catch (Exception) { /* ignore */ }
}
+ var filename = $"{presetId}.json";
if (fileSystem.GetFile(settingsDirectory, filename) is not null)
{
- // Override file?
- InformationManager.ShowInquiry(InquiryDataUtils.CreateTranslatable(
- "{=ModOptionsVM_OverridePreset}Preset Already Exists",
- "{=ModOptionsVM_OverridePresetDesc}Preset already exists! Do you want to override it?",
- true, true,
- "{=aeouhelq}Yes",
- "{=3CpNUnVl}Cancel",
- () =>
- {
- SavePreset();
- }, () => { }));
+ OverridePreset(CopyFile);
return;
}
- SavePreset();
- }, () => { }));
+ CopyFile();
+ }
+
+ RefreshPresetList();
+ }
+
+ void ExportPreset(GameFile presetFile)
+ {
+ var path = fileSystem.GetSystemPath(presetFile);
+ if (path is null) return;
+
+ var dialog = new SaveFileDialog
+ {
+ Title = "Export Preset",
+ Filter = "MCM Preset (.json)|*.json",
+ FileName = System.IO.Path.GetFileName(path),
+
+ ValidateNames = true,
+
+ OverwritePrompt = true,
+ };
+ if (dialog.ShowDialog())
+ {
+ try
+ {
+ File.Copy(path, dialog.FileName, true);
+ }
+ catch (Exception) { /* ignore */ }
+ }
+ }
+
+ void DeletePreset(GameFile presetFile)
+ {
+ fileSystem.WriteData(presetFile, null);
+
+ RefreshPresetList();
+ }
+
+ void OnActionSelected(List selected)
+ {
+ var selectedPresetKey = PresetsSelectorCopy.SelectedItem?.OriginalItem;
+ if (selectedPresetKey is null) return;
+
+ var presetsDirectory = fileSystem.GetOrCreateDirectory(fileSystem.GetModSettingsDirectory(), "Presets");
+ var settingsDirectory = fileSystem.GetOrCreateDirectory(presetsDirectory, settings.Id);
+
+ var filename = $"{selectedPresetKey.Id}.json";
+
+ switch (selected[0].Identifier)
+ {
+ case savePreset:
+ {
+ SaveAsPreset(settingsDirectory);
+ break;
+ }
+ case importPreset:
+ {
+ ImportNewPreset(settingsDirectory);
+ break;
+ }
+ case exportPreset:
+ {
+ var presetFile = fileSystem.GetFile(settingsDirectory, filename);
+ if (presetFile is null) return;
+ ExportPreset(presetFile);
+ break;
+ }
+
+ case deletePreset:
+ {
+ var presetFile = fileSystem.GetFile(settingsDirectory, filename);
+ if (presetFile is null) return;
+ DeletePreset(presetFile);
+ break;
+ }
+ }
+ }
+
+ var inquiries = new List
+ {
+ new(importPreset, new TextObject("{=ModOptionsVM_ManagePresetsImport}Import a new Preset").ToString(), null)
+ };
+
+ if (PresetsSelectorCopy.SelectedItem.OriginalItem.Id == "custom")
+ {
+ inquiries.Add(new(savePreset, new TextObject("{=ModOptionsVM_SaveAsPreset}Save As Preset").ToString(), null));
+ }
+
+ if (PresetsSelectorCopy.SelectedItem.OriginalItem.Id is not "custom" and not "default")
+ {
+ inquiries.Add(new(exportPreset, new TextObject("{=ModOptionsVM_ManagePresetsExport}Export Preset '{PRESETNAME}'", new Dictionary()
+ {
+ { "PRESETNAME", PresetsSelectorCopy.SelectedItem.OriginalItem.Name }
+ }).ToString(), null));
+ inquiries.Add(new(deletePreset, new TextObject("{=ModOptionsVM_ManagePresetsDelete}Delete Preset '{PRESETNAME}'", new Dictionary()
+ {
+ { "PRESETNAME", PresetsSelectorCopy.SelectedItem.OriginalItem.Name }
+ }).ToString(), null));
+ }
+
+ MBInformationManager.ShowMultiSelectionInquiry(InquiryDataUtils.CreateMultiTranslatable(
+ "{=ModOptionsVM_ManagePresets}Manage Presets", "",
+ inquiries,
+ true,
+ 1, 1,
+ "{=5Unqsx3N}Confirm",
+ "{=3CpNUnVl}Cancel",
+ OnActionSelected, _ => { }));
}
public override void OnFinalize()
diff --git a/src/MCM.UI/MCM.UI.csproj b/src/MCM.UI/MCM.UI.csproj
index 29f5f5ec..725b0fb6 100644
--- a/src/MCM.UI/MCM.UI.csproj
+++ b/src/MCM.UI/MCM.UI.csproj
@@ -8,6 +8,7 @@
MCM.UI
Debug;Release
$(DefineConstants)
+ true
diff --git a/src/MCM.UI/Utils/InquiryDataUtils.cs b/src/MCM.UI/Utils/InquiryDataUtils.cs
index 7336aaa6..5616bb2c 100644
--- a/src/MCM.UI/Utils/InquiryDataUtils.cs
+++ b/src/MCM.UI/Utils/InquiryDataUtils.cs
@@ -1,8 +1,10 @@
using HarmonyLib.BUTR.Extensions;
using System;
+using System.Collections.Generic;
using System.Linq;
+using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.Localization;
@@ -21,12 +23,25 @@ private delegate InquiryData V2Delegate(string titleText, string text, bool isAf
private static readonly V2Delegate? V2 =
AccessTools2.GetConstructorDelegate(typeof(InquiryData), typeof(V2Delegate).GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray());
+
private delegate TextInquiryData V1TextDelegate(string titleText, string text, bool isAffirmativeOptionShown, bool isNegativeOptionShown, string affirmativeText, string negativeText,
Action affirmativeAction, Action negativeAction, bool shouldInputBeObfuscated = false, Func>? textCondition = null, string soundEventPath = "",
string defaultInputText = "");
private static readonly V1TextDelegate? V1Text =
AccessTools2.GetConstructorDelegate(typeof(TextInquiryData), typeof(V1TextDelegate).GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray());
+
+ private delegate MultiSelectionInquiryData V1MultiDelegate(string titleText, string descriptionText, List inquiryElements, bool isExitShown, int maxSelectableOptionCount,
+ string affirmativeText, string negativeText, Action> affirmativeAction, Action> negativeAction, string soundEventPath = "");
+ private static readonly V1MultiDelegate? V1Multi =
+ AccessTools2.GetConstructorDelegate(typeof(MultiSelectionInquiryData), typeof(V1MultiDelegate).GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray());
+
+ private delegate MultiSelectionInquiryData V2MultiDelegate(string titleText, string descriptionText, List inquiryElements, bool isExitShown, int minSelectableOptionCount, int maxSelectableOptionCount,
+ string affirmativeText, string negativeText, Action> affirmativeAction, Action> negativeAction, string soundEventPath = "");
+ private static readonly V2MultiDelegate? V2Multi =
+ AccessTools2.GetConstructorDelegate(typeof(MultiSelectionInquiryData), typeof(V2MultiDelegate).GetMethod("Invoke").GetParameters().Select(x => x.ParameterType).ToArray());
+
+
public static InquiryData? Create(string titleText, string text, bool isAffirmativeOptionShown, bool isNegativeOptionShown, string affirmativeText, string negativeText, Action affirmativeAction, Action negativeAction)
{
if (V1 is not null)
@@ -53,5 +68,19 @@ private delegate TextInquiryData V1TextDelegate(string titleText, string text, b
public static TextInquiryData? CreateTextTranslatable(string titleText, string text, bool isAffirmativeOptionShown, bool isNegativeOptionShown, string affirmativeText, string negativeText, Action affirmativeAction, Action negativeAction) =>
CreateText(new TextObject(titleText).ToString(), new TextObject(text).ToString(), isAffirmativeOptionShown, isNegativeOptionShown, new TextObject(affirmativeText).ToString(), new TextObject(negativeText).ToString(), affirmativeAction, negativeAction);
+
+ public static MultiSelectionInquiryData? CreateMulti(string titleText, string descriptionText, List inquiryElements, bool isExitShown, int minSelectableOptionCount, int maxSelectableOptionCount, string affirmativeText, string negativeText, Action> affirmativeAction, Action> negativeAction)
+ {
+ if (V1Multi is not null)
+ return V1Multi(titleText, descriptionText, inquiryElements, isExitShown, maxSelectableOptionCount, affirmativeText, negativeText, affirmativeAction, negativeAction);
+
+ if (V2Multi is not null)
+ return V2Multi(titleText, descriptionText, inquiryElements, isExitShown, minSelectableOptionCount, maxSelectableOptionCount, affirmativeText, negativeText, affirmativeAction, negativeAction);
+
+ return null;
+ }
+
+ public static MultiSelectionInquiryData? CreateMultiTranslatable(string titleText, string descriptionText, List inquiryElements, bool isExitShown, int minSelectableOptionCount, int maxSelectableOptionCount, string affirmativeText, string negativeText, Action> affirmativeAction, Action> negativeAction) =>
+ CreateMulti(new TextObject(titleText).ToString(), new TextObject(descriptionText).ToString(), inquiryElements, isExitShown, minSelectableOptionCount, maxSelectableOptionCount, new TextObject(affirmativeText).ToString(), new TextObject(negativeText).ToString(), affirmativeAction, negativeAction);
}
}
\ No newline at end of file
diff --git a/src/MCM.UI/Utils/OpenSaveDialogs.cs b/src/MCM.UI/Utils/OpenSaveDialogs.cs
new file mode 100644
index 00000000..1522fd53
--- /dev/null
+++ b/src/MCM.UI/Utils/OpenSaveDialogs.cs
@@ -0,0 +1,2012 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+
+namespace MCM.UI.Utils;
+
+/// The StrPtr structure represents a LPTSTR.
+[StructLayout(LayoutKind.Sequential), DebuggerDisplay("{ptr}, {ToString()}")]
+file struct StrPtrAuto : IEquatable, IEquatable, IEquatable
+{
+ private IntPtr ptr;
+
+ /// Initializes a new instance of the struct.
+ /// The string value.
+ public StrPtrAuto(string s) => ptr = StringHelper.AllocString(s);
+
+ /// Initializes a new instance of the struct.
+ /// Number of characters to reserve in memory.
+ public StrPtrAuto(uint charLen) => ptr = StringHelper.AllocChars(charLen);
+
+ /// Gets a value indicating whether this instance is equivalent to null pointer or void*.
+ /// true if this instance is null; otherwise, false.
+ public bool IsNull => ptr == IntPtr.Zero;
+
+ /// Assigns a string pointer value to the pointer.
+ /// The string pointer value.
+ public void Assign(IntPtr stringPtr) { Free(); ptr = stringPtr; }
+
+ /// Assigns a new string value to the pointer.
+ /// The string value.
+ public void Assign(string s) => StringHelper.RefreshString(ref ptr, out var _, s);
+
+ /// Assigns a new string value to the pointer.
+ /// The string value.
+ /// The character count allocated.
+ /// true if new memory was allocated for the string; false if otherwise.
+ public bool Assign(string s, out uint charsAllocated) => StringHelper.RefreshString(ref ptr, out charsAllocated, s);
+
+ /// Assigns an integer to the pointer for uses such as LPSTR_TEXTCALLBACK.
+ /// The value to assign.
+ public void AssignConstant(int value) { Free(); ptr = (IntPtr)value; }
+
+ /// Frees the unmanaged string memory.
+ public void Free() { StringHelper.FreeString(ptr); ptr = IntPtr.Zero; }
+
+ /// Indicates whether the specified string is or an empty string ("").
+ ///
+ /// if the value parameter is or an empty string (""); otherwise, .
+ ///
+ public bool IsNullOrEmpty => ptr == IntPtr.Zero || StringHelper.GetString(ptr, CharSet.Auto, 1) == string.Empty;
+
+ /// Performs an implicit conversion from to .
+ /// The instance.
+ /// The result of the conversion.
+ public static implicit operator string?(StrPtrAuto p) => p.IsNull ? null : p.ToString();
+
+ /// Performs an explicit conversion from to .
+ /// The instance.
+ /// The result of the conversion.
+ public static explicit operator IntPtr(StrPtrAuto p) => p.ptr;
+
+ /// Performs an implicit conversion from to .
+ /// The pointer.
+ /// The result of the conversion.
+ public static implicit operator StrPtrAuto(IntPtr p) => new() { ptr = p };
+
+ /// Determines whether the specified , is equal to this instance.
+ /// The to compare with this instance.
+ /// true if the specified is equal to this instance; otherwise, false.
+ public bool Equals(IntPtr other) => EqualityComparer.Default.Equals(ptr, other);
+
+ /// Determines whether the specified , is equal to this instance.
+ /// The to compare with this instance.
+ /// true if the specified is equal to this instance; otherwise, false.
+ public bool Equals(string? other) => EqualityComparer.Default.Equals(this, other);
+
+ /// Determines whether the specified , is equal to this instance.
+ /// The to compare with this instance.
+ /// true if the specified is equal to this instance; otherwise, false.
+ public bool Equals(StrPtrAuto other) => Equals(other.ptr);
+
+ /// Determines whether the specified , is equal to this instance.
+ /// The to compare with this instance.
+ /// true if the specified is equal to this instance; otherwise, false.
+ public override bool Equals(object obj) => obj switch
+ {
+ null => IsNull,
+ string s => Equals(s),
+ StrPtrAuto p => Equals(p),
+ IntPtr p => Equals(p),
+ _ => base.Equals(obj),
+ };
+
+ /// Returns a hash code for this instance.
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ public override int GetHashCode() => ptr.GetHashCode();
+
+ /// Returns a that represents this instance.
+ /// A that represents this instance.
+ public override string ToString() => StringHelper.GetString(ptr) ?? "null";
+
+ /// Determines whether two specified instances of are equal.
+ /// The first pointer or handle to compare.
+ /// The second pointer or handle to compare.
+ /// if equals ; otherwise, .
+ public static bool operator ==(StrPtrAuto left, StrPtrAuto right) => left.Equals(right);
+
+ /// Determines whether two specified instances of are not equal.
+ /// The first pointer or handle to compare.
+ /// The second pointer or handle to compare.
+ /// if does not equal ; otherwise, .
+ public static bool operator !=(StrPtrAuto left, StrPtrAuto right) => !left.Equals(right);
+}
+
+/// A safe class that represents an object that is pinned in memory.
+///
+file static class StringHelper
+{
+ /// Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.
+ /// The number of characters, inclusive of the null terminator.
+ /// The method used to allocate the memory.
+ /// The character set.
+ /// The address of the block of memory allocated.
+ public static IntPtr AllocChars(uint count, Func memAllocator, CharSet charSet = CharSet.Auto)
+ {
+ if (count == 0) return IntPtr.Zero;
+ var sz = GetCharSize(charSet);
+ var ptr = memAllocator((int)count * sz);
+ if (count > 0)
+ {
+ if (sz == 1)
+ Marshal.WriteByte(ptr, 0);
+ else
+ Marshal.WriteInt16(ptr, 0);
+ }
+ return ptr;
+ }
+
+ /// Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.
+ /// The number of characters, inclusive of the null terminator.
+ /// The character set.
+ /// The address of the block of memory allocated.
+ public static IntPtr AllocChars(uint count, CharSet charSet = CharSet.Auto) => AllocChars(count, Marshal.AllocCoTaskMem, charSet);
+
+ /// Copies the contents of a managed object to a block of memory allocated from the unmanaged COM task allocator.
+ /// The managed object to copy.
+ /// The character set.
+ /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied.
+ public static IntPtr AllocSecureString(SecureString? s, CharSet charSet = CharSet.Auto)
+ {
+ if (s == null) return IntPtr.Zero;
+ if (GetCharSize(charSet) == 2)
+ return Marshal.SecureStringToCoTaskMemUnicode(s);
+ return Marshal.SecureStringToCoTaskMemAnsi(s);
+ }
+
+ /// Copies the contents of a managed object to a block of memory allocated from a supplied allocation method.
+ /// The managed object to copy.
+ /// The character set.
+ /// The method used to allocate the memory.
+ /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied.
+ public static IntPtr AllocSecureString(SecureString? s, CharSet charSet, Func memAllocator) => AllocSecureString(s, charSet, memAllocator, out _);
+
+ /// Copies the contents of a managed object to a block of memory allocated from a supplied allocation method.
+ /// The managed object to copy.
+ /// The character set.
+ /// The method used to allocate the memory.
+ /// Returns the number of allocated bytes for the string.
+ /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied.
+ public static IntPtr AllocSecureString(SecureString? s, CharSet charSet, Func memAllocator, out int allocatedBytes)
+ {
+ allocatedBytes = 0;
+ if (s == null) return IntPtr.Zero;
+ var chSz = GetCharSize(charSet);
+ var encoding = chSz == 2 ? Encoding.Unicode : Encoding.UTF8;
+ var hMem = AllocSecureString(s, charSet);
+ var str = chSz == 2 ? Marshal.PtrToStringUni(hMem) : Marshal.PtrToStringAnsi(hMem);
+ Marshal.FreeCoTaskMem(hMem);
+ if (str == null) return IntPtr.Zero;
+ var b = encoding.GetBytes(str);
+ var p = memAllocator(b.Length);
+ Marshal.Copy(b, 0, p, b.Length);
+ allocatedBytes = b.Length;
+ return p;
+ }
+
+ /// Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator.
+ /// A managed string to be copied.
+ /// The character set.
+ /// The allocated memory block, or 0 if is null.
+ public static IntPtr AllocString(string? s, CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.StringToCoTaskMemAuto(s) : (charSet == CharSet.Unicode ? Marshal.StringToCoTaskMemUni(s) : Marshal.StringToCoTaskMemAnsi(s));
+
+ /// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.
+ /// A managed string to be copied.
+ /// The character set.
+ /// The method used to allocate the memory.
+ /// The allocated memory block, or 0 if is null.
+ public static IntPtr AllocString(string? s, CharSet charSet, Func memAllocator) => AllocString(s, charSet, memAllocator, out _);
+
+ ///
+ /// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.
+ ///
+ /// A managed string to be copied.
+ /// The character set.
+ /// The method used to allocate the memory.
+ /// Returns the number of allocated bytes for the string.
+ /// The allocated memory block, or 0 if is null.
+ public static IntPtr AllocString(string? s, CharSet charSet, Func memAllocator, out int allocatedBytes)
+ {
+ if (s == null) { allocatedBytes = 0; return IntPtr.Zero; }
+ var b = s.GetBytes(true, charSet);
+ var p = memAllocator(b.Length);
+ Marshal.Copy(b, 0, p, allocatedBytes = b.Length);
+ return p;
+ }
+
+ ///
+ /// Zeros out the allocated memory behind a secure string and then frees that memory.
+ ///
+ /// The address of the memory to be freed.
+ /// The size in bytes of the memory pointed to by .
+ /// The memory freer.
+ public static void FreeSecureString(IntPtr ptr, int sizeInBytes, Action memFreer)
+ {
+ if (IsValue(ptr)) return;
+ var b = new byte[sizeInBytes];
+ Marshal.Copy(b, 0, ptr, b.Length);
+ memFreer(ptr);
+ }
+
+ /// Frees a block of memory allocated by the unmanaged COM task memory allocator for a string.
+ /// The address of the memory to be freed.
+ /// The character set of the string.
+ public static void FreeString(IntPtr ptr, CharSet charSet = CharSet.Auto)
+ {
+ if (IsValue(ptr)) return;
+ if (GetCharSize(charSet) == 2)
+ Marshal.ZeroFreeCoTaskMemUnicode(ptr);
+ else
+ Marshal.ZeroFreeCoTaskMemAnsi(ptr);
+ }
+
+ /// Gets the encoded bytes for a string including an optional null terminator.
+ /// The string value to convert.
+ /// if set to true include a null terminator at the end of the string in the resulting byte array.
+ /// The character set.
+ /// A byte array including encoded as per and the optional null terminator.
+ public static byte[] GetBytes(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) =>
+ GetBytes(value, GetCharSize(charSet) == 1 ? Encoding.UTF8 : Encoding.Unicode, nullTerm);
+
+ /// Gets the encoded bytes for a string including an optional null terminator.
+ /// The string value to convert.
+ /// The character encoding.
+ /// if set to true include a null terminator at the end of the string in the resulting byte array.
+ /// A byte array including encoded as per and the optional null terminator.
+ public static byte[] GetBytes(this string value, Encoding enc, bool nullTerm = true)
+ {
+ var chSz = GetCharSize(enc);
+ var ret = new byte[enc.GetByteCount(value) + (nullTerm ? chSz : 0)];
+ enc.GetBytes(value, 0, value.Length, ret, 0);
+ if (nullTerm)
+ enc.GetBytes(new[] { '\0' }, 0, 1, ret, ret.Length - chSz);
+ return ret;
+ }
+
+ /// Gets the number of bytes required to store the string.
+ /// The string value.
+ /// if set to true include a null terminator at the end of the string in the count if does not equal null.
+ /// The character set.
+ /// The number of bytes required to store . Returns 0 if is null.
+ public static int GetByteCount(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) =>
+ GetByteCount(value, GetCharSize(charSet) == 1 ? Encoding.UTF8 : Encoding.Unicode, nullTerm);
+
+ /// Gets the number of bytes required to store the string.
+ /// The string value.
+ /// The character encoding.
+ /// if set to true include a null terminator at the end of the string in the count if does not equal null.
+ /// The number of bytes required to store . Returns 0 if is null.
+ public static int GetByteCount(this string value, Encoding enc, bool nullTerm = true) =>
+ value is null ? 0 : enc.GetByteCount(value) + (nullTerm ? GetCharSize(enc) : 0);
+
+ /// Gets the size of a character defined by the supplied .
+ /// The character set to size.
+ /// The size of a standard character, in bytes, from .
+ public static int GetCharSize(CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.SystemDefaultCharSize : (charSet == CharSet.Unicode ? UnicodeEncoding.CharSize : 1);
+
+ /// Gets the size of a character defined by the supplied .
+ /// The character encoding type.
+ /// The size of a standard character, in bytes, from .
+ public static int GetCharSize(Encoding enc) => enc.GetByteCount(new[] { '\0' });
+
+ ///
+ /// Allocates a managed String and copies all characters up to the first null character or the end of the allocated memory pool from a string stored in unmanaged memory into it.
+ ///
+ /// The address of the first character.
+ /// The character set of the string.
+ /// If known, the total number of bytes allocated to the native memory in .
+ ///
+ /// A managed string that holds a copy of the unmanaged string if the value of the parameter is not null;
+ /// otherwise, this method returns null.
+ ///
+ public static string? GetString(IntPtr ptr, CharSet charSet = CharSet.Auto, long allocatedBytes = long.MaxValue)
+ {
+ if (IsValue(ptr)) return null;
+ var sb = new StringBuilder();
+ unsafe
+ {
+ var chkLen = 0L;
+ if (GetCharSize(charSet) == 1)
+ {
+ for (var uptr = (byte*)ptr; chkLen < allocatedBytes && *uptr != 0; chkLen++, uptr++)
+ sb.Append((char)*uptr);
+ }
+ else
+ {
+ for (var uptr = (ushort*)ptr; chkLen + 2 <= allocatedBytes && *uptr != 0; chkLen += 2, uptr++)
+ sb.Append((char)*uptr);
+ }
+ }
+ return sb.ToString();
+ }
+
+ ///
+ /// Allocates a managed String and copies all characters up to the first null character or at most characters from a string stored in unmanaged memory into it.
+ ///
+ /// The address of the first character.
+ /// The number of characters to copy.
+ /// The character set of the string.
+ ///
+ /// A managed string that holds a copy of the unmanaged string if the value of the parameter is not null;
+ /// otherwise, this method returns null.
+ ///
+ public static string? GetString(IntPtr ptr, int length, CharSet charSet = CharSet.Auto) => GetString(ptr, charSet, length * GetCharSize(charSet));
+
+ /// Indicates whether a specified string is , empty, or consists only of white-space characters.
+ /// The string to test.
+ ///
+ /// if the parameter is or , or if
+ /// value consists exclusively of white-space characters.
+ ///
+ public static bool IsNullOrWhiteSpace(string? value) => value is null || value.All(c => char.IsWhiteSpace(c));
+
+ /// Refreshes the memory block from the unmanaged COM task allocator and copies the contents of a new managed String.
+ /// The address of the first character.
+ /// Receives the new character length of the allocated memory block.
+ /// A managed string to be copied.
+ /// The character set of the string.
+ /// true if the memory block was reallocated; false if set to null.
+ public static bool RefreshString(ref IntPtr ptr, out uint charLen, string? s, CharSet charSet = CharSet.Auto)
+ {
+ FreeString(ptr, charSet);
+ ptr = AllocString(s, charSet);
+ charLen = s == null ? 0U : (uint)s.Length + 1;
+ return s != null;
+ }
+
+ /// Writes the specified string to a pointer to allocated memory.
+ /// The string value.
+ /// The pointer to the allocated memory.
+ /// The resulting number of bytes written.
+ /// if set to true include a null terminator at the end of the string in the count if does not equal null.
+ /// The character set of the string.
+ /// If known, the total number of bytes allocated to the native memory in .
+ public static void Write(string? value, IntPtr ptr, out int byteCnt, bool nullTerm = true, CharSet charSet = CharSet.Auto, long allocatedBytes = long.MaxValue)
+ {
+ if (value is null)
+ {
+ byteCnt = 0;
+ return;
+ }
+ if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr));
+ var bytes = GetBytes(value, nullTerm, charSet);
+ if (bytes.Length > allocatedBytes)
+ throw new ArgumentOutOfRangeException(nameof(allocatedBytes));
+ byteCnt = bytes.Length;
+ Marshal.Copy(bytes, 0, ptr, byteCnt);
+ }
+
+ private static bool IsValue(IntPtr ptr) => ptr.ToInt64() >> 16 == 0;
+}
+
+///
+/// An error code returned by the CommDlgExtendedError function.
+///
+///
+///
+///
+/// Error code
+/// Meaning
+///
+/// -
+/// CDERR
+/// General error codes that can be returned for any of the common dialog box functions.
+///
+/// -
+/// PDERR
+/// Error codes returned for the PrintDlg function.
+///
+/// -
+///
+/// -
+/// CFERR
+/// Error codes returned for the ChooseFont function.
+///
+/// -
+/// FNERR
+/// Error codes returned for the GetOpenFileName and GetSaveFileName functions.
+///
+/// -
+/// FRERR
+/// Error codes returned for the FindText and ReplaceText functions.
+///
+///
+///
+file enum ERR : uint
+{
+ ///
+ /// The dialog box could not be created. The common dialog box function's call to the DialogBox function failed. For example,
+ /// this error occurs if the common dialog box call specifies an invalid window handle.
+ ///
+ CDERR_DIALOGFAILURE = 0xFFFF,
+
+ ///
+ /// The common dialog box function failed to find a specified resource.
+ ///
+ CDERR_FINDRESFAILURE = 0x0006,
+
+ ///
+ /// The common dialog box function failed during initialization. This error often occurs when sufficient memory is not available.
+ ///
+ CDERR_INITIALIZATION = 0x0002,
+
+ ///
+ /// The common dialog box function failed to load a specified resource.
+ ///
+ CDERR_LOADRESFAILURE = 0x0007,
+
+ ///
+ /// The common dialog box function failed to load a specified string.
+ ///
+ CDERR_LOADSTRFAILURE = 0x0005,
+
+ ///
+ /// The common dialog box function failed to lock a specified resource.
+ ///
+ CDERR_LOCKRESFAILURE = 0x0008,
+
+ ///
+ /// The common dialog box function was unable to allocate memory for internal structures.
+ ///
+ CDERR_MEMALLOCFAILURE = 0x0009,
+
+ ///
+ /// The common dialog box function was unable to lock the memory associated with a handle.
+ ///
+ CDERR_MEMLOCKFAILURE = 0x000A,
+
+ ///
+ /// The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common
+ /// dialog box, but you failed to provide a corresponding instance handle.
+ ///
+ CDERR_NOHINSTANCE = 0x0004,
+
+ ///
+ /// The ENABLEHOOK flag was set in the Flags member of the initialization structure for the corresponding common
+ /// dialog box, but you failed to provide a pointer to a corresponding hook procedure.
+ ///
+ CDERR_NOHOOK = 0x000B,
+
+ ///
+ /// The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common dialog
+ /// box, but you failed to provide a corresponding template.
+ ///
+ CDERR_NOTEMPLATE = 0x0003,
+
+ ///
+ /// The RegisterWindowMessage function returned an error code when it was called by the common dialog box function.
+ ///
+ CDERR_REGISTERMSGFAIL = 0x000C,
+
+ ///
+ /// The lStructSize member of the initialization structure for the corresponding common dialog box is invalid.
+ ///
+ CDERR_STRUCTSIZE = 0x0001,
+
+ ///
+ /// The PrintDlg function failed when it attempted to create an information context.
+ ///
+ PDERR_CREATEICFAILURE = 0x100A,
+
+ ///
+ /// You called the PrintDlg function with the DN_DEFAULTPRN flag specified in the wDefault member of the DEVNAMES structure,
+ /// but the printer described by the other structure members did not match the current default printer. This error occurs when
+ /// you store the DEVNAMES structure, and the user changes the default printer by using the Control Panel.
+ /// To use the printer described by the DEVNAMES structure, clear the DN_DEFAULTPRN flag and call PrintDlg again.
+ /// To use the default printer, replace the DEVNAMES structure (and the structure, if one exists) with NULL; and call PrintDlg again.
+ ///
+ PDERR_DEFAULTDIFFERENT = 0x100C,
+
+ ///
+ /// The data in the DEVMODE and DEVNAMES structures describes two different printers.
+ ///
+ PDERR_DNDMMISMATCH = 0x1009,
+
+ ///
+ /// The printer driver failed to initialize a DEVMODE structure.
+ ///
+ PDERR_GETDEVMODEFAIL = 0x1005,
+
+ ///
+ /// The PrintDlg function failed during initialization, and there is no more specific extended error code to describe the failure.
+ /// This is the generic default error code for the function.
+ ///
+ PDERR_INITFAILURE = 0x1006,
+
+ ///
+ /// The PrintDlg function failed to load the device driver for the specified printer.
+ ///
+ PDERR_LOADDRVFAILURE = 0x1004,
+
+ ///
+ /// A default printer does not exist.
+ ///
+ PDERR_NODEFAULTPRN = 0x1008,
+
+ ///
+ /// No printer drivers were found.
+ ///
+ PDERR_NODEVICES = 0x1007,
+
+ ///
+ /// The PrintDlg function failed to parse the strings in the [devices] section of the WIN.INI file.
+ ///
+ PDERR_PARSEFAILURE = 0x1002,
+
+ ///
+ /// The [devices] section of the WIN.INI file did not contain an entry for the requested printer.
+ ///
+ PDERR_PRINTERNOTFOUND = 0x100B,
+
+ ///
+ /// The PD_RETURNDEFAULT flag was specified in the Flags member of the PRINTDLG structure, but the hDevMode or hDevNames member was not NULL.
+ ///
+ PDERR_RETDEFFAILURE = 0x1003,
+
+ ///
+ /// The PrintDlg function failed to load the required resources.
+ ///
+ PDERR_SETUPFAILURE = 0x1001,
+
+ ///
+ /// The size specified in the nSizeMax member of the CHOOSEFONT structure is less than the size specified in the nSizeMin member.
+ ///
+ CFERR_MAXLESSTHANMIN = 0x2002,
+
+ ///
+ /// No fonts exist.
+ ///
+ CFERR_NOFONTS = 0x2001,
+
+ ///
+ /// The buffer pointed to by the lpstrFile member of the OPENFILENAME structure is too small for the file name specified
+ /// by the user. The first two bytes of the lpstrFile buffer contain an integer value specifying the size required to receive
+ /// the full name, in characters.
+ ///
+ FNERR_BUFFERTOOSMALL = 0x3003,
+
+ ///
+ /// A file name is invalid.
+ ///
+ FNERR_INVALIDFILENAME = 0x3002,
+
+ ///
+ /// An attempt to subclass a list box failed because sufficient memory was not available.
+ ///
+ FNERR_SUBCLASSFAILURE = 0x3001,
+
+ ///
+ /// A member of the FINDREPLACE structure points to an invalid buffer.
+ ///
+ FRERR_BUFFERLENGTHZERO = 0x4001,
+}
+
+///
+/// A set of bit flags you can use to initialize the dialog box. When the dialog box returns, it sets these flags to indicate the
+/// user's input.
+///
+[Flags]
+file enum OFN
+{
+ ///
+ /// The File Name list box allows multiple selections. If you also set the OFN_EXPLORER flag, the dialog box uses the
+ /// Explorer-style user interface; otherwise, it uses the old-style user interface.
+ ///
+ /// If the user selects more than one file, the lpstrFile buffer returns the path to the current directory followed by the file
+ /// names of the selected files. The nFileOffset member is the offset, in bytes or characters, to the first file name, and the
+ /// nFileExtension member is not used. For Explorer-style dialog boxes, the directory and file name strings are NULL separated,
+ /// with an extra NULL character after the last file name. This format enables the Explorer-style dialog boxes to return long
+ /// file names that include spaces. For old-style dialog boxes, the directory and file name strings are separated by spaces and
+ /// the function uses short file names for file names with spaces. You can use the FindFirstFile function to convert between
+ /// long and short file names.
+ ///
+ ///
+ /// If you specify a custom template for an old-style dialog box, the definition of the File Name list box must contain the
+ /// LBS_EXTENDEDSEL value.
+ ///
+ ///
+ OFN_ALLOWMULTISELECT = 0x00000200,
+
+ ///
+ /// If the user specifies a file that does not exist, this flag causes the dialog box to prompt the user for permission to
+ /// create the file. If the user chooses to create the file, the dialog box closes and the function returns the specified name;
+ /// otherwise, the dialog box remains open. If you use this flag with the OFN_ALLOWMULTISELECT flag, the dialog box allows the
+ /// user to specify only one nonexistent file.
+ ///
+ OFN_CREATEPROMPT = 0x00002000,
+
+ ///
+ /// Prevents the system from adding a link to the selected file in the file system directory that contains the user's most
+ /// recently used documents. To retrieve the location of this directory, call the SHGetSpecialFolderLocation function with the
+ /// CSIDL_RECENT flag.
+ ///
+ OFN_DONTADDTORECENT = 0x02000000,
+
+ /// Enables the hook function specified in the lpfnHook member.
+ OFN_ENABLEHOOK = 0x00000020,
+
+ ///
+ /// Causes the dialog box to send CDN_INCLUDEITEM notification messages to your OFNHookProc hook procedure when the user opens a
+ /// folder. The dialog box sends a notification for each item in the newly opened folder. These messages enable you to control
+ /// which items the dialog box displays in the folder's item list.
+ ///
+ OFN_ENABLEINCLUDENOTIFY = 0x00400000,
+
+ ///
+ /// Enables the Explorer-style dialog box to be resized using either the mouse or the keyboard. By default, the Explorer-style
+ /// Open and Save As dialog boxes allow the dialog box to be resized regardless of whether this flag is set. This flag is
+ /// necessary only if you provide a hook procedure or custom template. The old-style dialog box does not permit resizing.
+ ///
+ OFN_ENABLESIZING = 0x00800000,
+
+ ///
+ /// The lpTemplateName member is a pointer to the name of a dialog template resource in the module identified by the hInstance
+ /// member. If the OFN_EXPLORER flag is set, the system uses the specified template to create a dialog box that is a child of
+ /// the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses the template to create an
+ /// old-style dialog box that replaces the default dialog box.
+ ///
+ OFN_ENABLETEMPLATE = 0x00000040,
+
+ ///
+ /// The hInstance member identifies a data block that contains a preloaded dialog box template. The system ignores
+ /// lpTemplateName if this flag is specified. If the OFN_EXPLORER flag is set, the system uses the specified template to create
+ /// a dialog box that is a child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses
+ /// the template to create an old-style dialog box that replaces the default dialog box.
+ ///
+ OFN_ENABLETEMPLATEHANDLE = 0x00000080,
+
+ ///
+ /// Indicates that any customizations made to the Open or Save As dialog box use the Explorer-style customization methods. For
+ /// more information, see Explorer-Style Hook Procedures and Explorer-Style Custom Templates.
+ ///
+ /// By default, the Open and Save As dialog boxes use the Explorer-style user interface regardless of whether this flag is set.
+ /// This flag is necessary only if you provide a hook procedure or custom template, or set the OFN_ALLOWMULTISELECT flag.
+ ///
+ ///
+ /// If you want the old-style user interface, omit the OFN_EXPLORER flag and provide a replacement old-style template or hook
+ /// procedure. If you want the old style but do not need a custom template or hook procedure, simply provide a hook procedure
+ /// that always returns FALSE.
+ ///
+ ///
+ OFN_EXPLORER = 0x00080000,
+
+ ///
+ /// The user typed a file name extension that differs from the extension specified by lpstrDefExt. The function does not use
+ /// this flag if lpstrDefExt is NULL.
+ ///
+ OFN_EXTENSIONDIFFERENT = 0x00000400,
+
+ ///
+ /// The user can type only names of existing files in the File Name entry field. If this flag is specified and the user enters
+ /// an invalid name, the dialog box procedure displays a warning in a message box. If this flag is specified, the
+ /// OFN_PATHMUSTEXIST flag is also used. This flag can be used in an Open dialog box. It cannot be used with a Save As dialog box.
+ ///
+ OFN_FILEMUSTEXIST = 0x00001000,
+
+ ///
+ /// Forces the showing of system and hidden files, thus overriding the user setting to show or not show hidden files. However, a
+ /// file that is marked both system and hidden is not shown.
+ ///
+ OFN_FORCESHOWHIDDEN = 0x10000000,
+
+ /// Hides the Read Only check box.
+ OFN_HIDEREADONLY = 0x00000004,
+
+ ///
+ /// For old-style dialog boxes, this flag causes the dialog box to use long file names. If this flag is not specified, or if the
+ /// OFN_ALLOWMULTISELECT flag is also set, old-style dialog boxes use short file names (8.3 format) for file names with spaces.
+ /// Explorer-style dialog boxes ignore this flag and always display long file names.
+ ///
+ OFN_LONGNAMES = 0x00200000,
+
+ ///
+ /// Restores the current directory to its original value if the user changed the directory while searching for files.
+ /// This flag is ineffective for GetOpenFileName.
+ ///
+ OFN_NOCHANGEDIR = 0x00000008,
+
+ ///
+ /// Directs the dialog box to return the path and file name of the selected shortcut (.LNK) file. If this value is not
+ /// specified, the dialog box returns the path and file name of the file referenced by the shortcut.
+ ///
+ OFN_NODEREFERENCELINKS = 0x00100000,
+
+ ///
+ /// For old-style dialog boxes, this flag causes the dialog box to use short file names (8.3 format). Explorer-style dialog
+ /// boxes ignore this flag and always display long file names.
+ ///
+ OFN_NOLONGNAMES = 0x00040000,
+
+ /// Hides and disables the Network button.
+ OFN_NONETWORKBUTTON = 0x00020000,
+
+ /// The returned file does not have the Read Only check box selected and is not in a write-protected directory.
+ OFN_NOREADONLYRETURN = 0x00008000,
+
+ ///
+ /// The file is not created before the dialog box is closed. This flag should be specified if the application saves the file on
+ /// a create-nonmodify network share. When an application specifies this flag, the library does not check for write protection,
+ /// a full disk, an open drive door, or network protection. Applications using this flag must perform file operations carefully,
+ /// because a file cannot be reopened once it is closed.
+ ///
+ OFN_NOTESTFILECREATE = 0x00010000,
+
+ ///
+ /// The common dialog boxes allow invalid characters in the returned file name. Typically, the calling application uses a hook
+ /// procedure that checks the file name by using the FILEOKSTRING message. If the text box in the edit control is empty or
+ /// contains nothing but spaces, the lists of files and directories are updated. If the text box in the edit control contains
+ /// anything else, nFileOffset and nFileExtension are set to values generated by parsing the text. No default extension is added
+ /// to the text, nor is text copied to the buffer specified by lpstrFileTitle. If the value specified by nFileOffset is less
+ /// than zero, the file name is invalid. Otherwise, the file name is valid, and nFileExtension and nFileOffset can be used as if
+ /// the OFN_NOVALIDATE flag had not been specified.
+ ///
+ OFN_NOVALIDATE = 0x00000100,
+
+ ///
+ /// Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm whether
+ /// to overwrite the file.
+ ///
+ OFN_OVERWRITEPROMPT = 0x00000002,
+
+ ///
+ /// The user can type only valid paths and file names. If this flag is used and the user types an invalid path and file name in
+ /// the File Name entry field, the dialog box function displays a warning in a message box.
+ ///
+ OFN_PATHMUSTEXIST = 0x00000800,
+
+ ///
+ /// Causes the Read Only check box to be selected initially when the dialog box is created. This flag indicates the state of the
+ /// Read Only check box when the dialog box is closed.
+ ///
+ OFN_READONLY = 0x00000001,
+
+ ///
+ /// Specifies that if a call to the OpenFile function fails because of a network sharing violation, the error is ignored and the
+ /// dialog box returns the selected file name. If this flag is not set, the dialog box notifies your hook procedure when a
+ /// network sharing violation occurs for the file name specified by the user. If you set the OFN_EXPLORER flag, the dialog box
+ /// sends the CDN_SHAREVIOLATION message to the hook procedure. If you do not set OFN_EXPLORER, the dialog box sends the
+ /// SHAREVISTRING registered message to the hook procedure.
+ ///
+ OFN_SHAREAWARE = 0x00004000,
+
+ ///
+ /// Causes the dialog box to display the Help button. The hwndOwner member must specify the window to receive the HELPMSGSTRING
+ /// registered messages that the dialog box sends when the user clicks the Help button. An Explorer-style dialog box sends a
+ /// CDN_HELP notification message to your hook procedure when the user clicks the Help button.
+ ///
+ OFN_SHOWHELP = 0x00000010,
+}
+
+/// A set of bit flags you can use to initialize the dialog box.
+[Flags]
+file enum OFN_EX
+{
+ ///
+ /// If this flag is set, the places bar is not displayed. If this flag is not set, Explorer-style dialog boxes include a places
+ /// bar containing icons for commonly-used folders, such as Favorites and Desktop.
+ ///
+ OFN_EX_NOPLACESBAR = 0x00000001,
+}
+
+///
+///
+/// [Starting with Windows Vista, the Open and Save As common dialog boxes have been superseded by the Common Item
+/// Dialog. We recommended that you use the Common Item Dialog API instead of these dialog boxes from the Common Dialog Box Library.]
+///
+///
+/// Receives notification messages sent from the dialog box. The function also receives messages for any additional controls that
+/// you defined by specifying a child dialog template. The OFNHookProc hook procedure is an application-defined or library-defined
+/// callback function that is used with the Explorer-style Open and Save As dialog boxes.
+///
+///
+/// The LPOFNHOOKPROC type defines a pointer to this callback function. OFNHookProc is a placeholder for the
+/// application-defined function name.
+///
+///
+///
+/// A handle to the child dialog box of the Open or Save As dialog box. Use the GetParent function to get the handle
+/// to the Open or Save As dialog box.
+///
+/// The identifier of the message being received.
+/// Additional information about the message. The exact meaning depends on the value of the Arg2 parameter.
+///
+/// Additional information about the message. The exact meaning depends on the value of the Arg2 parameter. If the Arg2 parameter
+/// indicates the WM_INITDIALOG message, Arg4 is a pointer to an OPENFILENAME structure containing the values specified when the
+/// dialog box was created.
+///
+///
+/// If the hook procedure returns zero, the default dialog box procedure processes the message.
+/// If the hook procedure returns a nonzero value, the default dialog box procedure ignores the message.
+///
+/// For the CDN_SHAREVIOLATION and CDN_FILEOK notification messages, the hook procedure should return a nonzero value to indicate
+/// that it has used the SetWindowLong function to set a nonzero DWL_MSGRESULT value.
+///
+///
+///
+///
+/// If you do not specify the OFN_EXPLORER flag when you create an Open or Save As dialog box, and you want a
+/// hook procedure, you must use an old-style OFNHookProcOldStyle hook procedure. In this case, the dialog box will have the
+/// old-style user interface.
+///
+///
+/// When you use the GetOpenFileName or GetSaveFileName functions to create an Explorer-style Open or Save As dialog
+/// box, you can provide an OFNHookProc hook procedure. To enable the hook procedure, use the OPENFILENAME structure that you passed
+/// to the dialog creation function. Specify the pointer to the hook procedure in the lpfnHook member and specify the
+/// OFN_ENABLEHOOK flag in the Flags member.
+///
+///
+/// If you provide a hook procedure for an Explorer-style common dialog box, the system creates a dialog box that is a child of the
+/// default dialog box. The hook procedure acts as the dialog procedure for the child dialog. This child dialog is based on the
+/// template you specified in the OPENFILENAME structure, or it is a default child dialog if no template is specified. The child
+/// dialog is created when the default dialog procedure is processing its WM_INITDIALOG message. After the child dialog processes
+/// its own WM_INITDIALOG message, the default dialog procedure moves the standard controls, if necessary, to make room for
+/// any additional controls of the child dialog. The system then sends the CDN_INITDONE notification message to the hook procedure.
+///
+///
+/// The hook procedure does not receive messages intended for the standard controls of the default dialog box. You can subclass the
+/// standard controls, but this is discouraged because it may make your application incompatible with later versions. However, the
+/// Explorer-style common dialog boxes provide a set of messages that the hook procedure can use to monitor and control the dialog.
+/// These include a set of notification messages sent from the dialog, as well as messages that you can send to retrieve information
+/// from the dialog. For a complete list of these messages, see Explorer-Style Hook Procedures.
+///
+///
+/// If the hook procedure processes the WM_CTLCOLORDLG message, it must return a valid brush handle to painting the background of
+/// the dialog box. In general, if it processes any WM_CTLCOLOR* message, it must return a valid brush handle to painting the
+/// background of the specified control.
+///
+///
+/// Do not call the EndDialog function from the hook procedure. Instead, the hook procedure can call the PostMessage function to
+/// post a WM_COMMAND message with the IDCANCEL value to the dialog box procedure. Posting IDCANCEL closes the dialog
+/// box and causes the dialog box function to return FALSE. If you need to know why the hook procedure closed the dialog box,
+/// you must provide your own communication mechanism between the hook procedure and your application.
+///
+///
+// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpofnhookproc LPOFNHOOKPROC Lpofnhookproc; UINT_PTR
+// Lpofnhookproc( HWND Arg1, UINT Arg2, WPARAM Arg3, LPARAM Arg4 ) {...}
+[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+file delegate IntPtr LPOFNHOOKPROC(IntPtr Arg1, uint Arg2, IntPtr Arg3, IntPtr Arg4);
+
+///
+///
+/// [Starting with Windows Vista, the Open and Save As common dialog boxes have been superseded by the Common Item
+/// Dialog. We recommended that you use the Common Item Dialog API instead of these dialog boxes from the Common Dialog Box Library.]
+///
+///
+/// Contains information that the GetOpenFileName and GetSaveFileName functions use to initialize an Open or Save As
+/// dialog box. After the user closes the dialog box, the system returns information about the user's selection in this structure.
+///
+///
+///
+/// For compatibility reasons, the Places Bar is hidden if Flags is set to OFN_ENABLEHOOK and lStructSize is OPENFILENAME_SIZE_VERSION_400.
+///
+// https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea typedef struct tagOFNA { DWORD lStructSize;
+// HWND hwndOwner; HINSTANCE hInstance; LPCSTR lpstrFilter; LPSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPSTR
+// lpstrFile; DWORD nMaxFile; LPSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCSTR lpstrInitialDir; LPCSTR lpstrTitle; DWORD Flags;
+// WORD nFileOffset; WORD nFileExtension; LPCSTR lpstrDefExt; LPARAM lCustData; LPOFNHOOKPROC lpfnHook; LPCSTR lpTemplateName;
+// LPEDITMENU lpEditInfo; LPCSTR lpstrPrompt; void *pvReserved; DWORD dwReserved; DWORD FlagsEx; } OPENFILENAMEA, *LPOPENFILENAMEA;
+[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+file struct OPENFILENAME
+{
+ ///
+ /// Type: DWORD
+ /// The length, in bytes, of the structure. Use
+ /// sizeof (OPENFILENAME)
+ /// for this parameter.
+ ///
+ ///
+ public uint lStructSize;
+
+ ///
+ /// Type: HWND
+ ///
+ /// A handle to the window that owns the dialog box. This member can be any valid window handle, or it can be NULL if the
+ /// dialog box has no owner.
+ ///
+ ///
+ public IntPtr hwndOwner; // TODO: HWND
+
+ ///
+ /// Type: HINSTANCE
+ ///
+ /// If the OFN_ENABLETEMPLATEHANDLE flag is set in the Flags member, hInstance is a handle to a memory
+ /// object containing a dialog box template. If the OFN_ENABLETEMPLATE flag is set, hInstance is a handle to a
+ /// module that contains a dialog box template named by the lpTemplateName member. If neither flag is set, this member is
+ /// ignored. If the OFN_EXPLORER flag is set, the system uses the specified template to create a dialog box that is a
+ /// child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses the template to
+ /// create an old-style dialog box that replaces the default dialog box.
+ ///
+ ///
+ public IntPtr hInstance; // TODO: HINSTANCE
+
+ ///
+ /// Type: LPCTSTR
+ ///
+ /// A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two
+ /// NULL characters.
+ ///
+ ///
+ /// The first string in each pair is a display string that describes the filter (for example, "Text Files"), and the second
+ /// string specifies the filter pattern (for example, ".TXT"). To specify multiple filter patterns for a single display string,
+ /// use a semicolon to separate the patterns (for example, ".TXT;.DOC;.BAK"). A pattern string can be a combination of valid
+ /// file name characters and the asterisk (*) wildcard character. Do not include spaces in the pattern string.
+ ///
+ ///
+ /// The system does not change the order of the filters. It displays them in the File Types combo box in the order
+ /// specified in lpstrFilter.
+ ///
+ /// If lpstrFilter is NULL, the dialog box does not display any filters.
+ ///
+ /// In the case of a shortcut, if no filter is set, GetOpenFileName and GetSaveFileName retrieve the name of the .lnk file, not
+ /// its target. This behavior is the same as setting the OFN_NODEREFERENCELINKS flag in the Flags member. To
+ /// retrieve a shortcut's target without filtering, use the string
+ /// "All Files\0*.*\0\0"
+ /// .
+ ///
+ ///
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string lpstrFilter;
+
+ ///
+ /// Type: LPTSTR
+ ///
+ /// A static buffer that contains a pair of null-terminated filter strings for preserving the filter pattern chosen by the user.
+ /// The first string is your display string that describes the custom filter, and the second string is the filter pattern
+ /// selected by the user. The first time your application creates the dialog box, you specify the first string, which can be any
+ /// nonempty string. When the user selects a file, the dialog box copies the current filter pattern to the second string. The
+ /// preserved filter pattern can be one of the patterns specified in the lpstrFilter buffer, or it can be a filter
+ /// pattern typed by the user. The system uses the strings to initialize the user-defined file filter the next time the dialog
+ /// box is created. If the nFilterIndex member is zero, the dialog box uses the custom filter.
+ ///
+ /// If this member is NULL, the dialog box does not preserve user-defined filter patterns.
+ ///
+ /// If this member is not NULL, the value of the nMaxCustFilter member must specify the size, in characters, of
+ /// the lpstrCustomFilter buffer.
+ ///
+ ///
+ public StrPtrAuto lpstrCustomFilter;
+
+ ///
+ /// Type: DWORD
+ ///
+ /// The size, in characters, of the buffer identified by lpstrCustomFilter. This buffer should be at least 40 characters
+ /// long. This member is ignored if lpstrCustomFilter is NULL or points to a NULL string.
+ ///
+ ///
+ public uint nMaxCustFilter;
+
+ ///
+ /// Type: DWORD
+ ///
+ /// The index of the currently selected filter in the File Types control. The buffer pointed to by lpstrFilter
+ /// contains pairs of strings that define the filters. The first pair of strings has an index value of 1, the second pair 2, and
+ /// so on. An index of zero indicates the custom filter specified by lpstrCustomFilter. You can specify an index on input
+ /// to indicate the initial filter description and filter pattern for the dialog box. When the user selects a file,
+ /// nFilterIndex returns the index of the currently displayed filter. If nFilterIndex is zero and
+ /// lpstrCustomFilter is NULL, the system uses the first filter in the lpstrFilter buffer. If all three
+ /// members are zero or NULL, the system does not use any filters and does not show any files in the file list control of
+ /// the dialog box.
+ ///
+ ///
+ public uint nFilterIndex;
+
+ ///
+ /// Type: LPTSTR
+ ///
+ /// The file name used to initialize the File Name edit control. The first character of this buffer must be NULL
+ /// if initialization is not necessary. When the GetOpenFileName or GetSaveFileName function returns successfully, this buffer
+ /// contains the drive designator, path, file name, and extension of the selected file.
+ ///
+ ///
+ /// If the OFN_ALLOWMULTISELECT flag is set and the user selects multiple files, the buffer contains the current
+ /// directory followed by the file names of the selected files. For Explorer-style dialog boxes, the directory and file name
+ /// strings are NULL separated, with an extra NULL character after the last file name. For old-style dialog boxes,
+ /// the strings are space separated and the function uses short file names for file names with spaces. You can use the
+ /// FindFirstFile function to convert between long and short file names. If the user selects only one file, the lpstrFile
+ /// string does not have a separator between the path and file name.
+ ///
+ ///
+ /// If the buffer is too small, the function returns FALSE and the CommDlgExtendedError function returns
+ /// FNERR_BUFFERTOOSMALL. In this case, the first two bytes of the lpstrFile buffer contain the required size, in
+ /// bytes or characters.
+ ///
+ ///
+ public StrPtrAuto lpstrFile;
+
+ ///
+ /// Type: DWORD
+ ///
+ /// The size, in characters, of the buffer pointed to by lpstrFile. The buffer must be large enough to store the path and
+ /// file name string or strings, including the terminating NULL character. The GetOpenFileName and GetSaveFileName
+ /// functions return FALSE if the buffer is too small to contain the file information. The buffer should be at least 256
+ /// characters long.
+ ///
+ ///
+ public uint nMaxFile;
+
+ ///
+ /// Type: LPTSTR
+ /// The file name and extension (without path information) of the selected file. This member can be NULL.
+ ///
+ public StrPtrAuto lpstrFileTitle;
+
+ ///
+ /// Type: DWORD
+ ///
+ /// The size, in characters, of the buffer pointed to by lpstrFileTitle. This member is ignored if lpstrFileTitle
+ /// is NULL.
+ ///
+ ///
+ public uint nMaxFileTitle;
+
+ ///
+ /// Type: LPCTSTR
+ /// The initial directory. The algorithm for selecting the initial directory varies on different platforms.
+ /// Windows 7:
+ ///
+ /// -
+ ///
+ /// If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save
+ /// As dialog box, the path most recently selected by the user is used as the initial directory.
+ ///
+ ///
+ /// -
+ /// Otherwise, if lpstrFile contains a path, that path is the initial directory.
+ ///
+ /// -
+ /// Otherwise, if lpstrInitialDir is not NULL, it specifies the initial directory.
+ ///
+ /// -
+ ///
+ /// If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the
+ /// initial directory is the current directory.
+ ///
+ ///
+ /// -
+ /// Otherwise, the initial directory is the personal files directory of the current user.
+ ///
+ /// -
+ /// Otherwise, the initial directory is the Desktop folder.
+ ///
+ ///
+ /// Windows 2000/XP/Vista:
+ ///
+ /// -
+ /// If lpstrFile contains a path, that path is the initial directory.
+ ///
+ /// -
+ /// Otherwise, lpstrInitialDir specifies the initial directory.
+ ///
+ /// -
+ ///
+ /// Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used
+ /// is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
+ ///
+ ///
+ /// -
+ ///
+ /// If lpstrInitialDir is NULL and the current directory contains any files of the specified filter types, the
+ /// initial directory is the current directory.
+ ///
+ ///
+ /// -
+ /// Otherwise, the initial directory is the personal files directory of the current user.
+ ///
+ /// -
+ /// Otherwise, the initial directory is the Desktop folder.
+ ///
+ ///
+ ///
+ public StrPtrAuto lpstrInitialDir;
+
+ ///
+ /// Type: LPCTSTR
+ ///
+ /// A string to be placed in the title bar of the dialog box. If this member is NULL, the system uses the default title
+ /// (that is, Save As or Open).
+ ///
+ ///
+ public StrPtrAuto lpstrTitle;
+
+ ///
+ /// Type: DWORD
+ ///
+ /// A set of bit flags you can use to initialize the dialog box. When the dialog box returns, it sets these flags to indicate
+ /// the user's input. This member can be a combination of the following flags.
+ ///
+ ///
+ ///
+ /// Value
+ /// Meaning
+ ///
+ /// -
+ /// OFN_ALLOWMULTISELECT 0x00000200
+ ///
+ /// The File Name list box allows multiple selections. If you also set the OFN_EXPLORER flag, the dialog box uses the
+ /// Explorer-style user interface; otherwise, it uses the old-style user interface. If the user selects more than one file, the
+ /// lpstrFile buffer returns the path to the current directory followed by the file names of the selected files. The nFileOffset
+ /// member is the offset, in bytes or characters, to the first file name, and the nFileExtension member is not used. For
+ /// Explorer-style dialog boxes, the directory and file name strings are NULL separated, with an extra NULL character after the
+ /// last file name. This format enables the Explorer-style dialog boxes to return long file names that include spaces. For
+ /// old-style dialog boxes, the directory and file name strings are separated by spaces and the function uses short file names
+ /// for file names with spaces. You can use the FindFirstFile function to convert between long and short file names. If you
+ /// specify a custom template for an old-style dialog box, the definition of the File Name list box must contain the
+ /// LBS_EXTENDEDSEL value.
+ ///
+ ///
+ /// -
+ /// OFN_CREATEPROMPT 0x00002000
+ ///
+ /// If the user specifies a file that does not exist, this flag causes the dialog box to prompt the user for permission to
+ /// create the file. If the user chooses to create the file, the dialog box closes and the function returns the specified name;
+ /// otherwise, the dialog box remains open. If you use this flag with the OFN_ALLOWMULTISELECT flag, the dialog box allows the
+ /// user to specify only one nonexistent file.
+ ///
+ ///
+ /// -
+ /// OFN_DONTADDTORECENT 0x02000000
+ ///
+ /// Prevents the system from adding a link to the selected file in the file system directory that contains the user's most
+ /// recently used documents. To retrieve the location of this directory, call the SHGetSpecialFolderLocation function with the
+ /// CSIDL_RECENT flag.
+ ///
+ ///
+ /// -
+ /// OFN_ENABLEHOOK 0x00000020
+ /// Enables the hook function specified in the lpfnHook member.
+ ///
+ /// -
+ /// OFN_ENABLEINCLUDENOTIFY 0x00400000
+ ///
+ /// Causes the dialog box to send CDN_INCLUDEITEM notification messages to your OFNHookProc hook procedure when the user opens a
+ /// folder. The dialog box sends a notification for each item in the newly opened folder. These messages enable you to control
+ /// which items the dialog box displays in the folder's item list.
+ ///
+ ///
+ /// -
+ /// OFN_ENABLESIZING 0x00800000
+ ///
+ /// Enables the Explorer-style dialog box to be resized using either the mouse or the keyboard. By default, the Explorer-style
+ /// Open and Save As dialog boxes allow the dialog box to be resized regardless of whether this flag is set. This flag is
+ /// necessary only if you provide a hook procedure or custom template. The old-style dialog box does not permit resizing.
+ ///
+ ///
+ /// -
+ /// OFN_ENABLETEMPLATE 0x00000040
+ ///
+ /// The lpTemplateName member is a pointer to the name of a dialog template resource in the module identified by the hInstance
+ /// member. If the OFN_EXPLORER flag is set, the system uses the specified template to create a dialog box that is a child of
+ /// the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses the template to create an
+ /// old-style dialog box that replaces the default dialog box.
+ ///
+ ///
+ /// -
+ /// OFN_ENABLETEMPLATEHANDLE 0x00000080
+ ///
+ /// The hInstance member identifies a data block that contains a preloaded dialog box template. The system ignores
+ /// lpTemplateName if this flag is specified. If the OFN_EXPLORER flag is set, the system uses the specified template to create
+ /// a dialog box that is a child of the default Explorer-style dialog box. If the OFN_EXPLORER flag is not set, the system uses
+ /// the template to create an old-style dialog box that replaces the default dialog box.
+ ///
+ ///
+ /// -
+ /// OFN_EXPLORER 0x00080000
+ ///
+ /// Indicates that any customizations made to the Open or Save As dialog box use the Explorer-style customization methods. For
+ /// more information, see Explorer-Style Hook Procedures and Explorer-Style Custom Templates. By default, the Open and Save As
+ /// dialog boxes use the Explorer-style user interface regardless of whether this flag is set. This flag is necessary only if
+ /// you provide a hook procedure or custom template, or set the OFN_ALLOWMULTISELECT flag. If you want the old-style user
+ /// interface, omit the OFN_EXPLORER flag and provide a replacement old-style template or hook procedure. If you want the old
+ /// style but do not need a custom template or hook procedure, simply provide a hook procedure that always returns FALSE.
+ ///
+ ///
+ /// -
+ /// OFN_EXTENSIONDIFFERENT 0x00000400
+ ///
+ /// The user typed a file name extension that differs from the extension specified by lpstrDefExt. The function does not use
+ /// this flag if lpstrDefExt is NULL.
+ ///
+ ///
+ /// -
+ /// OFN_FILEMUSTEXIST 0x00001000
+ ///
+ /// The user can type only names of existing files in the File Name entry field. If this flag is specified and the user enters
+ /// an invalid name, the dialog box procedure displays a warning in a message box. If this flag is specified, the
+ /// OFN_PATHMUSTEXIST flag is also used. This flag can be used in an Open dialog box. It cannot be used with a Save As dialog box.
+ ///
+ ///
+ /// -
+ /// OFN_FORCESHOWHIDDEN 0x10000000
+ ///
+ /// Forces the showing of system and hidden files, thus overriding the user setting to show or not show hidden files. However, a
+ /// file that is marked both system and hidden is not shown.
+ ///
+ ///
+ /// -
+ /// OFN_HIDEREADONLY 0x00000004
+ /// Hides the Read Only check box.
+ ///
+ /// -
+ /// OFN_LONGNAMES 0x00200000
+ ///
+ /// For old-style dialog boxes, this flag causes the dialog box to use long file names. If this flag is not specified, or if the
+ /// OFN_ALLOWMULTISELECT flag is also set, old-style dialog boxes use short file names (8.3 format) for file names with spaces.
+ /// Explorer-style dialog boxes ignore this flag and always display long file names.
+ ///
+ ///
+ /// -
+ /// OFN_NOCHANGEDIR 0x00000008
+ ///
+ /// Restores the current directory to its original value if the user changed the directory while searching for files. This flag
+ /// is ineffective for GetOpenFileName.
+ ///
+ ///
+ /// -
+ /// OFN_NODEREFERENCELINKS 0x00100000
+ ///
+ /// Directs the dialog box to return the path and file name of the selected shortcut (.LNK) file. If this value is not
+ /// specified, the dialog box returns the path and file name of the file referenced by the shortcut.
+ ///
+ ///
+ /// -
+ /// OFN_NOLONGNAMES 0x00040000
+ ///
+ /// For old-style dialog boxes, this flag causes the dialog box to use short file names (8.3 format). Explorer-style dialog
+ /// boxes ignore this flag and always display long file names.
+ ///
+ ///
+ /// -
+ /// OFN_NONETWORKBUTTON 0x00020000
+ /// Hides and disables the Network button.
+ ///
+ /// -
+ /// OFN_NOREADONLYRETURN 0x00008000
+ /// The returned file does not have the Read Only check box selected and is not in a write-protected directory.
+ ///
+ /// -
+ /// OFN_NOTESTFILECREATE 0x00010000
+ ///
+ /// The file is not created before the dialog box is closed. This flag should be specified if the application saves the file on
+ /// a create-nonmodify network share. When an application specifies this flag, the library does not check for write protection,
+ /// a full disk, an open drive door, or network protection. Applications using this flag must perform file operations carefully,
+ /// because a file cannot be reopened once it is closed.
+ ///
+ ///
+ /// -
+ /// OFN_NOVALIDATE 0x00000100
+ ///
+ /// The common dialog boxes allow invalid characters in the returned file name. Typically, the calling application uses a hook
+ /// procedure that checks the file name by using the FILEOKSTRING message. If the text box in the edit control is empty or
+ /// contains nothing but spaces, the lists of files and directories are updated. If the text box in the edit control contains
+ /// anything else, nFileOffset and nFileExtension are set to values generated by parsing the text. No default extension is added
+ /// to the text, nor is text copied to the buffer specified by lpstrFileTitle. If the value specified by nFileOffset is less
+ /// than zero, the file name is invalid. Otherwise, the file name is valid, and nFileExtension and nFileOffset can be used as if
+ /// the OFN_NOVALIDATE flag had not been specified.
+ ///
+ ///
+ /// -
+ /// OFN_OVERWRITEPROMPT 0x00000002
+ ///
+ /// Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm whether
+ /// to overwrite the file.
+ ///
+ ///
+ /// -
+ /// OFN_PATHMUSTEXIST 0x00000800
+ ///
+ /// The user can type only valid paths and file names. If this flag is used and the user types an invalid path and file name in
+ /// the File Name entry field, the dialog box function displays a warning in a message box.
+ ///
+ ///
+ /// -
+ /// OFN_READONLY 0x00000001
+ ///
+ /// Causes the Read Only check box to be selected initially when the dialog box is created. This flag indicates the state of the
+ /// Read Only check box when the dialog box is closed.
+ ///
+ ///
+ /// -
+ /// OFN_SHAREAWARE 0x00004000
+ ///
+ /// Specifies that if a call to the OpenFile function fails because of a network sharing violation, the error is ignored and the
+ /// dialog box returns the selected file name. If this flag is not set, the dialog box notifies your hook procedure when a
+ /// network sharing violation occurs for the file name specified by the user. If you set the OFN_EXPLORER flag, the dialog box
+ /// sends the CDN_SHAREVIOLATION message to the hook procedure. If you do not set OFN_EXPLORER, the dialog box sends the
+ /// SHAREVISTRING registered message to the hook procedure.
+ ///
+ ///
+ /// -
+ /// OFN_SHOWHELP 0x00000010
+ ///
+ /// Causes the dialog box to display the Help button. The hwndOwner member must specify the window to receive the HELPMSGSTRING
+ /// registered messages that the dialog box sends when the user clicks the Help button. An Explorer-style dialog box sends a
+ /// CDN_HELP notification message to your hook procedure when the user clicks the Help button.
+ ///
+ ///
+ ///
+ ///
+ public OFN Flags;
+
+ ///
+ /// Type: WORD
+ ///
+ /// The zero-based offset, in characters, from the beginning of the path to the file name in the string pointed to by
+ /// lpstrFile. For the ANSI version, this is the number of bytes; for the Unicode version, this is the number of
+ /// characters. For example, if lpstrFile points to the following string, "c:\dir1\dir2\file.ext", this member contains
+ /// the value 13 to indicate the offset of the "file.ext" string. If the user selects more than one file, nFileOffset is
+ /// the offset to the first file name.
+ ///
+ ///
+ public ushort nFileOffset;
+
+ ///
+ /// Type: WORD
+ ///
+ /// The zero-based offset, in characters, from the beginning of the path to the file name extension in the string pointed to by
+ /// lpstrFile. For the ANSI version, this is the number of bytes; for the Unicode version, this is the number of
+ /// characters. Usually the file name extension is the substring which follows the last occurrence of the dot (".") character.
+ /// For example, txt is the extension of the filename readme.txt, html the extension of readme.txt.html. Therefore, if
+ /// lpstrFile points to the string "c:\dir1\dir2\readme.txt", this member contains the value 20. If lpstrFile
+ /// points to the string "c:\dir1\dir2\readme.txt.html", this member contains the value 24. If lpstrFile points to the
+ /// string "c:\dir1\dir2\readme.txt.html.", this member contains the value 29. If lpstrFile points to a string that does
+ /// not contain any "." character such as "c:\dir1\dir2\readme", this member contains zero.
+ ///
+ ///
+ public ushort nFileExtension;
+
+ ///
+ /// Type: LPCTSTR
+ ///
+ /// The default extension. GetOpenFileName and GetSaveFileName append this extension to the file name if the user fails to type
+ /// an extension. This string can be any length, but only the first three characters are appended. The string should not contain
+ /// a period (.). If this member is NULL and the user fails to type an extension, no extension is appended.
+ ///
+ ///
+ public StrPtrAuto lpstrDefExt;
+
+ ///
+ /// Type: LPARAM
+ ///
+ /// Application-defined data that the system passes to the hook procedure identified by the lpfnHook member. When the
+ /// system sends the WM_INITDIALOG message to the hook procedure, the message's lParam parameter is a pointer to the
+ /// OPENFILENAME structure specified when the dialog box was created. The hook procedure can use this pointer to get the
+ /// lCustData value.
+ ///
+ ///
+ public IntPtr lCustData;
+
+ ///
+ /// Type: LPOFNHOOKPROC
+ ///
+ /// A pointer to a hook procedure. This member is ignored unless the Flags member includes the OFN_ENABLEHOOK flag.
+ ///
+ ///
+ /// If the OFN_EXPLORER flag is not set in the Flags member, lpfnHook is a pointer to an
+ /// OFNHookProcOldStyle hook procedure that receives messages intended for the dialog box. The hook procedure returns
+ /// FALSE to pass a message to the default dialog box procedure or TRUE to discard the message.
+ ///
+ ///
+ /// If OFN_EXPLORER is set, lpfnHook is a pointer to an OFNHookProc hook procedure. The hook procedure receives
+ /// notification messages sent from the dialog box. The hook procedure also receives messages for any additional controls that
+ /// you defined by specifying a child dialog template. The hook procedure does not receive messages intended for the standard
+ /// controls of the default dialog box.
+ ///
+ ///
+ [MarshalAs(UnmanagedType.FunctionPtr)]
+ public LPOFNHOOKPROC lpfnHook;
+
+ ///
+ /// Type: LPCTSTR
+ ///
+ /// The name of the dialog template resource in the module identified by the hInstance member. For numbered dialog box
+ /// resources, this can be a value returned by the MAKEINTRESOURCE macro. This member is ignored unless the
+ /// OFN_ENABLETEMPLATE flag is set in the Flags member. If the OFN_EXPLORER flag is set, the system uses
+ /// the specified template to create a dialog box that is a child of the default Explorer-style dialog box. If the
+ /// OFN_EXPLORER flag is not set, the system uses the template to create an old-style dialog box that replaces the
+ /// default dialog box.
+ ///
+ ///
+ [MarshalAs(UnmanagedType.LPTStr)]
+ public string lpTemplateName;
+
+ ///
+ /// Type: void*
+ /// This member is reserved.
+ ///
+ private IntPtr pvReserved;
+
+ ///
+ /// Type: DWORD
+ /// This member is reserved.
+ ///
+ private uint dwReserved;
+
+ ///
+ /// Type: DWORD
+ /// A set of bit flags you can use to initialize the dialog box. Currently, this member can be zero or the following flag.
+ ///
+ ///
+ /// Value
+ /// Meaning
+ ///
+ /// -
+ /// OFN_EX_NOPLACESBAR 0x00000001
+ ///
+ /// If this flag is set, the places bar is not displayed. If this flag is not set, Explorer-style dialog boxes include a places
+ /// bar containing icons for commonly-used folders, such as Favorites and Desktop.
+ ///
+ ///
+ ///
+ ///
+ public OFN_EX FlagsEx;
+}
+
+file class PInvoke
+{
+ ///
+ ///
+ /// [Starting with Windows Vista, the Open and Save As common dialog boxes have been superseded by the Common Item
+ /// Dialog. We recommended that you use the Common Item Dialog API instead of these dialog boxes from the Common Dialog Box Library.]
+ ///
+ ///
+ /// Creates an Open dialog box that lets the user specify the drive, directory, and the name of a file or set of files to be opened.
+ ///
+ ///
+ ///
+ /// Type: LPOPENFILENAME
+ ///
+ /// A pointer to an OPENFILENAME structure that contains information used to initialize the dialog box. When GetOpenFileName
+ /// returns, this structure contains information about the user's file selection.
+ ///
+ ///
+ ///
+ /// Type: BOOL
+ ///
+ /// If the user specifies a file name and clicks the OK button, the return value is nonzero. The buffer pointed to by the
+ /// lpstrFile member of the OPENFILENAME structure contains the full path and file name specified by the user.
+ ///
+ ///
+ /// If the user cancels or closes the Open dialog box or an error occurs, the return value is zero. To get extended error
+ /// information, call the CommDlgExtendedError function, which can return one of the following values.
+ ///
+ ///
+ ///
+ ///
+ /// The Explorer-style Open dialog box provides user-interface features that are similar to the Windows Explorer. You can
+ /// provide an OFNHookProc hook procedure for an Explorer-style Open dialog box. To enable the hook procedure, set the
+ /// OFN_EXPLORER and OFN_ENABLEHOOK flags in the Flags member of the OPENFILENAME structure and specify the
+ /// address of the hook procedure in the lpfnHook member.
+ ///
+ ///
+ /// Windows continues to support the old-style Open dialog box for applications that want to maintain a user-interface
+ /// consistent with the old-style user-interface. To display the old-style Open dialog box, enable an OFNHookProcOldStyle
+ /// hook procedure and ensure that the OFN_EXPLORER flag is not set.
+ ///
+ /// To display a dialog box that allows the user to select a directory instead of a file, call the SHBrowseForFolder function.
+ /// Note, when selecting multiple files, the total character limit for the file names depends on the version of the function.
+ ///
+ /// -
+ /// ANSI: 32k limit
+ ///
+ /// -
+ /// Unicode: no restriction
+ ///
+ ///
+ /// Examples
+ /// For an example, see Opening a File.
+ ///
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nf-commdlg-getopenfilenamea BOOL GetOpenFileNameA( LPOPENFILENAMEA
+ // Arg1 );
+ [DllImport("comdlg32.dll", SetLastError = false, CharSet = CharSet.Auto)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GetOpenFileName(ref OPENFILENAME Arg1);
+
+ ///
+ ///
+ /// [Starting with Windows Vista, the Open and Save As common dialog boxes have been superseded by the Common Item
+ /// Dialog. We recommended that you use the Common Item Dialog API instead of these dialog boxes from the Common Dialog Box Library.]
+ ///
+ /// Creates a Save dialog box that lets the user specify the drive, directory, and name of a file to save.
+ ///
+ ///
+ /// Type: LPOPENFILENAME
+ ///
+ /// A pointer to an OPENFILENAME structure that contains information used to initialize the dialog box. When GetSaveFileName
+ /// returns, this structure contains information about the user's file selection.
+ ///
+ ///
+ ///
+ /// Type: BOOL
+ ///
+ /// If the user specifies a file name and clicks the OK button and the function is successful, the return value is nonzero.
+ /// The buffer pointed to by the lpstrFile member of the OPENFILENAME structure contains the full path and file name
+ /// specified by the user.
+ ///
+ ///
+ /// If the user cancels or closes the Save dialog box or an error such as the file name buffer being too small occurs, the
+ /// return value is zero. To get extended error information, call the CommDlgExtendedError function, which can return one of the
+ /// following values:
+ ///
+ ///
+ ///
+ ///
+ /// The Explorer-style Save dialog box that provides user-interface features that are similar to the Windows Explorer. You
+ /// can provide an OFNHookProc hook procedure for an Explorer-style Save dialog box. To enable the hook procedure, set the
+ /// OFN_EXPLORER and OFN_ENABLEHOOK flags in the Flags member of the OPENFILENAME structure and specify the
+ /// address of the hook procedure in the lpfnHook member.
+ ///
+ ///
+ /// Windows continues to support old-style Save dialog boxes for applications that want to maintain a user-interface
+ /// consistent with the old-style user-interface. To display the old-style Save dialog box, enable an OFNHookProcOldStyle
+ /// hook procedure and ensure that the OFN_EXPLORER flag is not set.
+ ///
+ /// Examples
+ /// For an example, see Creating an Enhanced Metafile.
+ ///
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nf-commdlg-getsavefilenamea BOOL GetSaveFileNameA( LPOPENFILENAMEA
+ // Arg1 );
+ [DllImport("comdlg32.dll", SetLastError = false, CharSet = CharSet.Auto)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GetSaveFileName(ref OPENFILENAME Arg1);
+
+ ///
+ /// Returns a common dialog box error code. This code indicates the most recent error to occur during the execution of one of the
+ /// common dialog box functions.
+ ///
+ ///
+ /// Type: DWORD
+ ///
+ /// If the most recent call to a common dialog box function succeeded, the return value is undefined. If the common dialog box
+ /// function returned FALSE because the user closed or canceled the dialog box, the return value is zero. Otherwise, the
+ /// return value is a nonzero error code.
+ ///
+ ///
+ /// The CommDlgExtendedError function can return general error codes for any of the common dialog box functions. In addition,
+ /// there are error codes that are returned only for a specific common dialog box. All of these error codes are defined in Cderr.h.
+ /// The following general error codes can be returned for any of the common dialog box functions.
+ ///
+ ///
+ ///
+ /// Return code/value
+ /// Description
+ ///
+ /// -
+ /// CDERR_DIALOGFAILURE 0xFFFF
+ ///
+ /// The dialog box could not be created. The common dialog box function's call to the DialogBox function failed. For example, this
+ /// error occurs if the common dialog box call specifies an invalid window handle.
+ ///
+ ///
+ /// -
+ /// CDERR_FINDRESFAILURE 0x0006
+ /// The common dialog box function failed to find a specified resource.
+ ///
+ /// -
+ /// CDERR_INITIALIZATION 0x0002
+ /// The common dialog box function failed during initialization. This error often occurs when sufficient memory is not available.
+ ///
+ /// -
+ /// CDERR_LOADRESFAILURE 0x0007
+ /// The common dialog box function failed to load a specified resource.
+ ///
+ /// -
+ /// CDERR_LOADSTRFAILURE 0x0005
+ /// The common dialog box function failed to load a specified string.
+ ///
+ /// -
+ /// CDERR_LOCKRESFAILURE 0x0008
+ /// The common dialog box function failed to lock a specified resource.
+ ///
+ /// -
+ /// CDERR_MEMALLOCFAILURE 0x0009
+ /// The common dialog box function was unable to allocate memory for internal structures.
+ ///
+ /// -
+ /// CDERR_MEMLOCKFAILURE 0x000A
+ /// The common dialog box function was unable to lock the memory associated with a handle.
+ ///
+ /// -
+ /// CDERR_NOHINSTANCE 0x0004
+ ///
+ /// The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but
+ /// you failed to provide a corresponding instance handle.
+ ///
+ ///
+ /// -
+ /// CDERR_NOHOOK 0x000B
+ ///
+ /// The ENABLEHOOK flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but you
+ /// failed to provide a pointer to a corresponding hook procedure.
+ ///
+ ///
+ /// -
+ /// CDERR_NOTEMPLATE 0x0003
+ ///
+ /// The ENABLETEMPLATE flag was set in the Flags member of the initialization structure for the corresponding common dialog box, but
+ /// you failed to provide a corresponding template.
+ ///
+ ///
+ /// -
+ /// CDERR_REGISTERMSGFAIL 0x000C
+ /// The RegisterWindowMessage function returned an error code when it was called by the common dialog box function.
+ ///
+ /// -
+ /// CDERR_STRUCTSIZE 0x0001
+ /// The lStructSize member of the initialization structure for the corresponding common dialog box is invalid.
+ ///
+ ///
+ /// The following error codes can be returned for the PrintDlg function.
+ ///
+ ///
+ /// Return code/value
+ /// Description
+ ///
+ /// -
+ /// PDERR_CREATEICFAILURE 0x100A
+ /// The PrintDlg function failed when it attempted to create an information context.
+ ///
+ /// -
+ /// PDERR_DEFAULTDIFFERENT 0x100C
+ ///
+ /// You called the PrintDlg function with the DN_DEFAULTPRN flag specified in the wDefault member of the DEVNAMES structure, but the
+ /// printer described by the other structure members did not match the current default printer. This error occurs when you store the
+ /// DEVNAMES structure, and the user changes the default printer by using the Control Panel. To use the printer described by the
+ /// DEVNAMES structure, clear the DN_DEFAULTPRN flag and call PrintDlg again. To use the default printer, replace the DEVNAMES
+ /// structure (and the structure, if one exists) with NULL; and call PrintDlg again.
+ ///
+ ///
+ /// -
+ /// PDERR_DNDMMISMATCH 0x1009
+ /// The data in the DEVMODE and DEVNAMES structures describes two different printers.
+ ///
+ /// -
+ /// PDERR_GETDEVMODEFAIL 0x1005
+ /// The printer driver failed to initialize a DEVMODE structure.
+ ///
+ /// -
+ /// PDERR_INITFAILURE 0x1006
+ ///
+ /// The PrintDlg function failed during initialization, and there is no more specific extended error code to describe the failure.
+ /// This is the generic default error code for the function.
+ ///
+ ///
+ /// -
+ /// PDERR_LOADDRVFAILURE 0x1004
+ /// The PrintDlg function failed to load the device driver for the specified printer.
+ ///
+ /// -
+ /// PDERR_NODEFAULTPRN 0x1008
+ /// A default printer does not exist.
+ ///
+ /// -
+ /// PDERR_NODEVICES 0x1007
+ /// No printer drivers were found.
+ ///
+ /// -
+ /// PDERR_PARSEFAILURE 0x1002
+ /// The PrintDlg function failed to parse the strings in the [devices] section of the WIN.INI file.
+ ///
+ /// -
+ /// PDERR_PRINTERNOTFOUND 0x100B
+ /// The [devices] section of the WIN.INI file did not contain an entry for the requested printer.
+ ///
+ /// -
+ /// PDERR_RETDEFFAILURE 0x1003
+ ///
+ /// The PD_RETURNDEFAULT flag was specified in the Flags member of the PRINTDLG structure, but the hDevMode or hDevNames member was
+ /// not NULL.
+ ///
+ ///
+ /// -
+ /// PDERR_SETUPFAILURE 0x1001
+ /// The PrintDlg function failed to load the required resources.
+ ///
+ ///
+ /// The following error codes can be returned for the ChooseFont function.
+ ///
+ ///
+ /// Return code/value
+ /// Description
+ ///
+ /// -
+ /// CFERR_MAXLESSTHANMIN CFERR_MAXLESSTHANMIN
+ ///
+ /// The size specified in the nSizeMax member of the CHOOSEFONT structure is less than the size specified in the nSizeMin member.
+ ///
+ ///
+ /// -
+ /// CFERR_NOFONTS 0x2001
+ /// No fonts exist.
+ ///
+ ///
+ /// The following error codes can be returned for the GetOpenFileName and GetSaveFileName functions.
+ ///
+ ///
+ /// Return code/value
+ /// Description
+ ///
+ /// -
+ /// FNERR_BUFFERTOOSMALL 0x3003
+ ///
+ /// The buffer pointed to by the lpstrFile member of the OPENFILENAME structure is too small for the file name specified by the
+ /// user. The first two bytes of the lpstrFile buffer contain an integer value specifying the size required to receive the full
+ /// name, in characters.
+ ///
+ ///
+ /// -
+ /// FNERR_INVALIDFILENAME 0x3002
+ /// A file name is invalid.
+ ///
+ /// -
+ /// FNERR_SUBCLASSFAILURE 0x3001
+ /// An attempt to subclass a list box failed because sufficient memory was not available.
+ ///
+ ///
+ /// The following error code can be returned for the FindText and ReplaceText functions.
+ ///
+ ///
+ /// Return code/value
+ /// Description
+ ///
+ /// -
+ /// FRERR_BUFFERLENGTHZERO 0x4001
+ /// A member of the FINDREPLACE structure points to an invalid buffer.
+ ///
+ ///
+ ///
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nf-commdlg-commdlgextendederror DWORD CommDlgExtendedError();
+ [DllImport("comdlg32.dll", SetLastError = false, ExactSpelling = true)]
+ public static extern ERR CommDlgExtendedError();
+}
+
+internal abstract class FileDialog
+{
+ public const int MAX_FILE_LENGTH = 2048;
+
+ ///
+ /// Specifies that the user can type only valid paths and file names. If this flag is
+ /// used and the user types an invalid path and file name in the File Name entry field,
+ /// a warning is displayed in a message box.
+ ///
+ public bool CheckPathExists { get; set; } = false; // OFN_PATHMUSTEXIST
+
+ ///
+ /// Gets or sets the current file name filter string,
+ /// which determines the choices that appear in the "Save as file type" or
+ /// "Files of type" box at the bottom of the dialog box.
+ ///
+ /// This is an example filter string:
+ /// Filter = "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*"
+ ///
+ ///
+ /// Thrown in the setter if the new filter string does not have an even number of tokens
+ /// separated by the vertical bar character '|' (that is, the new filter string is invalid.)
+ ///
+ ///
+ /// If DereferenceLinks is true and the filter string is null, a blank
+ /// filter string (equivalent to "|*.*") will be automatically substituted to work
+ /// around the issue documented in Knowledge Base article 831559
+ /// Callers must have FileIOPermission(PermissionState.Unrestricted) to call this API.
+ ///
+ public string? Filter { get; set; } = "All files(*.*)\0\0";
+
+ ///
+ /// Gets or sets the index of the filter currently selected in the file dialog box.
+ ///
+ /// NOTE: The index of the first filter entry is 1, not 0.
+ ///
+ public int FilterIndex { get; set; } = 1;
+
+ ///
+ /// Gets or sets the initial directory displayed by the file dialog box.
+ ///
+ public string? InitialDirectory { get; set; } = null;
+
+ ///
+ /// Gets or sets a string shown in the title bar of the file dialog.
+ /// If this property is null, a localized default from the operating
+ /// system itself will be used (typically something like "Save As" or "Open")
+ ///
+ public string? Title { get; set; } = "Open a file...";
+
+ ///
+ /// Gets or sets a value indicating whether the dialog box accepts only valid
+ /// Win32 file names.
+ ///
+ public bool ValidateNames { get; set; } = false; // OFN_NOVALIDATE
+
+ public bool ShowHidden { get; set; } = false;
+
+
+ public abstract bool ShowDialog();
+}
+
+internal class SaveFileDialog : FileDialog
+{
+ ///
+ /// Restores the current directory to its original value if the user
+ /// changed the directory while searching for files.
+ ///
+ public bool RestoreDirectory { get; set; } // OFN_NOCHANGEDIR
+
+ ///
+ /// Gets or sets a value indicating whether the dialog box prompts the user for
+ /// permission to create a file if the user specifies a file that does not exist.
+ ///
+ ///
+ /// Callers must have UIPermission.AllWindows to call this API.
+ ///
+ public bool CreatePrompt { get; set; } = false; // OFN_CREATEPROMPT
+
+ ///
+ /// Gets or sets a value indicating whether the Save As dialog box displays a
+ /// warning if the user specifies a file name that already exists.
+ ///
+ ///
+ /// Callers must have UIPermission.AllWindows to call this API.
+ ///
+ public bool OverwritePrompt { get; set; } = false; // OFN_OVERWRITEPROMPT
+
+ public string? FileName { get; set; }
+
+ public override bool ShowDialog()
+ {
+ var fileName = Marshal.ReAllocCoTaskMem(Marshal.StringToCoTaskMemUni(FileName ?? string.Empty), MAX_FILE_LENGTH);
+ //using var fileName = new SafeCoTaskMemString(FileName ?? string.Empty, MAX_FILE_LENGTH);
+ //using var fileTitle = new SafeCoTaskMemString(MAX_FILE_LENGTH);
+ var ofn = new OPENFILENAME
+ {
+ lStructSize = (uint) Marshal.SizeOf(),
+ lpstrFilter = $"{Filter?.Replace("|", "\0")}\0",
+ nFilterIndex = 1,
+ lpstrFileTitle = default,
+ nMaxFileTitle = 0,
+ lpstrInitialDir = string.IsNullOrEmpty(InitialDirectory) ? default : new StrPtrAuto(InitialDirectory!),
+ lpstrTitle = string.IsNullOrEmpty(Title) ? default : new StrPtrAuto(Title!),
+ lpstrFile = fileName,
+ nMaxFile = MAX_FILE_LENGTH,
+ Flags = OFN.OFN_EXPLORER
+ };
+
+ if (CheckPathExists)
+ ofn.Flags |= OFN.OFN_PATHMUSTEXIST;
+ if (!ValidateNames)
+ ofn.Flags |= OFN.OFN_NOVALIDATE;
+ if (ShowHidden)
+ ofn.Flags |= OFN.OFN_FORCESHOWHIDDEN;
+
+ if (RestoreDirectory)
+ ofn.Flags |= OFN.OFN_NOCHANGEDIR;
+ if (CreatePrompt)
+ ofn.Flags |= OFN.OFN_CREATEPROMPT;
+ if (OverwritePrompt)
+ ofn.Flags |= OFN.OFN_OVERWRITEPROMPT;
+
+ var result = PInvoke.GetSaveFileName(ref ofn);
+ if (result)
+ FileName = Marshal.PtrToStringUni(fileName);
+
+ Marshal.FreeCoTaskMem(fileName);
+
+ return result;
+ }
+}
+
+internal class OpenFileDialog : FileDialog
+{
+ ///
+ /// Gets or sets a value indicating whether
+ /// the dialog box displays a warning if the
+ /// user specifies a file name that does not exist.
+ ///
+ public bool CheckFileExists { get; set; } = false; // OFN_FILEMUSTEXIST
+
+ ///
+ /// Gets or sets an option flag indicating whether the
+ /// dialog box allows multiple files to be selected.
+ ///
+ public bool Multiselect { get; set; } = false; // OFN_ALLOWMULTISELECT
+
+ ///
+ /// Gets or sets a value indicating whether the read-only
+ /// check box is selected.
+ ///
+ public bool ReadOnlyChecked { get; set; } = false; // OFN_READONLY
+
+ ///
+ /// Gets or sets a value indicating whether the dialog
+ /// contains a read-only check box.
+ ///
+ public bool ShowReadOnly { get; set; } = false; // OFN_HIDEREADONLY
+
+ ///
+ /// Gets or sets a string containing the full path of the file selected in
+ /// the file dialog box.
+ ///
+ public string? FileName => FileNames?.Length > 0 ? FileNames[0] : null;
+
+ ///
+ /// Gets the file names of all selected files in the dialog box.
+ ///
+ public string[]? FileNames { get; protected set; } = null;
+
+ public override bool ShowDialog()
+ {
+ FileNames = null;
+
+ var file = Marshal.AllocHGlobal(MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize);
+ for (var i = 0; i < MAX_FILE_LENGTH * Marshal.SystemDefaultCharSize; i++)
+ Marshal.WriteByte(file, i, 0);
+
+ var fileTitle = string.IsNullOrEmpty(FileName) ? IntPtr.Zero : Marshal.ReAllocCoTaskMem(Marshal.StringToCoTaskMemUni(FileName ?? string.Empty), MAX_FILE_LENGTH);
+ var ofn = new OPENFILENAME
+ {
+ lStructSize = (uint) Marshal.SizeOf(),
+ lpstrFilter = Filter?.Replace("|", "\0") + "\0",
+ nFilterIndex = 1,
+ lpstrFileTitle = fileTitle,
+ nMaxFileTitle = MAX_FILE_LENGTH,
+ lpstrInitialDir = string.IsNullOrEmpty(InitialDirectory) ? default : new StrPtrAuto(InitialDirectory!),
+ lpstrTitle = string.IsNullOrEmpty(Title) ? default : new StrPtrAuto(Title!),
+ lpstrFile = file,
+ nMaxFile = MAX_FILE_LENGTH,
+ Flags = OFN.OFN_EXPLORER
+ };
+
+ if (CheckPathExists)
+ ofn.Flags |= OFN.OFN_PATHMUSTEXIST;
+ if (!ValidateNames)
+ ofn.Flags |= OFN.OFN_NOVALIDATE;
+ if (ShowHidden)
+ ofn.Flags |= OFN.OFN_FORCESHOWHIDDEN;
+
+ if (CheckFileExists)
+ ofn.Flags |= OFN.OFN_FILEMUSTEXIST;
+ if (Multiselect)
+ ofn.Flags |= OFN.OFN_ALLOWMULTISELECT;
+ if (ReadOnlyChecked)
+ ofn.Flags |= OFN.OFN_READONLY;
+ if (!ShowReadOnly)
+ ofn.Flags |= OFN.OFN_HIDEREADONLY;
+
+ var result = PInvoke.GetOpenFileName(ref ofn);
+ if (result)
+ {
+ var filePointer = file;
+ var pointer = (long) filePointer;
+ var fileStr = Marshal.PtrToStringAuto(filePointer);
+ var strList = new List();
+
+ // Retrieve file names
+ while (fileStr.Length > 0)
+ {
+ strList.Add(fileStr);
+
+ pointer += fileStr.Length * Marshal.SystemDefaultCharSize + Marshal.SystemDefaultCharSize;
+ filePointer = (IntPtr) pointer;
+ fileStr = Marshal.PtrToStringAuto(filePointer);
+ }
+
+ if (strList.Count > 1)
+ {
+ FileNames = new string[strList.Count - 1];
+ for (var i = 1; i < strList.Count; i++)
+ {
+ FileNames[i - 1] = Path.Combine(strList[0], strList[i]);
+ }
+ }
+ else
+ {
+ FileNames = strList.ToArray();
+ }
+ }
+
+ if (fileTitle != IntPtr.Zero) Marshal.FreeCoTaskMem(fileTitle);
+
+ return result;
+ }
+}
\ No newline at end of file