src/Bannerlord.LauncherEx/Helpers/Input/OpenSaveDialogs.cs
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 Bannerlord.LauncherEx.Helpers.Input;
/// <summary>The StrPtr structure represents a LPTSTR.</summary>
[StructLayout(LayoutKind.Sequential), DebuggerDisplay("{ptr}, {ToString()}")]
file struct StrPtrAuto : IEquatable<string>, IEquatable<StrPtrAuto>, IEquatable<IntPtr>
{
private IntPtr ptr;
/// <summary>Initializes a new instance of the <see cref="StrPtrAuto"/> struct.</summary>
/// <param name="s">The string value.</param>
public StrPtrAuto(string s) => ptr = StringHelper.AllocString(s);
/// <summary>Initializes a new instance of the <see cref="StrPtrAuto"/> struct.</summary>
/// <param name="charLen">Number of characters to reserve in memory.</param>
public StrPtrAuto(uint charLen) => ptr = StringHelper.AllocChars(charLen);
/// <summary>Gets a value indicating whether this instance is equivalent to null pointer or void*.</summary>
/// <value><c>true</c> if this instance is null; otherwise, <c>false</c>.</value>
public bool IsNull => ptr == IntPtr.Zero;
/// <summary>Assigns a string pointer value to the pointer.</summary>
/// <param name="stringPtr">The string pointer value.</param>
public void Assign(IntPtr stringPtr) { Free(); ptr = stringPtr; }
/// <summary>Assigns a new string value to the pointer.</summary>
/// <param name="s">The string value.</param>
public void Assign(string s) => StringHelper.RefreshString(ref ptr, out var _, s);
/// <summary>Assigns a new string value to the pointer.</summary>
/// <param name="s">The string value.</param>
/// <param name="charsAllocated">The character count allocated.</param>
/// <returns><c>true</c> if new memory was allocated for the string; <c>false</c> if otherwise.</returns>
public bool Assign(string s, out uint charsAllocated) => StringHelper.RefreshString(ref ptr, out charsAllocated, s);
/// <summary>Assigns an integer to the pointer for uses such as LPSTR_TEXTCALLBACK.</summary>
/// <param name="value">The value to assign.</param>
public void AssignConstant(int value) { Free(); ptr = (IntPtr) value; }
/// <summary>Frees the unmanaged string memory.</summary>
public void Free() { StringHelper.FreeString(ptr); ptr = IntPtr.Zero; }
/// <summary>Indicates whether the specified string is <see langword="null"/> or an empty string ("").</summary>
/// <returns>
/// <see langword="true"/> if the value parameter is <see langword="null"/> or an empty string (""); otherwise, <see langword="false"/>.
/// </returns>
public bool IsNullOrEmpty => ptr == IntPtr.Zero || StringHelper.GetString(ptr, CharSet.Auto, 1) == string.Empty;
/// <summary>Performs an implicit conversion from <see cref="StrPtrAuto"/> to <see cref="string"/>.</summary>
/// <param name="p">The <see cref="StrPtrAuto"/> instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator string?(StrPtrAuto p) => p.IsNull ? null : p.ToString();
/// <summary>Performs an explicit conversion from <see cref="StrPtrAuto"/> to <see cref="System.IntPtr"/>.</summary>
/// <param name="p">The <see cref="StrPtrAuto"/> instance.</param>
/// <returns>The result of the conversion.</returns>
public static explicit operator IntPtr(StrPtrAuto p) => p.ptr;
/// <summary>Performs an implicit conversion from <see cref="IntPtr"/> to <see cref="StrPtrAuto"/>.</summary>
/// <param name="p">The pointer.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator StrPtrAuto(IntPtr p) => new() { ptr = p };
/// <summary>Determines whether the specified <see cref="IntPtr"/>, is equal to this instance.</summary>
/// <param name="other">The <see cref="IntPtr"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="IntPtr"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(IntPtr other) => EqualityComparer<IntPtr>.Default.Equals(ptr, other);
/// <summary>Determines whether the specified <see cref="IntPtr"/>, is equal to this instance.</summary>
/// <param name="other">The <see cref="IntPtr"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="IntPtr"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(string? other) => EqualityComparer<string?>.Default.Equals(this, other);
/// <summary>Determines whether the specified <see cref="StrPtrAuto"/>, is equal to this instance.</summary>
/// <param name="other">The <see cref="StrPtrAuto"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="StrPtrAuto"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public bool Equals(StrPtrAuto other) => Equals(other.ptr);
/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
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),
};
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode() => ptr.GetHashCode();
/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
/// <returns>A <see cref="string"/> that represents this instance.</returns>
public override string ToString() => StringHelper.GetString(ptr) ?? "null";
/// <summary>Determines whether two specified instances of <see cref="StrPtrAuto"/> are equal.</summary>
/// <param name="left">The first pointer or handle to compare.</param>
/// <param name="right">The second pointer or handle to compare.</param>
/// <returns><see langword="true"/> if <paramref name="left"/> equals <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator ==(StrPtrAuto left, StrPtrAuto right) => left.Equals(right);
/// <summary>Determines whether two specified instances of <see cref="StrPtrAuto"/> are not equal.</summary>
/// <param name="left">The first pointer or handle to compare.</param>
/// <param name="right">The second pointer or handle to compare.</param>
/// <returns><see langword="true"/> if <paramref name="left"/> does not equal <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(StrPtrAuto left, StrPtrAuto right) => !left.Equals(right);
}
/// <summary>A safe class that represents an object that is pinned in memory.</summary>
/// <seealso cref="IDisposable"/>
file static class StringHelper
{
/// <summary>Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.</summary>
/// <param name="count">The number of characters, inclusive of the null terminator.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address of the block of memory allocated.</returns>
public static IntPtr AllocChars(uint count, Func<int, IntPtr> 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;
}
/// <summary>Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.</summary>
/// <param name="count">The number of characters, inclusive of the null terminator.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address of the block of memory allocated.</returns>
public static IntPtr AllocChars(uint count, CharSet charSet = CharSet.Auto) => AllocChars(count, Marshal.AllocCoTaskMem, charSet);
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from the unmanaged COM task allocator.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
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);
}
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
public static IntPtr AllocSecureString(SecureString? s, CharSet charSet, Func<int, IntPtr> memAllocator) => AllocSecureString(s, charSet, memAllocator, out _);
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="allocatedBytes">Returns the number of allocated bytes for the string.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
public static IntPtr AllocSecureString(SecureString? s, CharSet charSet, Func<int, IntPtr> 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;
}
/// <summary>Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator.</summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s"/> is null.</returns>
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));
/// <summary>Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s"/> is null.</returns>
public static IntPtr AllocString(string? s, CharSet charSet, Func<int, IntPtr> memAllocator) => AllocString(s, charSet, memAllocator, out _);
/// <summary>
/// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.
/// </summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="allocatedBytes">Returns the number of allocated bytes for the string.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s" /> is null.</returns>
public static IntPtr AllocString(string? s, CharSet charSet, Func<int, IntPtr> 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;
}
/// <summary>
/// Zeros out the allocated memory behind a secure string and then frees that memory.
/// </summary>
/// <param name="ptr">The address of the memory to be freed.</param>
/// <param name="sizeInBytes">The size in bytes of the memory pointed to by <paramref name="ptr"/>.</param>
/// <param name="memFreer">The memory freer.</param>
public static void FreeSecureString(IntPtr ptr, int sizeInBytes, Action<IntPtr> memFreer)
{
if (IsValue(ptr)) return;
var b = new byte[sizeInBytes];
Marshal.Copy(b, 0, ptr, b.Length);
memFreer(ptr);
}
/// <summary>Frees a block of memory allocated by the unmanaged COM task memory allocator for a string.</summary>
/// <param name="ptr">The address of the memory to be freed.</param>
/// <param name="charSet">The character set of the string.</param>
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);
}
/// <summary>Gets the encoded bytes for a string including an optional null terminator.</summary>
/// <param name="value">The string value to convert.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the resulting byte array.</param>
/// <param name="charSet">The character set.</param>
/// <returns>A byte array including <paramref name="value"/> encoded as per <paramref name="charSet"/> and the optional null terminator.</returns>
public static byte[] GetBytes(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) =>
GetBytes(value, GetCharSize(charSet) == 1 ? Encoding.UTF8 : Encoding.Unicode, nullTerm);
/// <summary>Gets the encoded bytes for a string including an optional null terminator.</summary>
/// <param name="value">The string value to convert.</param>
/// <param name="enc">The character encoding.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the resulting byte array.</param>
/// <returns>A byte array including <paramref name="value"/> encoded as per <paramref name="enc"/> and the optional null terminator.</returns>
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;
}
/// <summary>Gets the number of bytes required to store the string.</summary>
/// <param name="value">The string value.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the count if <paramref name="value"/> does not equal <c>null</c>.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The number of bytes required to store <paramref name="value"/>. Returns 0 if <paramref name="value"/> is <c>null</c>.</returns>
public static int GetByteCount(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) =>
GetByteCount(value, GetCharSize(charSet) == 1 ? Encoding.UTF8 : Encoding.Unicode, nullTerm);
/// <summary>Gets the number of bytes required to store the string.</summary>
/// <param name="value">The string value.</param>
/// <param name="enc">The character encoding.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the count if <paramref name="value"/> does not equal <c>null</c>.</param>
/// <returns>The number of bytes required to store <paramref name="value"/>. Returns 0 if <paramref name="value"/> is <c>null</c>.</returns>
public static int GetByteCount(this string value, Encoding enc, bool nullTerm = true) =>
value is null ? 0 : enc.GetByteCount(value) + (nullTerm ? GetCharSize(enc) : 0);
/// <summary>Gets the size of a character defined by the supplied <see cref="CharSet"/>.</summary>
/// <param name="charSet">The character set to size.</param>
/// <returns>The size of a standard character, in bytes, from <paramref name="charSet"/>.</returns>
public static int GetCharSize(CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.SystemDefaultCharSize : (charSet == CharSet.Unicode ? UnicodeEncoding.CharSize : 1);
/// <summary>Gets the size of a character defined by the supplied <see cref="Encoding"/>.</summary>
/// <param name="enc">The character encoding type.</param>
/// <returns>The size of a standard character, in bytes, from <paramref name="enc"/>.</returns>
public static int GetCharSize(Encoding enc) => enc.GetByteCount(new[] { '\0' });
/// <summary>
/// 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.
/// </summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="charSet">The character set of the string.</param>
/// <param name="allocatedBytes">If known, the total number of bytes allocated to the native memory in <paramref name="ptr"/>.</param>
/// <returns>
/// A managed string that holds a copy of the unmanaged string if the value of the <paramref name="ptr"/> parameter is not null;
/// otherwise, this method returns null.
/// </returns>
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();
}
/// <summary>
/// Allocates a managed String and copies all characters up to the first null character or at most <paramref name="length"/> characters from a string stored in unmanaged memory into it.
/// </summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="length">The number of characters to copy.</param>
/// <param name="charSet">The character set of the string.</param>
/// <returns>
/// A managed string that holds a copy of the unmanaged string if the value of the <paramref name="ptr"/> parameter is not null;
/// otherwise, this method returns null.
/// </returns>
public static string? GetString(IntPtr ptr, int length, CharSet charSet = CharSet.Auto) => GetString(ptr, charSet, length * GetCharSize(charSet));
/// <summary>Indicates whether a specified string is <see langword="null"/>, empty, or consists only of white-space characters.</summary>
/// <param name="value">The string to test.</param>
/// <returns>
/// <see langword="true"/> if the <paramref name="value"/> parameter is <see langword="null"/> or <see cref="string.Empty"/>, or if
/// value consists exclusively of white-space characters.
/// </returns>
public static bool IsNullOrWhiteSpace(string? value) => value is null || value.All(c => char.IsWhiteSpace(c));
/// <summary>Refreshes the memory block from the unmanaged COM task allocator and copies the contents of a new managed String.</summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="charLen">Receives the new character length of the allocated memory block.</param>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set of the string.</param>
/// <returns><c>true</c> if the memory block was reallocated; <c>false</c> if set to null.</returns>
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;
}
/// <summary>Writes the specified string to a pointer to allocated memory.</summary>
/// <param name="value">The string value.</param>
/// <param name="ptr">The pointer to the allocated memory.</param>
/// <param name="byteCnt">The resulting number of bytes written.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the count if <paramref name="value"/> does not equal <c>null</c>.</param>
/// <param name="charSet">The character set of the string.</param>
/// <param name="allocatedBytes">If known, the total number of bytes allocated to the native memory in <paramref name="ptr"/>.</param>
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;
}
/// <summary>
/// An error code returned by the CommDlgExtendedError function.
/// </summary>
/// <remarks>
/// <list type="table">
/// <listheader>
/// <term>Error code</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>CDERR</term>
/// <term>General error codes that can be returned for any of the common dialog box functions.</term>
/// </item>
/// <item>
/// <term>PDERR</term>
/// <term>Error codes returned for the PrintDlg function.</term>
/// </item>
/// <item>
/// </item>
/// <item>
/// <term>CFERR</term>
/// <term>Error codes returned for the ChooseFont function.</term>
/// </item>
/// <item>
/// <term>FNERR</term>
/// <term>Error codes returned for the GetOpenFileName and GetSaveFileName functions.</term>
/// </item>
/// <item>
/// <term>FRERR</term>
/// <term>Error codes returned for the FindText and ReplaceText functions.</term>
/// </item>
/// </list>
/// </remarks>
file enum ERR : uint
{
/// <summary>
/// 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.
/// </summary>
CDERR_DIALOGFAILURE = 0xFFFF,
/// <summary>
/// The common dialog box function failed to find a specified resource.
/// </summary>
CDERR_FINDRESFAILURE = 0x0006,
/// <summary>
/// The common dialog box function failed during initialization. This error often occurs when sufficient memory is not available.
/// </summary>
CDERR_INITIALIZATION = 0x0002,
/// <summary>
/// The common dialog box function failed to load a specified resource.
/// </summary>
CDERR_LOADRESFAILURE = 0x0007,
/// <summary>
/// The common dialog box function failed to load a specified string.
/// </summary>
CDERR_LOADSTRFAILURE = 0x0005,
/// <summary>
/// The common dialog box function failed to lock a specified resource.
/// </summary>
CDERR_LOCKRESFAILURE = 0x0008,
/// <summary>
/// The common dialog box function was unable to allocate memory for internal structures.
/// </summary>
CDERR_MEMALLOCFAILURE = 0x0009,
/// <summary>
/// The common dialog box function was unable to lock the memory associated with a handle.
/// </summary>
CDERR_MEMLOCKFAILURE = 0x000A,
/// <summary>
/// The <c>ENABLETEMPLATE</c> flag was set in the <c>Flags</c> member of the initialization structure for the corresponding common
/// dialog box, but you failed to provide a corresponding instance handle.
/// </summary>
CDERR_NOHINSTANCE = 0x0004,
/// <summary>
/// The <c>ENABLEHOOK</c> flag was set in the <c>Flags</c> member of the initialization structure for the corresponding common
/// dialog box, but you failed to provide a pointer to a corresponding hook procedure.
/// </summary>
CDERR_NOHOOK = 0x000B,
/// <summary>
/// The <c>ENABLETEMPLATE</c> flag was set in the <c>Flags</c> member of the initialization structure for the corresponding common dialog
/// box, but you failed to provide a corresponding template.
/// </summary>
CDERR_NOTEMPLATE = 0x0003,
/// <summary>
/// The RegisterWindowMessage function returned an error code when it was called by the common dialog box function.
/// </summary>
CDERR_REGISTERMSGFAIL = 0x000C,
/// <summary>
/// The <c>lStructSize</c> member of the initialization structure for the corresponding common dialog box is invalid.
/// </summary>
CDERR_STRUCTSIZE = 0x0001,
/// <summary>
/// The PrintDlg function failed when it attempted to create an information context.
/// </summary>
PDERR_CREATEICFAILURE = 0x100A,
/// <summary>
/// You called the PrintDlg function with the <c>DN_DEFAULTPRN</c> flag specified in the <c>wDefault</c> member of the <c>DEVNAMES</c> structure,
/// but the printer described by the other structure members did not match the current default printer. This error occurs when
/// you store the <c>DEVNAMES</c> structure, and the user changes the default printer by using the Control Panel.
/// <para>To use the printer described by the <c>DEVNAMES</c> structure, clear the <c>DN_DEFAULTPRN</c> flag and call PrintDlg again.</para>
/// <para>To use the default printer, replace the <c>DEVNAMES</c> structure (and the structure, if one exists) with <c>NULL</c>; and call PrintDlg again.</para>
/// </summary>
PDERR_DEFAULTDIFFERENT = 0x100C,
/// <summary>
/// The data in the <c>DEVMODE</c> and <c>DEVNAMES</c> structures describes two different printers.
/// </summary>
PDERR_DNDMMISMATCH = 0x1009,
/// <summary>
/// The printer driver failed to initialize a <c>DEVMODE</c> structure.
/// </summary>
PDERR_GETDEVMODEFAIL = 0x1005,
/// <summary>
/// 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.
/// </summary>
PDERR_INITFAILURE = 0x1006,
/// <summary>
/// The PrintDlg function failed to load the device driver for the specified printer.
/// </summary>
PDERR_LOADDRVFAILURE = 0x1004,
/// <summary>
/// A default printer does not exist.
/// </summary>
PDERR_NODEFAULTPRN = 0x1008,
/// <summary>
/// No printer drivers were found.
/// </summary>
PDERR_NODEVICES = 0x1007,
/// <summary>
/// The PrintDlg function failed to parse the strings in the [devices] section of the WIN.INI file.
/// </summary>
PDERR_PARSEFAILURE = 0x1002,
/// <summary>
/// The [devices] section of the WIN.INI file did not contain an entry for the requested printer.
/// </summary>
PDERR_PRINTERNOTFOUND = 0x100B,
/// <summary>
/// The <c>PD_RETURNDEFAULT</c> flag was specified in the Flags member of the <c>PRINTDLG</c> structure, but the <c>hDevMode</c> or <c>hDevNames</c> member was not <c>NULL</c>.
/// </summary>
PDERR_RETDEFFAILURE = 0x1003,
/// <summary>
/// The PrintDlg function failed to load the required resources.
/// </summary>
PDERR_SETUPFAILURE = 0x1001,
/// <summary>
/// The size specified in the <c>nSizeMax</c> member of the <c>CHOOSEFONT</c> structure is less than the size specified in the <c>nSizeMin</c> member.
/// </summary>
CFERR_MAXLESSTHANMIN = 0x2002,
/// <summary>
/// No fonts exist.
/// </summary>
CFERR_NOFONTS = 0x2001,
/// <summary>
/// The buffer pointed to by the <c>lpstrFile</c> member of the <c>OPENFILENAME</c> structure is too small for the file name specified
/// by the user. The first two bytes of the <c>lpstrFile</c> buffer contain an integer value specifying the size required to receive
/// the full name, in characters.
/// </summary>
FNERR_BUFFERTOOSMALL = 0x3003,
/// <summary>
/// A file name is invalid.
/// </summary>
FNERR_INVALIDFILENAME = 0x3002,
/// <summary>
/// An attempt to subclass a list box failed because sufficient memory was not available.
/// </summary>
FNERR_SUBCLASSFAILURE = 0x3001,
/// <summary>
/// A member of the <c>FINDREPLACE</c> structure points to an invalid buffer.
/// </summary>
FRERR_BUFFERLENGTHZERO = 0x4001,
}
/// <summary>
/// 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.
/// </summary>
[Flags]
file enum OFN
{
/// <summary>
/// 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.
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </summary>
OFN_ALLOWMULTISELECT = 0x00000200,
/// <summary>
/// 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.
/// </summary>
OFN_CREATEPROMPT = 0x00002000,
/// <summary>
/// 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.
/// </summary>
OFN_DONTADDTORECENT = 0x02000000,
/// <summary>Enables the hook function specified in the lpfnHook member.</summary>
OFN_ENABLEHOOK = 0x00000020,
/// <summary>
/// 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.
/// </summary>
OFN_ENABLEINCLUDENOTIFY = 0x00400000,
/// <summary>
/// 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.
/// </summary>
OFN_ENABLESIZING = 0x00800000,
/// <summary>
/// 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.
/// </summary>
OFN_ENABLETEMPLATE = 0x00000040,
/// <summary>
/// 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.
/// </summary>
OFN_ENABLETEMPLATEHANDLE = 0x00000080,
/// <summary>
/// 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.
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </summary>
OFN_EXPLORER = 0x00080000,
/// <summary>
/// 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.
/// </summary>
OFN_EXTENSIONDIFFERENT = 0x00000400,
/// <summary>
/// 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.
/// </summary>
OFN_FILEMUSTEXIST = 0x00001000,
/// <summary>
/// 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.
/// </summary>
OFN_FORCESHOWHIDDEN = 0x10000000,
/// <summary>Hides the Read Only check box.</summary>
OFN_HIDEREADONLY = 0x00000004,
/// <summary>
/// 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.
/// </summary>
OFN_LONGNAMES = 0x00200000,
/// <summary>
/// Restores the current directory to its original value if the user changed the directory while searching for files.
/// <para>This flag is ineffective for GetOpenFileName.</para>
/// </summary>
OFN_NOCHANGEDIR = 0x00000008,
/// <summary>
/// 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.
/// </summary>
OFN_NODEREFERENCELINKS = 0x00100000,
/// <summary>
/// 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.
/// </summary>
OFN_NOLONGNAMES = 0x00040000,
/// <summary>Hides and disables the Network button.</summary>
OFN_NONETWORKBUTTON = 0x00020000,
/// <summary>The returned file does not have the Read Only check box selected and is not in a write-protected directory.</summary>
OFN_NOREADONLYRETURN = 0x00008000,
/// <summary>
/// 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.
/// </summary>
OFN_NOTESTFILECREATE = 0x00010000,
/// <summary>
/// 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.
/// </summary>
OFN_NOVALIDATE = 0x00000100,
/// <summary>
/// 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.
/// </summary>
OFN_OVERWRITEPROMPT = 0x00000002,
/// <summary>
/// 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.
/// </summary>
OFN_PATHMUSTEXIST = 0x00000800,
/// <summary>
/// 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.
/// </summary>
OFN_READONLY = 0x00000001,
/// <summary>
/// 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.
/// </summary>
OFN_SHAREAWARE = 0x00004000,
/// <summary>
/// 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.
/// </summary>
OFN_SHOWHELP = 0x00000010,
}
/// <summary>A set of bit flags you can use to initialize the dialog box.</summary>
[Flags]
file enum OFN_EX
{
/// <summary>
/// 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.
/// </summary>
OFN_EX_NOPLACESBAR = 0x00000001,
}
/// <summary>
/// <para>
/// [Starting with Windows Vista, the <c>Open</c> and <c>Save As</c> 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.]
/// </para>
/// <para>
/// 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 <c>Open</c> and <c>Save As</c> dialog boxes.
/// </para>
/// <para>
/// The <c>LPOFNHOOKPROC</c> type defines a pointer to this callback function. OFNHookProc is a placeholder for the
/// application-defined function name.
/// </para>
/// </summary>
/// <param name="Arg1">
/// A handle to the child dialog box of the <c>Open</c> or <c>Save As</c> dialog box. Use the GetParent function to get the handle
/// to the <c>Open</c> or <c>Save As</c> dialog box.
/// </param>
/// <param name="Arg2">The identifier of the message being received.</param>
/// <param name="Arg3">Additional information about the message. The exact meaning depends on the value of the Arg2 parameter.</param>
/// <param name="Arg4">
/// 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.
/// </param>
/// <returns>
/// <para>If the hook procedure returns zero, the default dialog box procedure processes the message.</para>
/// <para>If the hook procedure returns a nonzero value, the default dialog box procedure ignores the message.</para>
/// <para>
/// 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 <c>DWL_MSGRESULT</c> value.
/// </para>
/// </returns>
/// <remarks>
/// <para>
/// If you do not specify the <c>OFN_EXPLORER</c> flag when you create an <c>Open</c> or <c>Save As</c> 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.
/// </para>
/// <para>
/// When you use the GetOpenFileName or GetSaveFileName functions to create an Explorer-style <c>Open</c> or <c>Save As</c> 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 <c>lpfnHook</c> member and specify the
/// <c>OFN_ENABLEHOOK</c> flag in the <c>Flags</c> member.
/// </para>
/// <para>
/// 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 <c>WM_INITDIALOG</c> 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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 <c>WM_CTLCOLOR*</c> message, it must return a valid brush handle to painting the
/// background of the specified control.
/// </para>
/// <para>
/// 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 <c>IDCANCEL</c> value to the dialog box procedure. Posting <c>IDCANCEL</c> closes the dialog
/// box and causes the dialog box function to return <c>FALSE</c>. 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.
/// </para>
/// </remarks>
// 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);
/// <summary>
/// <para>
/// [Starting with Windows Vista, the <c>Open</c> and <c>Save As</c> 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.]
/// </para>
/// <para>
/// Contains information that the GetOpenFileName and GetSaveFileName functions use to initialize an <c>Open</c> or <c>Save As</c>
/// dialog box. After the user closes the dialog box, the system returns information about the user's selection in this structure.
/// </para>
/// </summary>
/// <remarks>
/// For compatibility reasons, the Places Bar is hidden if <c>Flags</c> is set to <c>OFN_ENABLEHOOK</c> and <c>lStructSize</c> is <c>OPENFILENAME_SIZE_VERSION_400</c>.
/// </remarks>
// 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
{
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>The length, in bytes, of the structure. Use
/// <code>sizeof (OPENFILENAME)</code>
/// for this parameter.
/// </para>
/// </summary>
public uint lStructSize;
/// <summary>
/// <para>Type: <c>HWND</c></para>
/// <para>
/// A handle to the window that owns the dialog box. This member can be any valid window handle, or it can be <c>NULL</c> if the
/// dialog box has no owner.
/// </para>
/// </summary>
public IntPtr hwndOwner; // TODO: HWND
/// <summary>
/// <para>Type: <c>HINSTANCE</c></para>
/// <para>
/// If the <c>OFN_ENABLETEMPLATEHANDLE</c> flag is set in the <c>Flags</c> member, <c>hInstance</c> is a handle to a memory
/// object containing a dialog box template. If the <c>OFN_ENABLETEMPLATE</c> flag is set, <c>hInstance</c> is a handle to a
/// module that contains a dialog box template named by the <c>lpTemplateName</c> member. If neither flag is set, this member is
/// ignored. If the <c>OFN_EXPLORER</c> 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 <c>OFN_EXPLORER</c> flag is not set, the system uses the template to
/// create an old-style dialog box that replaces the default dialog box.
/// </para>
/// </summary>
public IntPtr hInstance; // TODO: HINSTANCE
/// <summary>
/// <para>Type: <c>LPCTSTR</c></para>
/// <para>
/// A buffer containing pairs of null-terminated filter strings. The last string in the buffer must be terminated by two
/// <c>NULL</c> characters.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// The system does not change the order of the filters. It displays them in the <c>File Types</c> combo box in the order
/// specified in <c>lpstrFilter</c>.
/// </para>
/// <para>If <c>lpstrFilter</c> is <c>NULL</c>, the dialog box does not display any filters.</para>
/// <para>
/// 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 <c>OFN_NODEREFERENCELINKS</c> flag in the <c>Flags</c> member. To
/// retrieve a shortcut's target without filtering, use the string
/// <code>"All Files\0*.*\0\0"</code>
/// .
/// </para>
/// </summary>
[MarshalAs(UnmanagedType.LPTStr)]
public string lpstrFilter;
/// <summary>
/// <para>Type: <c>LPTSTR</c></para>
/// <para>
/// 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 <c>lpstrFilter</c> 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 <c>nFilterIndex</c> member is zero, the dialog box uses the custom filter.
/// </para>
/// <para>If this member is <c>NULL</c>, the dialog box does not preserve user-defined filter patterns.</para>
/// <para>
/// If this member is not <c>NULL</c>, the value of the <c>nMaxCustFilter</c> member must specify the size, in characters, of
/// the <c>lpstrCustomFilter</c> buffer.
/// </para>
/// </summary>
public StrPtrAuto lpstrCustomFilter;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// The size, in characters, of the buffer identified by <c>lpstrCustomFilter</c>. This buffer should be at least 40 characters
/// long. This member is ignored if <c>lpstrCustomFilter</c> is <c>NULL</c> or points to a <c>NULL</c> string.
/// </para>
/// </summary>
public uint nMaxCustFilter;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// The index of the currently selected filter in the <c>File Types</c> control. The buffer pointed to by <c>lpstrFilter</c>
/// 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 <c>lpstrCustomFilter</c>. 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,
/// <c>nFilterIndex</c> returns the index of the currently displayed filter. If <c>nFilterIndex</c> is zero and
/// <c>lpstrCustomFilter</c> is <c>NULL</c>, the system uses the first filter in the <c>lpstrFilter</c> buffer. If all three
/// members are zero or <c>NULL</c>, the system does not use any filters and does not show any files in the file list control of
/// the dialog box.
/// </para>
/// </summary>
public uint nFilterIndex;
/// <summary>
/// <para>Type: <c>LPTSTR</c></para>
/// <para>
/// The file name used to initialize the <c>File Name</c> edit control. The first character of this buffer must be <c>NULL</c>
/// 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.
/// </para>
/// <para>
/// If the <c>OFN_ALLOWMULTISELECT</c> 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 <c>NULL</c> separated, with an extra <c>NULL</c> 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 <c>lpstrFile</c>
/// string does not have a separator between the path and file name.
/// </para>
/// <para>
/// If the buffer is too small, the function returns <c>FALSE</c> and the CommDlgExtendedError function returns
/// <c>FNERR_BUFFERTOOSMALL</c>. In this case, the first two bytes of the <c>lpstrFile</c> buffer contain the required size, in
/// bytes or characters.
/// </para>
/// </summary>
public StrPtrAuto lpstrFile;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// The size, in characters, of the buffer pointed to by <c>lpstrFile</c>. The buffer must be large enough to store the path and
/// file name string or strings, including the terminating <c>NULL</c> character. The GetOpenFileName and GetSaveFileName
/// functions return <c>FALSE</c> if the buffer is too small to contain the file information. The buffer should be at least 256
/// characters long.
/// </para>
/// </summary>
public uint nMaxFile;
/// <summary>
/// <para>Type: <c>LPTSTR</c></para>
/// <para>The file name and extension (without path information) of the selected file. This member can be <c>NULL</c>.</para>
/// </summary>
public StrPtrAuto lpstrFileTitle;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// The size, in characters, of the buffer pointed to by <c>lpstrFileTitle</c>. This member is ignored if <c>lpstrFileTitle</c>
/// is <c>NULL</c>.
/// </para>
/// </summary>
public uint nMaxFileTitle;
/// <summary>
/// <para>Type: <c>LPCTSTR</c></para>
/// <para>The initial directory. The algorithm for selecting the initial directory varies on different platforms.</para>
/// <para><c>Windows 7:</c></para>
/// <list type="number">
/// <item>
/// <term>
/// If <c>lpstrInitialDir</c> has the same value as was passed the first time the application used an <c>Open</c> or <c>Save
/// As</c> dialog box, the path most recently selected by the user is used as the initial directory.
/// </term>
/// </item>
/// <item>
/// <term>Otherwise, if <c>lpstrFile</c> contains a path, that path is the initial directory.</term>
/// </item>
/// <item>
/// <term>Otherwise, if <c>lpstrInitialDir</c> is not <c>NULL</c>, it specifies the initial directory.</term>
/// </item>
/// <item>
/// <term>
/// If <c>lpstrInitialDir</c> is <c>NULL</c> and the current directory contains any files of the specified filter types, the
/// initial directory is the current directory.
/// </term>
/// </item>
/// <item>
/// <term>Otherwise, the initial directory is the personal files directory of the current user.</term>
/// </item>
/// <item>
/// <term>Otherwise, the initial directory is the Desktop folder.</term>
/// </item>
/// </list>
/// <para>Windows 2000/XP/Vista:</para>
/// <list type="number">
/// <item>
/// <term>If <c>lpstrFile</c> contains a path, that path is the initial directory.</term>
/// </item>
/// <item>
/// <term>Otherwise, <c>lpstrInitialDir</c> specifies the initial directory.</term>
/// </item>
/// <item>
/// <term>
/// Otherwise, if the application has used an <c>Open</c> or <c>Save As</c> 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.
/// </term>
/// </item>
/// <item>
/// <term>
/// If <c>lpstrInitialDir</c> is <c>NULL</c> and the current directory contains any files of the specified filter types, the
/// initial directory is the current directory.
/// </term>
/// </item>
/// <item>
/// <term>Otherwise, the initial directory is the personal files directory of the current user.</term>
/// </item>
/// <item>
/// <term>Otherwise, the initial directory is the Desktop folder.</term>
/// </item>
/// </list>
/// </summary>
public StrPtrAuto lpstrInitialDir;
/// <summary>
/// <para>Type: <c>LPCTSTR</c></para>
/// <para>
/// A string to be placed in the title bar of the dialog box. If this member is <c>NULL</c>, the system uses the default title
/// (that is, <c>Save As</c> or <c>Open</c>).
/// </para>
/// </summary>
public StrPtrAuto lpstrTitle;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// 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.
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>OFN_ALLOWMULTISELECT 0x00000200</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_CREATEPROMPT 0x00002000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_DONTADDTORECENT 0x02000000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_ENABLEHOOK 0x00000020</term>
/// <term>Enables the hook function specified in the lpfnHook member.</term>
/// </item>
/// <item>
/// <term>OFN_ENABLEINCLUDENOTIFY 0x00400000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_ENABLESIZING 0x00800000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_ENABLETEMPLATE 0x00000040</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_ENABLETEMPLATEHANDLE 0x00000080</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_EXPLORER 0x00080000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_EXTENSIONDIFFERENT 0x00000400</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_FILEMUSTEXIST 0x00001000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_FORCESHOWHIDDEN 0x10000000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_HIDEREADONLY 0x00000004</term>
/// <term>Hides the Read Only check box.</term>
/// </item>
/// <item>
/// <term>OFN_LONGNAMES 0x00200000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_NOCHANGEDIR 0x00000008</term>
/// <term>
/// Restores the current directory to its original value if the user changed the directory while searching for files. This flag
/// is ineffective for GetOpenFileName.
/// </term>
/// </item>
/// <item>
/// <term>OFN_NODEREFERENCELINKS 0x00100000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_NOLONGNAMES 0x00040000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_NONETWORKBUTTON 0x00020000</term>
/// <term>Hides and disables the Network button.</term>
/// </item>
/// <item>
/// <term>OFN_NOREADONLYRETURN 0x00008000</term>
/// <term>The returned file does not have the Read Only check box selected and is not in a write-protected directory.</term>
/// </item>
/// <item>
/// <term>OFN_NOTESTFILECREATE 0x00010000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_NOVALIDATE 0x00000100</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_OVERWRITEPROMPT 0x00000002</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_PATHMUSTEXIST 0x00000800</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_READONLY 0x00000001</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_SHAREAWARE 0x00004000</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>OFN_SHOWHELP 0x00000010</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// </list>
/// </summary>
public OFN Flags;
/// <summary>
/// <para>Type: <c>WORD</c></para>
/// <para>
/// The zero-based offset, in characters, from the beginning of the path to the file name in the string pointed to by
/// <c>lpstrFile</c>. For the ANSI version, this is the number of bytes; for the Unicode version, this is the number of
/// characters. For example, if <c>lpstrFile</c> 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, <c>nFileOffset</c> is
/// the offset to the first file name.
/// </para>
/// </summary>
public ushort nFileOffset;
/// <summary>
/// <para>Type: <c>WORD</c></para>
/// <para>
/// The zero-based offset, in characters, from the beginning of the path to the file name extension in the string pointed to by
/// <c>lpstrFile</c>. 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
/// <c>lpstrFile</c> points to the string "c:\dir1\dir2\readme.txt", this member contains the value 20. If <c>lpstrFile</c>
/// points to the string "c:\dir1\dir2\readme.txt.html", this member contains the value 24. If <c>lpstrFile</c> points to the
/// string "c:\dir1\dir2\readme.txt.html.", this member contains the value 29. If <c>lpstrFile</c> points to a string that does
/// not contain any "." character such as "c:\dir1\dir2\readme", this member contains zero.
/// </para>
/// </summary>
public ushort nFileExtension;
/// <summary>
/// <para>Type: <c>LPCTSTR</c></para>
/// <para>
/// 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 <c>NULL</c> and the user fails to type an extension, no extension is appended.
/// </para>
/// </summary>
public StrPtrAuto lpstrDefExt;
/// <summary>
/// <para>Type: <c>LPARAM</c></para>
/// <para>
/// Application-defined data that the system passes to the hook procedure identified by the <c>lpfnHook</c> member. When the
/// system sends the WM_INITDIALOG message to the hook procedure, the message's lParam parameter is a pointer to the
/// <c>OPENFILENAME</c> structure specified when the dialog box was created. The hook procedure can use this pointer to get the
/// <c>lCustData</c> value.
/// </para>
/// </summary>
public IntPtr lCustData;
/// <summary>
/// <para>Type: <c>LPOFNHOOKPROC</c></para>
/// <para>
/// A pointer to a hook procedure. This member is ignored unless the <c>Flags</c> member includes the <c>OFN_ENABLEHOOK</c> flag.
/// </para>
/// <para>
/// If the <c>OFN_EXPLORER</c> flag is not set in the <c>Flags</c> member, <c>lpfnHook</c> is a pointer to an
/// OFNHookProcOldStyle hook procedure that receives messages intended for the dialog box. The hook procedure returns
/// <c>FALSE</c> to pass a message to the default dialog box procedure or <c>TRUE</c> to discard the message.
/// </para>
/// <para>
/// If <c>OFN_EXPLORER</c> is set, <c>lpfnHook</c> 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.
/// </para>
/// </summary>
[MarshalAs(UnmanagedType.FunctionPtr)]
public LPOFNHOOKPROC lpfnHook;
/// <summary>
/// <para>Type: <c>LPCTSTR</c></para>
/// <para>
/// The name of the dialog template resource in the module identified by the <c>hInstance</c> member. For numbered dialog box
/// resources, this can be a value returned by the MAKEINTRESOURCE macro. This member is ignored unless the
/// <c>OFN_ENABLETEMPLATE</c> flag is set in the <c>Flags</c> member. If the <c>OFN_EXPLORER</c> 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
/// <c>OFN_EXPLORER</c> flag is not set, the system uses the template to create an old-style dialog box that replaces the
/// default dialog box.
/// </para>
/// </summary>
[MarshalAs(UnmanagedType.LPTStr)]
public string lpTemplateName;
/// <summary>
/// <para>Type: <c>void*</c></para>
/// <para>This member is reserved.</para>
/// </summary>
private IntPtr pvReserved;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>This member is reserved.</para>
/// </summary>
private uint dwReserved;
/// <summary>
/// <para>Type: <c>DWORD</c></para>
/// <para>A set of bit flags you can use to initialize the dialog box. Currently, this member can be zero or the following flag.</para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <term>OFN_EX_NOPLACESBAR 0x00000001</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// </list>
/// </summary>
public OFN_EX FlagsEx;
}
file class PInvoke
{
/// <summary>
/// <para>
/// [Starting with Windows Vista, the <c>Open</c> and <c>Save As</c> 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.]
/// </para>
/// <para>
/// Creates an <c>Open</c> dialog box that lets the user specify the drive, directory, and the name of a file or set of files to be opened.
/// </para>
/// </summary>
/// <param name="Arg1">
/// <para>Type: <c>LPOPENFILENAME</c></para>
/// <para>
/// A pointer to an OPENFILENAME structure that contains information used to initialize the dialog box. When <c>GetOpenFileName</c>
/// returns, this structure contains information about the user's file selection.
/// </para>
/// </param>
/// <returns>
/// <para>Type: <c>BOOL</c></para>
/// <para>
/// If the user specifies a file name and clicks the <c>OK</c> button, the return value is nonzero. The buffer pointed to by the
/// <c>lpstrFile</c> member of the OPENFILENAME structure contains the full path and file name specified by the user.
/// </para>
/// <para>
/// If the user cancels or closes the <c>Open</c> 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.
/// </para>
/// </returns>
/// <remarks>
/// <para>
/// The Explorer-style <c>Open</c> dialog box provides user-interface features that are similar to the Windows Explorer. You can
/// provide an OFNHookProc hook procedure for an Explorer-style <c>Open</c> dialog box. To enable the hook procedure, set the
/// <c>OFN_EXPLORER</c> and <c>OFN_ENABLEHOOK</c> flags in the <c>Flags</c> member of the OPENFILENAME structure and specify the
/// address of the hook procedure in the <c>lpfnHook</c> member.
/// </para>
/// <para>
/// Windows continues to support the old-style <c>Open</c> dialog box for applications that want to maintain a user-interface
/// consistent with the old-style user-interface. To display the old-style <c>Open</c> dialog box, enable an OFNHookProcOldStyle
/// hook procedure and ensure that the <c>OFN_EXPLORER</c> flag is not set.
/// </para>
/// <para>To display a dialog box that allows the user to select a directory instead of a file, call the SHBrowseForFolder function.</para>
/// <para>Note, when selecting multiple files, the total character limit for the file names depends on the version of the function.</para>
/// <list type="bullet">
/// <item>
/// <term>ANSI: 32k limit</term>
/// </item>
/// <item>
/// <term>Unicode: no restriction</term>
/// </item>
/// </list>
/// <para>Examples</para>
/// <para>For an example, see Opening a File.</para>
/// </remarks>
// 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);
/// <summary>
/// <para>
/// [Starting with Windows Vista, the <c>Open</c> and <c>Save As</c> 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.]
/// </para>
/// <para>Creates a <c>Save</c> dialog box that lets the user specify the drive, directory, and name of a file to save.</para>
/// </summary>
/// <param name="Arg1">
/// <para>Type: <c>LPOPENFILENAME</c></para>
/// <para>
/// A pointer to an OPENFILENAME structure that contains information used to initialize the dialog box. When <c>GetSaveFileName</c>
/// returns, this structure contains information about the user's file selection.
/// </para>
/// </param>
/// <returns>
/// <para>Type: <c>BOOL</c></para>
/// <para>
/// If the user specifies a file name and clicks the <c>OK</c> button and the function is successful, the return value is nonzero.
/// The buffer pointed to by the <c>lpstrFile</c> member of the OPENFILENAME structure contains the full path and file name
/// specified by the user.
/// </para>
/// <para>
/// If the user cancels or closes the <c>Save</c> 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:
/// </para>
/// </returns>
/// <remarks>
/// <para>
/// The Explorer-style <c>Save</c> 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 <c>Save</c> dialog box. To enable the hook procedure, set the
/// <c>OFN_EXPLORER</c> and <c>OFN_ENABLEHOOK</c> flags in the <c>Flags</c> member of the OPENFILENAME structure and specify the
/// address of the hook procedure in the <c>lpfnHook</c> member.
/// </para>
/// <para>
/// Windows continues to support old-style <c>Save</c> dialog boxes for applications that want to maintain a user-interface
/// consistent with the old-style user-interface. To display the old-style <c>Save</c> dialog box, enable an OFNHookProcOldStyle
/// hook procedure and ensure that the <c>OFN_EXPLORER</c> flag is not set.
/// </para>
/// <para>Examples</para>
/// <para>For an example, see Creating an Enhanced Metafile.</para>
/// </remarks>
// 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);
/// <summary>
/// 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.
/// </summary>
/// <returns>
/// <para>Type: <c>DWORD</c></para>
/// <para>
/// If the most recent call to a common dialog box function succeeded, the return value is undefined. If the common dialog box
/// function returned <c>FALSE</c> because the user closed or canceled the dialog box, the return value is zero. Otherwise, the
/// return value is a nonzero error code.
/// </para>
/// <para>
/// The <c>CommDlgExtendedError</c> 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.
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Return code/value</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>CDERR_DIALOGFAILURE 0xFFFF</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>CDERR_FINDRESFAILURE 0x0006</term>
/// <term>The common dialog box function failed to find a specified resource.</term>
/// </item>
/// <item>
/// <term>CDERR_INITIALIZATION 0x0002</term>
/// <term>The common dialog box function failed during initialization. This error often occurs when sufficient memory is not available.</term>
/// </item>
/// <item>
/// <term>CDERR_LOADRESFAILURE 0x0007</term>
/// <term>The common dialog box function failed to load a specified resource.</term>
/// </item>
/// <item>
/// <term>CDERR_LOADSTRFAILURE 0x0005</term>
/// <term>The common dialog box function failed to load a specified string.</term>
/// </item>
/// <item>
/// <term>CDERR_LOCKRESFAILURE 0x0008</term>
/// <term>The common dialog box function failed to lock a specified resource.</term>
/// </item>
/// <item>
/// <term>CDERR_MEMALLOCFAILURE 0x0009</term>
/// <term>The common dialog box function was unable to allocate memory for internal structures.</term>
/// </item>
/// <item>
/// <term>CDERR_MEMLOCKFAILURE 0x000A</term>
/// <term>The common dialog box function was unable to lock the memory associated with a handle.</term>
/// </item>
/// <item>
/// <term>CDERR_NOHINSTANCE 0x0004</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>CDERR_NOHOOK 0x000B</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>CDERR_NOTEMPLATE 0x0003</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>CDERR_REGISTERMSGFAIL 0x000C</term>
/// <term>The RegisterWindowMessage function returned an error code when it was called by the common dialog box function.</term>
/// </item>
/// <item>
/// <term>CDERR_STRUCTSIZE 0x0001</term>
/// <term>The lStructSize member of the initialization structure for the corresponding common dialog box is invalid.</term>
/// </item>
/// </list>
/// <para>The following error codes can be returned for the PrintDlg function.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code/value</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>PDERR_CREATEICFAILURE 0x100A</term>
/// <term>The PrintDlg function failed when it attempted to create an information context.</term>
/// </item>
/// <item>
/// <term>PDERR_DEFAULTDIFFERENT 0x100C</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>PDERR_DNDMMISMATCH 0x1009</term>
/// <term>The data in the DEVMODE and DEVNAMES structures describes two different printers.</term>
/// </item>
/// <item>
/// <term>PDERR_GETDEVMODEFAIL 0x1005</term>
/// <term>The printer driver failed to initialize a DEVMODE structure.</term>
/// </item>
/// <item>
/// <term>PDERR_INITFAILURE 0x1006</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>PDERR_LOADDRVFAILURE 0x1004</term>
/// <term>The PrintDlg function failed to load the device driver for the specified printer.</term>
/// </item>
/// <item>
/// <term>PDERR_NODEFAULTPRN 0x1008</term>
/// <term>A default printer does not exist.</term>
/// </item>
/// <item>
/// <term>PDERR_NODEVICES 0x1007</term>
/// <term>No printer drivers were found.</term>
/// </item>
/// <item>
/// <term>PDERR_PARSEFAILURE 0x1002</term>
/// <term>The PrintDlg function failed to parse the strings in the [devices] section of the WIN.INI file.</term>
/// </item>
/// <item>
/// <term>PDERR_PRINTERNOTFOUND 0x100B</term>
/// <term>The [devices] section of the WIN.INI file did not contain an entry for the requested printer.</term>
/// </item>
/// <item>
/// <term>PDERR_RETDEFFAILURE 0x1003</term>
/// <term>
/// The PD_RETURNDEFAULT flag was specified in the Flags member of the PRINTDLG structure, but the hDevMode or hDevNames member was
/// not NULL.
/// </term>
/// </item>
/// <item>
/// <term>PDERR_SETUPFAILURE 0x1001</term>
/// <term>The PrintDlg function failed to load the required resources.</term>
/// </item>
/// </list>
/// <para>The following error codes can be returned for the ChooseFont function.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code/value</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>CFERR_MAXLESSTHANMIN CFERR_MAXLESSTHANMIN</term>
/// <term>
/// The size specified in the nSizeMax member of the CHOOSEFONT structure is less than the size specified in the nSizeMin member.
/// </term>
/// </item>
/// <item>
/// <term>CFERR_NOFONTS 0x2001</term>
/// <term>No fonts exist.</term>
/// </item>
/// </list>
/// <para>The following error codes can be returned for the GetOpenFileName and GetSaveFileName functions.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code/value</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>FNERR_BUFFERTOOSMALL 0x3003</term>
/// <term>
/// 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.
/// </term>
/// </item>
/// <item>
/// <term>FNERR_INVALIDFILENAME 0x3002</term>
/// <term>A file name is invalid.</term>
/// </item>
/// <item>
/// <term>FNERR_SUBCLASSFAILURE 0x3001</term>
/// <term>An attempt to subclass a list box failed because sufficient memory was not available.</term>
/// </item>
/// </list>
/// <para>The following error code can be returned for the FindText and ReplaceText functions.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code/value</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>FRERR_BUFFERLENGTHZERO 0x4001</term>
/// <term>A member of the FINDREPLACE structure points to an invalid buffer.</term>
/// </item>
/// </list>
/// </returns>
// 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;
/// <summary>
/// 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.
/// </summary>
public bool CheckPathExists { get; set; } = false; // OFN_PATHMUSTEXIST
/// <summary>
/// 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 (*.*)|*.*"
/// </summary>
/// <exception cref="System.ArgumentException">
/// 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.)
/// </exception>
/// <remarks>
/// 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.
/// </remarks>
public string? Filter { get; set; } = "All files(*.*)\0\0";
/// <summary>
/// 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.
/// </summary>
public int FilterIndex { get; set; } = 1;
/// <summary>
/// Gets or sets the initial directory displayed by the file dialog box.
/// </summary>
public string? InitialDirectory { get; set; } = null;
/// <summary>
/// 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")
/// </summary>
public string? Title { get; set; } = "Open a file...";
/// <summary>
/// Gets or sets a value indicating whether the dialog box accepts only valid
/// Win32 file names.
/// </summary>
public bool ValidateNames { get; set; } = false; // OFN_NOVALIDATE
public bool ShowHidden { get; set; } = false;
public abstract bool ShowDialog();
}
internal class SaveFileDialog : FileDialog
{
/// <summary>
/// Restores the current directory to its original value if the user
/// changed the directory while searching for files.
/// </summary>
public bool RestoreDirectory { get; set; } // OFN_NOCHANGEDIR
/// <summary>
/// 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.
/// </summary>
/// <Remarks>
/// Callers must have UIPermission.AllWindows to call this API.
/// </Remarks>
public bool CreatePrompt { get; set; } = false; // OFN_CREATEPROMPT
/// <summary>
/// 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.
/// </summary>
/// <Remarks>
/// Callers must have UIPermission.AllWindows to call this API.
/// </Remarks>
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<OPENFILENAME>(),
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
{
/// <summary>
/// Gets or sets a value indicating whether
/// the dialog box displays a warning if the
/// user specifies a file name that does not exist.
/// </summary>
public bool CheckFileExists { get; set; } = false; // OFN_FILEMUSTEXIST
/// <summary>
/// Gets or sets an option flag indicating whether the
/// dialog box allows multiple files to be selected.
/// </summary>
public bool Multiselect { get; set; } = false; // OFN_ALLOWMULTISELECT
/// <summary>
/// Gets or sets a value indicating whether the read-only
/// check box is selected.
/// </summary>
public bool ReadOnlyChecked { get; set; } = false; // OFN_READONLY
/// <summary>
/// Gets or sets a value indicating whether the dialog
/// contains a read-only check box.
/// </summary>
public bool ShowReadOnly { get; set; } = false; // OFN_HIDEREADONLY
/// <summary>
/// Gets or sets a string containing the full path of the file selected in
/// the file dialog box.
/// </summary>
public string? FileName => FileNames?.Length > 0 ? FileNames[0] : null;
/// <summary>
/// Gets the file names of all selected files in the dialog box.
/// </summary>
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<OPENFILENAME>(),
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<string>();
// 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;
}
}