From 3c3b5b63b3b84606309195d3fe8ece1e72a051da Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Wed, 6 Jun 2018 14:59:29 +0200 Subject: [PATCH] Use newer WinApi for OpenFileDialog and SaveFileDialog --- .../Interop/UnmanagedMethods.cs | 313 ++++++++++++------ .../Avalonia.Win32/SystemDialogImpl.cs | 228 +++++-------- 2 files changed, 295 insertions(+), 246 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 86dcec410b..e1048bc2e7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1184,16 +1184,6 @@ namespace Avalonia.Win32.Interop public IntPtr hIconSm; } - [Flags] - public enum OpenFileNameFlags - { - OFN_ALLOWMULTISELECT = 0x00000200, - OFN_EXPLORER = 0x00080000, - OFN_HIDEREADONLY = 0x00000004, - OFN_NOREADONLYRETURN = 0x00008000, - OFN_OVERWRITEPROMPT = 0x00000002 - } - public enum HRESULT : uint { S_FALSE = 0x0001, @@ -1213,7 +1203,7 @@ namespace Avalonia.Win32.Interop public const uint SIGDN_FILESYSPATH = 0x80058000; [Flags] - internal enum FOS : uint + public enum FOS : uint { FOS_OVERWRITEPROMPT = 0x00000002, FOS_STRICTFILETYPES = 0x00000004, @@ -1237,135 +1227,246 @@ namespace Avalonia.Win32.Interop FOS_DEFAULTNOMINIMODE = 0x20000000 } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct OpenFileName + public static class ShellIds { - public int lStructSize; - public IntPtr hwndOwner; - public IntPtr hInstance; - public IntPtr lpstrFilter; - public IntPtr lpstrCustomFilter; - public int nMaxCustFilter; - public int nFilterIndex; - public IntPtr lpstrFile; - public int nMaxFile; - public IntPtr lpstrFileTitle; - public int nMaxFileTitle; - public IntPtr lpstrInitialDir; - public IntPtr lpstrTitle; - public OpenFileNameFlags Flags; - private readonly ushort Unused; - private readonly ushort Unused2; - public IntPtr lpstrDefExt; - public IntPtr lCustData; - public IntPtr lpfnHook; - public IntPtr lpTemplateName; - public IntPtr reservedPtr; - public int reservedInt; - public int flagsEx; - } - } + public static readonly Guid OpenFileDialog = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7"); + public static readonly Guid SaveFileDialog = Guid.Parse("C0B4E2F3-BA21-4773-8DBA-335EC946EB8B"); + public static readonly Guid IFileDialog = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8"); + public static readonly Guid IShellItem = Guid.Parse("43826D1E-E718-42EE-BC55-A1E261C37BFE"); + } - [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IFileDialog - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - [PreserveSig()] - uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes(uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] COMDLG_FILTERSPEC[] rgFilterSpec); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileTypeIndex([In] uint iFileType); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypeIndex([In] uint iFileType); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFileTypeIndex(out uint piFileType); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileTypeIndex(out uint piFileType); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Unadvise([In] uint dwCookie); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Unadvise([In] uint dwCookie); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetOptions([In] uint fos); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetOptions(out uint fos); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Close([MarshalAs(UnmanagedType.Error)] uint hr); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Close([MarshalAs(UnmanagedType.Error)] uint hr); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetClientGuid([In] ref Guid guid); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetClientGuid([In] ref Guid guid); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint ClearClientData(); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint ClearClientData(); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); - } + } + + [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileOpenDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); - [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IShellItem - { - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileTypeIndex([In] uint iFileType); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFileTypeIndex(out uint piFileType); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Unadvise([In] uint dwCookie); - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); - + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Close([MarshalAs(UnmanagedType.Error)] int hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ClearClientData(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai); + } + + [ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItemArray + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, + [In] ref Guid riid, out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyDescriptionList([In] ref PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount(out uint pdwNumItems); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems); + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct PROPERTYKEY + { + public Guid fmtid; + public uint pid; + } + + public enum SIATTRIBFLAGS + { + SIATTRIBFLAGS_AND = 1, + SIATTRIBFLAGS_APPCOMPAT = 3, + SIATTRIBFLAGS_OR = 2 + } + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct COMDLG_FILTERSPEC + { + [MarshalAs(UnmanagedType.LPWStr)] + public string pszName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszSpec; + } } - + [Flags] internal enum DropEffect : int { diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index 9b6bf57e92..d555e93a88 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -1,152 +1,94 @@ +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Platform; +using Avalonia.Win32.Interop; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Platform; -using Avalonia.Win32.Interop; namespace Avalonia.Win32 { class SystemDialogImpl : ISystemDialogImpl { - static char[] ToChars(string s) - { - if (s == null) - return null; - var chars = new char[s.Length]; - for (int c = 0; c < s.Length; c++) - chars[c] = s[c]; - return chars; - } - public unsafe Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; return Task.Factory.StartNew(() => { - var filters = new StringBuilder(); - foreach (var filter in dialog.Filters) - { - var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); - filters.Append(filter.Name); - filters.Append(" ("); - filters.Append(extMask); - filters.Append(")"); - filters.Append('\0'); - filters.Append(extMask); - filters.Append('\0'); - } - if (filters.Length == 0) - filters.Append("All files\0*.*\0"); - filters.Append('\0'); + var result = new string[0]; - var filterBuffer = new char[filters.Length]; - filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length); - - var defExt = ToChars((dialog as SaveFileDialog)?.DefaultExtension); - var fileBuffer = new char[256]; - dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length); + Guid clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; + Guid iid = UnmanagedMethods.ShellIds.IFileDialog; + UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk); + var frm = (UnmanagedMethods.IFileDialog)unk; - string userSelectedExt = string.Empty; + var openDialog = dialog as OpenFileDialog; + uint options; + frm.GetOptions(out options); + options |= (uint)(UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); + if (openDialog?.AllowMultiple == true) + options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT; + frm.SetOptions(options); - var title = ToChars(dialog.Title); - var initialDir = ToChars(dialog.InitialDirectory); + var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; + frm.SetDefaultExtension(defaultExtension); + frm.SetFileName(dialog.InitialFileName ?? ""); + frm.SetTitle(dialog.Title); - fixed (char* pFileBuffer = fileBuffer) - fixed (char* pFilterBuffer = filterBuffer) - fixed (char* pDefExt = defExt) - fixed (char* pInitDir = initialDir) - fixed (char* pTitle = title) + var filters = new List(); + foreach (var filter in dialog.Filters) { - var ofn = new UnmanagedMethods.OpenFileName() - { - hwndOwner = hWnd, - hInstance = IntPtr.Zero, - lCustData = IntPtr.Zero, - nFilterIndex = 0, - Flags = - UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER | - UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY, - nMaxCustFilter = 0, - nMaxFile = fileBuffer.Length - 1, - nMaxFileTitle = 0, - lpTemplateName = IntPtr.Zero, - lpfnHook = IntPtr.Zero, - lpstrCustomFilter = IntPtr.Zero, - lpstrDefExt = new IntPtr(pDefExt), - lpstrFile = new IntPtr(pFileBuffer), - lpstrFileTitle = IntPtr.Zero, - lpstrFilter = new IntPtr(pFilterBuffer), - lpstrInitialDir = new IntPtr(pInitDir), - lpstrTitle = new IntPtr(pTitle), - - }; - ofn.lStructSize = Marshal.SizeOf(ofn); - if ((dialog as OpenFileDialog)?.AllowMultiple == true) - ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_ALLOWMULTISELECT; - - if (dialog is SaveFileDialog) - ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_NOREADONLYRETURN | - UnmanagedMethods.OpenFileNameFlags.OFN_OVERWRITEPROMPT; - - var pofn = &ofn; - - // We should save the current directory to restore it later. - var currentDirectory = Environment.CurrentDirectory; - - var res = dialog is OpenFileDialog - ? UnmanagedMethods.GetOpenFileName(new IntPtr(pofn)) - : UnmanagedMethods.GetSaveFileName(new IntPtr(pofn)); - - // Restore the old current directory, since GetOpenFileName and GetSaveFileName change it after they're called - Environment.CurrentDirectory = currentDirectory; - - if (!res) - return null; - if (dialog?.Filters.Count > 0) - userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions.FirstOrDefault(); + var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); + filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); } - var cStart = 0; - string dir = null; - var files = new List(); - for (var c = 0; c < fileBuffer.Length; c++) + if (filters.Count == 0) + filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" }); + + frm.SetFileTypes((uint)filters.Count, filters.ToArray()); + frm.SetFileTypeIndex(0); + + if (dialog.InitialDirectory != null) { - if (fileBuffer[c] == 0) + UnmanagedMethods.IShellItem directoryShellItem; + Guid riid = UnmanagedMethods.ShellIds.IShellItem; + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { - //Encountered double zero char - if (cStart == c) - break; - - var s = new string(fileBuffer, cStart, c - cStart); - if (dir == null) - dir = s; - else - files.Add(s); - cStart = c + 1; + frm.SetFolder(directoryShellItem); + frm.SetDefaultFolder(directoryShellItem); } } - if (files.Count == 0) + + if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) { - if (dialog is SaveFileDialog) + if (openDialog?.AllowMultiple == true) { - if (string.IsNullOrWhiteSpace(Path.GetExtension(dir)) && - !string.IsNullOrWhiteSpace(userSelectedExt) && - !userSelectedExt.Contains("*")) - dir = Path.ChangeExtension(dir, userSelectedExt); + UnmanagedMethods.IShellItemArray shellItemArray; + ((UnmanagedMethods.IFileOpenDialog)frm).GetResults(out shellItemArray); + uint count; + shellItemArray.GetCount(out count); + result = new string[count]; + for (uint i = 0; i < count; i++) + { + UnmanagedMethods.IShellItem shellItem; + shellItemArray.GetItemAt(i, out shellItem); + result[i] = GetAbsoluteFilePath(shellItem); + } + } + else + { + UnmanagedMethods.IShellItem shellItem; + if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + { + result = new string[] { GetAbsoluteFilePath(shellItem) }; + } } - - return new[] { dir }; } - return files.Select(f => Path.Combine(dir, f)).ToArray(); + return result; }); } @@ -157,11 +99,11 @@ namespace Avalonia.Win32 string result = string.Empty; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; - var clsid = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7"); - var iid = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8"); + Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; + Guid iid = UnmanagedMethods.ShellIds.IFileDialog; UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk); - var frm = (IFileDialog)unk; + var frm = (UnmanagedMethods.IFileDialog)unk; uint options; frm.GetOptions(out options); options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); @@ -169,8 +111,8 @@ namespace Avalonia.Win32 if (dialog.InitialDirectory != null) { - IShellItem directoryShellItem; - var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + UnmanagedMethods.IShellItem directoryShellItem; + Guid riid = UnmanagedMethods.ShellIds.IShellItem; if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetFolder(directoryShellItem); @@ -179,8 +121,8 @@ namespace Avalonia.Win32 if (dialog.DefaultDirectory != null) { - IShellItem directoryShellItem; - var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + UnmanagedMethods.IShellItem directoryShellItem; + Guid riid = UnmanagedMethods.ShellIds.IShellItem; if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetDefaultFolder(directoryShellItem); @@ -189,29 +131,35 @@ namespace Avalonia.Win32 if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) { - IShellItem shellItem; + UnmanagedMethods.IShellItem shellItem; if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { - IntPtr pszString; - if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) - { - if (pszString != IntPtr.Zero) - { - try - { - result = Marshal.PtrToStringAuto(pszString); - } - finally - { - Marshal.FreeCoTaskMem(pszString); - } - } - } + result = GetAbsoluteFilePath(shellItem); } } return result; }); } + + private string GetAbsoluteFilePath(UnmanagedMethods.IShellItem shellItem) + { + IntPtr pszString; + if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) + { + if (pszString != IntPtr.Zero) + { + try + { + return Marshal.PtrToStringAuto(pszString); + } + finally + { + Marshal.FreeCoTaskMem(pszString); + } + } + } + return ""; + } } }