From 9cc90543157ba2957a3c3c77bf954d51ac838330 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 13 Nov 2021 02:09:46 -0500 Subject: [PATCH 1/3] Use microcom for system dialogs --- .../Platform/ISystemDialogImpl.cs | 6 +- .../Avalonia.Win32/Avalonia.Win32.csproj | 1 + .../Interop/UnmanagedMethods.cs | 268 ++--------------- .../Avalonia.Win32/SystemDialogImpl.cs | 274 +++++++++++------- src/Windows/Avalonia.Win32/Win32Com/win32.idl | 191 ++++++++++++ src/tools/MicroComGenerator/ParseException.cs | 2 +- 6 files changed, 383 insertions(+), 359 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Win32Com/win32.idl diff --git a/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs b/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs index affec6301b..7bd13ecdb1 100644 --- a/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs +++ b/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs @@ -1,5 +1,7 @@ using System.Threading.Tasks; +#nullable enable + namespace Avalonia.Controls.Platform { /// @@ -13,8 +15,8 @@ namespace Avalonia.Controls.Platform /// The details of the file dialog to show. /// The parent window. /// A task returning the selected filenames. - Task ShowFileDialogAsync(FileDialog dialog, Window parent); + Task ShowFileDialogAsync(FileDialog dialog, Window parent); - Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent); + Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent); } } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index fe5f806fbe..2a2aff322a 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 938f4222e0..648a3c6a34 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -6,6 +6,9 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; +using Avalonia.MicroCom; +using Avalonia.Win32.Win32Com; + // ReSharper disable InconsistentNaming #pragma warning disable 169, 649 @@ -1244,8 +1247,19 @@ namespace Avalonia.Win32.Interop internal static extern int CoCreateInstance(ref Guid clsid, IntPtr ignore1, int ignore2, ref Guid iid, [Out] out IntPtr pUnkOuter); + internal unsafe static T CreateInstance(ref Guid clsid, ref Guid iid) where T : IUnknown + { + var hresult = CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out IntPtr pUnk); + if (hresult != 0) + { + throw new COMException("CreateInstance", hresult); + } + using var unk = MicroComRuntime.CreateProxyFor(pUnk, true); + return MicroComRuntime.QueryInterface(unk); + } + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv); [DllImport("user32.dll", SetLastError = true)] public static extern bool OpenClipboard(IntPtr hWndOwner); @@ -1888,7 +1902,8 @@ namespace Avalonia.Win32.Interop E_INVALIDARG = 0x80070057, E_OUTOFMEMORY = 0x8007000E, E_NOTIMPL = 0x80004001, - E_UNEXPECTED = 0x8000FFFF + E_UNEXPECTED = 0x8000FFFF, + E_CANCELLED = 0x800704C7, } public enum Icons @@ -1897,33 +1912,6 @@ namespace Avalonia.Win32.Interop ICON_BIG = 1 } - public const uint SIGDN_FILESYSPATH = 0x80058000; - - [Flags] - public enum FOS : uint - { - FOS_OVERWRITEPROMPT = 0x00000002, - FOS_STRICTFILETYPES = 0x00000004, - FOS_NOCHANGEDIR = 0x00000008, - FOS_PICKFOLDERS = 0x00000020, - FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. - FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. - FOS_NOVALIDATE = 0x00000100, - FOS_ALLOWMULTISELECT = 0x00000200, - FOS_PATHMUSTEXIST = 0x00000800, - FOS_FILEMUSTEXIST = 0x00001000, - FOS_CREATEPROMPT = 0x00002000, - FOS_SHAREAWARE = 0x00004000, - FOS_NOREADONLYRETURN = 0x00008000, - FOS_NOTESTFILECREATE = 0x00010000, - FOS_HIDEMRUPLACES = 0x00020000, - FOS_HIDEPINNEDPLACES = 0x00040000, - FOS_NODEREFERENCELINKS = 0x00100000, - FOS_DONTADDTORECENT = 0x02000000, - FOS_FORCESHOWHIDDEN = 0x10000000, - FOS_DEFAULTNOMINIMODE = 0x20000000 - } - public static class ShellIds { public static readonly Guid OpenFileDialog = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7"); @@ -1934,228 +1922,6 @@ namespace Avalonia.Win32.Interop public static readonly Guid ITaskBarList2 = Guid.Parse("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf"); } - [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(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 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 Unadvise([In] uint dwCookie); - - [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)] - 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 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 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 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 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 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 SetClientGuid([In] ref Guid guid); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - uint ClearClientData(); - - [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); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void SetFileTypeIndex([In] uint iFileType); - - [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] - void 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)] - void Unadvise([In] uint dwCookie); - - [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 { diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index f595a58c91..6a609273a5 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -1,170 +1,234 @@ +#nullable enable using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.MicroCom; using Avalonia.Win32.Interop; +using Avalonia.Win32.Win32Com; namespace Avalonia.Win32 { - class SystemDialogImpl : ISystemDialogImpl + internal class SystemDialogImpl : ISystemDialogImpl { - private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | - UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT; + private const uint SIGDN_FILESYSPATH = 0x80058000; - public unsafe Task ShowFileDialogAsync(FileDialog dialog, Window parent) + private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE | + FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT; + + public unsafe Task ShowFileDialogAsync(FileDialog dialog, Window parent) { var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; - return Task.Factory.StartNew(() => + return Task.Run(() => { - string[] result = default; - - 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 object unk); - var frm = (UnmanagedMethods.IFileDialog)unk; + string[]? result = default; + try + { + var clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; + var iid = UnmanagedMethods.ShellIds.IFileDialog; + var frm = UnmanagedMethods.CreateInstance(ref clsid, ref iid); - var openDialog = dialog as OpenFileDialog; + var openDialog = dialog as OpenFileDialog; - uint options; - frm.GetOptions(out options); - options |= (uint)(DefaultDialogOptions); - if (openDialog?.AllowMultiple == true) - options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT; - frm.SetOptions(options); + var options = frm.Options; + options |= DefaultDialogOptions; + if (openDialog?.AllowMultiple == true) + { + options |= FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; + } + frm.SetOptions(options); - var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; - frm.SetDefaultExtension(defaultExtension); - frm.SetFileName(dialog.InitialFileName ?? ""); - frm.SetTitle(dialog.Title ?? ""); + var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; + fixed (char* pExt = defaultExtension) + { + frm.SetDefaultExtension(pExt); + } - var filters = new List(); - if (dialog.Filters != null) - { - foreach (var filter in dialog.Filters) + var initialFileName = dialog.InitialFileName ?? ""; + fixed (char* fExt = initialFileName) { - var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); - filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); + frm.SetFileName(fExt); } - } - if (filters.Count == 0) - filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" }); - frm.SetFileTypes((uint)filters.Count, filters.ToArray()); - frm.SetFileTypeIndex(0); + var title = dialog.Title ?? ""; + fixed (char* tExt = title) + { + frm.SetTitle(tExt); + } - if (dialog.Directory != null) - { - UnmanagedMethods.IShellItem directoryShellItem; - Guid riid = UnmanagedMethods.ShellIds.IShellItem; - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + fixed (void* pFilters = FiltersToPointer(dialog.Filters, out var count)) { - frm.SetFolder(directoryShellItem); - frm.SetDefaultFolder(directoryShellItem); + frm.SetFileTypes((ushort)count, pFilters); } - } - if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) - { - if (openDialog?.AllowMultiple == true) + frm.SetFileTypeIndex(0); + + if (dialog.Directory != null) { - 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++) + var riid = UnmanagedMethods.ShellIds.IShellItem; + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem) + == (uint)UnmanagedMethods.HRESULT.S_OK) { - UnmanagedMethods.IShellItem shellItem; - shellItemArray.GetItemAt(i, out shellItem); - result[i] = GetAbsoluteFilePath(shellItem); + var proxy = MicroComRuntime.CreateProxyFor(directoryShellItem, true); + frm.SetFolder(proxy); + frm.SetDefaultFolder(proxy); } } - else + + frm.Show(hWnd); + + if (openDialog?.AllowMultiple == true) { - UnmanagedMethods.IShellItem shellItem; - if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + using var fileOpenDialog = frm.QueryInterface(); + var shellItemArray = fileOpenDialog.Results; + var count = shellItemArray.Count; + + var results = new List(); + for (int i = 0; i < count; i++) { - result = new string[] { GetAbsoluteFilePath(shellItem) }; + var shellItem = shellItemArray.GetItemAt(i); + if (GetAbsoluteFilePath(shellItem) is { } selected) + { + results.Add(selected); + } } + result = results.ToArray(); + } + else if (frm.Result is { } shellItem + && GetAbsoluteFilePath(shellItem) is { } singleResult) + { + result = new[] { singleResult }; } - } - return result; - }); + return result; + } + catch (COMException ex) + { + if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) + { + return result; + } + throw new Win32Exception(ex.HResult); + } + })!; } - public Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) + public unsafe Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) { - return Task.Factory.StartNew(() => + return Task.Run(() => { - string result = default; + string? result = default; + try + { + var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; + var clsid = UnmanagedMethods.ShellIds.OpenFileDialog; + var iid = UnmanagedMethods.ShellIds.IFileDialog; + var frm = UnmanagedMethods.CreateInstance(ref clsid, ref iid); - var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; - Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog; - Guid iid = UnmanagedMethods.ShellIds.IFileDialog; + var options = frm.Options; + options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | DefaultDialogOptions; + frm.SetOptions(options); - UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk); - var frm = (UnmanagedMethods.IFileDialog)unk; - uint options; - frm.GetOptions(out options); - options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | DefaultDialogOptions); - frm.SetOptions(options); - frm.SetTitle(dialog.Title ?? ""); + var title = dialog.Title ?? ""; + fixed (char* tExt = title) + { + frm.SetTitle(tExt); + } - if (dialog.Directory != null) - { - UnmanagedMethods.IShellItem directoryShellItem; - Guid riid = UnmanagedMethods.ShellIds.IShellItem; - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + if (dialog.Directory != null) { - frm.SetFolder(directoryShellItem); + var riid = UnmanagedMethods.ShellIds.IShellItem; + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem) + == (uint)UnmanagedMethods.HRESULT.S_OK) + { + var proxy = MicroComRuntime.CreateProxyFor(directoryShellItem, true); + frm.SetFolder(proxy); + frm.SetDefaultFolder(proxy); + } } - } - if (dialog.Directory != null) - { - UnmanagedMethods.IShellItem directoryShellItem; - Guid riid = UnmanagedMethods.ShellIds.IShellItem; - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + frm.Show(hWnd); + if (frm.Result is not null) { - frm.SetDefaultFolder(directoryShellItem); + result = GetAbsoluteFilePath(frm.Result); } - } - if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) + return result; + } + catch (COMException ex) { - UnmanagedMethods.IShellItem shellItem; - if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) + if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) { - result = GetAbsoluteFilePath(shellItem); + return result; } + throw new Win32Exception(ex.HResult); } - - return result; }); } - private string GetAbsoluteFilePath(UnmanagedMethods.IShellItem shellItem) + private unsafe IShellItem GetShellItemFromPtr(IntPtr ptr) { - IntPtr pszString; - if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) + return MicroComRuntime.CreateProxyFor(ptr, true); + } + + private unsafe string? GetAbsoluteFilePath(IShellItem shellItem) + { + var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH)); + if (pszString != IntPtr.Zero) { - if (pszString != IntPtr.Zero) + try { - try - { - return Marshal.PtrToStringAuto(pszString); - } - finally - { - Marshal.FreeCoTaskMem(pszString); - } + return Marshal.PtrToStringUni(pszString); + } + finally + { + Marshal.FreeCoTaskMem(pszString); } } return default; } + + private unsafe byte[] FiltersToPointer(List? filters, out int lenght) + { + if (filters == null || filters.Count == 0) + { + filters = new List + { + new FileDialogFilter { Name = "All files", Extensions = new List { "*" } } + }; + } + + var size = Marshal.SizeOf(); + var arr = new byte[size]; + var resultArr = new byte[size * filters.Count]; + + for (int i = 0; i < filters.Count; i++) + { + var filter = filters[i]; + var filterPtr = Marshal.AllocHGlobal(size); + try + { + var filterStr = new UnmanagedMethods.COMDLG_FILTERSPEC + { + pszName = filter.Name, + pszSpec = string.Join(";", filter.Extensions.Select(e => "*." + e)) + }; + + Marshal.StructureToPtr(filterStr, filterPtr, false); + Marshal.Copy(filterPtr, resultArr, i * size, size); + } + finally + { + Marshal.FreeHGlobal(filterPtr); + } + } + + lenght = filters.Count; + return resultArr; + } } } diff --git a/src/Windows/Avalonia.Win32/Win32Com/win32.idl b/src/Windows/Avalonia.Win32/Win32Com/win32.idl new file mode 100644 index 0000000000..4a39782e02 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32Com/win32.idl @@ -0,0 +1,191 @@ +@clr-namespace Avalonia.Win32.Win32Com +@clr-access internal +@clr-map FLOAT float +@clr-map HSTRING IntPtr +@clr-map Vector2 System.Numerics.Vector2 +@clr-map Vector3 System.Numerics.Vector3 +@clr-map Quaternion System.Numerics.Quaternion +@clr-map Matrix4x4 System.Numerics.Matrix4x4 +@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT +@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE +@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT +@clr-map HWND IntPtr +@clr-map BOOL int +@clr-map DWORD int +@clr-map boolean int +@clr-map BYTE byte +@clr-map INT16 short +@clr-map INT32 int +@clr-map INT64 long +@clr-map UINT ushort +@clr-map UINT16 ushort +@clr-map ULONG uint +@clr-map UINT32 uint +@clr-map UINT64 ulong +@clr-map DOUBLE double +@clr-map GUID System.Guid +@clr-map WCHAR System.Char +@clr-map REFGUID System.Guid* +@clr-map REFIID System.Guid* + +[flags] +enum FILEOPENDIALOGOPTIONS +{ + FOS_OVERWRITEPROMPT = 0x00000002, + FOS_STRICTFILETYPES = 0x00000004, + FOS_NOCHANGEDIR = 0x00000008, + FOS_PICKFOLDERS = 0x00000020, + FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. + FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. + FOS_NOVALIDATE = 0x00000100, + FOS_ALLOWMULTISELECT = 0x00000200, + FOS_PATHMUSTEXIST = 0x00000800, + FOS_FILEMUSTEXIST = 0x00001000, + FOS_CREATEPROMPT = 0x00002000, + FOS_SHAREAWARE = 0x00004000, + FOS_NOREADONLYRETURN = 0x00008000, + FOS_NOTESTFILECREATE = 0x00010000, + FOS_HIDEMRUPLACES = 0x00020000, + FOS_HIDEPINNEDPLACES = 0x00040000, + FOS_NODEREFERENCELINKS = 0x00100000, + FOS_DONTADDTORECENT = 0x02000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000 +} + +[ + object, + uuid(43826d1e-e718-42ee-bc55-a1e261c37bfe), + pointer_default(unique) +] +interface IShellItem : IUnknown +{ + HRESULT BindToHandler( + [in, unique] void* pbc, + [in] REFGUID bhid, + [in] REFIID riid, + [out, iid_is(riid)] void** ppv); + + HRESULT GetParent([out] IShellItem** ppsi); + + HRESULT GetDisplayName( + [in] uint sigdnName, + [out, string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName); + + HRESULT GetAttributes( + [in] ULONG sfgaoMask, + [out] ULONG* psfgaoAttribs); + + HRESULT Compare( + [in] IShellItem* psi, + [in] ULONG hint, + [out] int* piOrder); +} + +[ + object, + uuid(B63EA76D-1F85-456F-A19C-48159EFA858B), + pointer_default(unique) +] +interface IShellItemArray : IUnknown +{ + HRESULT BindToHandler([in, unique] void* pbc, [in] REFGUID bhid, [in] REFIID riid, [out, iid_is(riid)] void** ppvOut); + + HRESULT GetPropertyStore([in] UINT flags, [in] REFIID riid, [out, iid_is(riid)] void** ppv); + + HRESULT GetPropertyDescriptionList([in] void* keyType, [in] REFIID riid, [out, iid_is(riid)] void** ppv); + + HRESULT GetAttributes([in] int AttribFlags, [in] UINT sfgaoMask, [out] UINT* psfgaoAttribs); + + HRESULT GetCount([out] DWORD* pdwNumItems); + + HRESULT GetItemAt([in] DWORD dwIndex, [out] IShellItem** ppsi); + + HRESULT EnumItems([out] void** ppenumShellItems); +} + +[ + object, + uuid(B4DB1657-70D7-485E-8E3E-6FCB5A5C1802), + pointer_default(unique) +] +interface IModalWindow : IUnknown +{ + [local] + HRESULT Show( + [in, unique] HWND hwndOwner); +} + +[ + object, + uuid(42F85136-DB7E-439C-85F1-E4075D135FC8), + pointer_default(unique) +] +interface IFileDialog : IModalWindow +{ + HRESULT SetFileTypes( + [in] UINT cFileTypes, + [in, size_is(cFileTypes)] void* rgFilterSpec); + + HRESULT SetFileTypeIndex([in] UINT iFileType); + + HRESULT GetFileTypeIndex([out] UINT* piFileType); + + HRESULT Advise( + [in] void* pfde, + [out] DWORD* pdwCookie); + + HRESULT Unadvise([in] DWORD dwCookie); + + HRESULT SetOptions([in] FILEOPENDIALOGOPTIONS fos); + + HRESULT GetOptions([out] FILEOPENDIALOGOPTIONS* pfos); + + HRESULT SetDefaultFolder([in] IShellItem* psi); + + HRESULT SetFolder([in] IShellItem* psi); + + HRESULT GetFolder([out] IShellItem** ppsi); + + HRESULT GetCurrentSelection([out] IShellItem** ppsi); + + HRESULT SetFileName([in, string] WCHAR* pszName); + + HRESULT GetFileName([out, string] WCHAR** pszName); + + HRESULT SetTitle([in, string] WCHAR* pszTitle); + + HRESULT SetOkButtonLabel([in, string] WCHAR* pszText); + + HRESULT SetFileNameLabel([in, string] WCHAR* pszLabel); + + HRESULT GetResult([out] IShellItem** ppsi); + + HRESULT AddPlace( + [in] IShellItem* psi, + [in] INT32 fdap); + + HRESULT SetDefaultExtension([in, string] WCHAR* pszDefaultExtension); + + HRESULT Close([in] HRESULT hr); + + HRESULT SetClientGuid([in] REFGUID guid); + + HRESULT ClearClientData(); + + HRESULT SetFilter([in] void* pFilter); +} + +[ + object, + uuid(D57C7288-D4AD-4768-BE02-9D969532D960), + pointer_default(unique) +] +interface IFileOpenDialog : IFileDialog +{ + HRESULT GetResults( + [out] IShellItemArray** ppenum); + + HRESULT GetSelectedItems( + [out] IShellItemArray** ppsai); +} diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs index dfa8dcfe54..cb54918100 100644 --- a/src/tools/MicroComGenerator/ParseException.cs +++ b/src/tools/MicroComGenerator/ParseException.cs @@ -7,7 +7,7 @@ namespace MicroComGenerator public int Line { get; } public int Position { get; } - public ParseException(string message, int line, int position) : base(message) + public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}") { Line = line; Position = position; From bd2e7d23ca2d611343ffa0abe48c14c137733ba7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 28 Nov 2021 02:31:05 -0500 Subject: [PATCH 2/3] Update dialogs sample page --- samples/ControlCatalog/Pages/DialogsPage.xaml | 15 ++++++ .../ControlCatalog/Pages/DialogsPage.xaml.cs | 47 +++++++++++++++---- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 0497fe9c3b..a910962dde 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -2,16 +2,31 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.DialogsPage"> + + Use filters + + + + + + + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 49921fb7f6..e96b7aff08 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Dialogs; using Avalonia.Layout; using Avalonia.Markup.Xaml; @@ -16,6 +17,11 @@ namespace ControlCatalog.Pages { this.InitializeComponent(); + var results = this.FindControl("PickerLastResults"); + var resultsVisible = this.FindControl("PickerLastResultsVisible"); + + string lastSelectedDirectory = null; + List GetFilters() { if (this.FindControl("UseFilters").IsChecked != true) @@ -34,44 +40,67 @@ namespace ControlCatalog.Pages }; } - this.FindControl