From 3dac39618223e8eef086cc204d4ef3366e7241af Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 24 Jun 2022 01:00:20 -0400 Subject: [PATCH] Update Windows implementations --- src/Windows/Avalonia.Win32/Win32Platform.cs | 1 - ...mDialogImpl.cs => Win32StorageProvider.cs} | 205 +++++++++--------- src/Windows/Avalonia.Win32/WindowImpl.cs | 7 +- 3 files changed, 111 insertions(+), 102 deletions(-) rename src/Windows/Avalonia.Win32/{SystemDialogImpl.cs => Win32StorageProvider.cs} (53%) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index dc5e5324c4..4e13a0555d 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -160,7 +160,6 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) - .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control) { diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs similarity index 53% rename from src/Windows/Avalonia.Win32/SystemDialogImpl.cs rename to src/Windows/Avalonia.Win32/Win32StorageProvider.cs index 410ba55627..bb1de56f2b 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs @@ -1,86 +1,142 @@ -#nullable enable +#nullable enable + using System; +using System.Linq; using System.Collections.Generic; +using System.IO; 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.Platform.Storage; +using Avalonia.Platform.Storage.FileIO; using Avalonia.Win32.Interop; using Avalonia.Win32.Win32Com; namespace Avalonia.Win32 { - internal class SystemDialogImpl : ISystemDialogImpl + internal class Win32StorageProvider : BclStorageProvider { private const uint SIGDN_FILESYSPATH = 0x80058000; private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE | FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT; - public unsafe Task ShowFileDialogAsync(FileDialog dialog, Window parent) + private readonly WindowImpl _windowImpl; + + public Win32StorageProvider(WindowImpl windowImpl) + { + _windowImpl = windowImpl; + } + + public override bool CanOpen => true; + + public override bool CanSave => true; + + public override bool CanPickFolder => true; + + public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) + { + var files = await ShowFilePicker( + true, true, + options.AllowMultiple, false, + options.Title, null, options.SuggestedStartLocation, null, null); + return files.Select(f => new BclStorageFolder(new DirectoryInfo(f))).ToArray(); + } + + public override async Task> OpenFilePickerAsync(FilePickerOpenOptions options) + { + var files = await ShowFilePicker( + true, false, + options.AllowMultiple, false, + options.Title, null, options.SuggestedStartLocation, + null, options.FileTypeFilter); + return files.Select(f => new BclStorageFile(new FileInfo(f))).ToArray(); + } + + public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) + { + var files = await ShowFilePicker( + false, false, + false, options.ShowOverwritePrompt, + options.Title, options.SuggestedFileName, options.SuggestedStartLocation, + options.DefaultExtension, options.FileTypeChoices); + return files.Select(f => new BclStorageFile(new FileInfo(f))).FirstOrDefault(); + } + + private unsafe Task> ShowFilePicker( + bool isOpenFile, + bool openFolder, + bool allowMultiple, + bool? showOverwritePrompt, + string? title, + string? suggestedFileName, + IStorageFolder? folder, + string? defaultExtension, + IReadOnlyList? filters) { - var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero; return Task.Run(() => { - string[]? result = default; + IEnumerable result = Array.Empty(); try { - var clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog; + var clsid = isOpenFile ? 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 options = frm.Options; options |= DefaultDialogOptions; - if (openDialog?.AllowMultiple == true) + if (openFolder) + { + options |= FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS; + } + if (allowMultiple) { options |= FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT; } - if (dialog is SaveFileDialog saveFileDialog) + if (showOverwritePrompt == false) { - var overwritePrompt = saveFileDialog.ShowOverwritePrompt ?? true; - - if (!overwritePrompt) - { - options &= ~FILEOPENDIALOGOPTIONS.FOS_OVERWRITEPROMPT; - } + options &= ~FILEOPENDIALOGOPTIONS.FOS_OVERWRITEPROMPT; } frm.SetOptions(options); - var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; - fixed (char* pExt = defaultExtension) + if (defaultExtension is not null) { - frm.SetDefaultExtension(pExt); + fixed (char* pExt = defaultExtension) + { + frm.SetDefaultExtension(pExt); + } } - var initialFileName = dialog.InitialFileName ?? ""; - fixed (char* fExt = initialFileName) + suggestedFileName ??= ""; + fixed (char* fExt = suggestedFileName) { frm.SetFileName(fExt); } - var title = dialog.Title ?? ""; + title ??= ""; fixed (char* tExt = title) { frm.SetTitle(tExt); } - fixed (void* pFilters = FiltersToPointer(dialog.Filters, out var count)) + if (!openFolder) { - frm.SetFileTypes((ushort)count, pFilters); + fixed (void* pFilters = FiltersToPointer(filters, out var count)) + { + frm.SetFileTypes((ushort)count, pFilters); + if (count > 0) + { + frm.SetFileTypeIndex(0); + } + } } - frm.SetFileTypeIndex(0); - - if (dialog.Directory != null) + if (folder?.TryGetUri(out var folderPath) == true) { var riid = UnmanagedMethods.ShellIds.IShellItem; - if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem) + if (UnmanagedMethods.SHCreateItemFromParsingName(folderPath.LocalPath, IntPtr.Zero, ref riid, out var directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { var proxy = MicroComRuntime.CreateProxyFor(directoryShellItem, true); @@ -89,18 +145,18 @@ namespace Avalonia.Win32 } } - var showResult = frm.Show(hWnd); + var showResult = frm.Show(_windowImpl.Handle!.Handle); if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) { return result; - } + } else if ((uint)showResult != (uint)UnmanagedMethods.HRESULT.S_OK) { throw new Win32Exception(showResult); } - if (openDialog?.AllowMultiple == true) + if (allowMultiple) { using var fileOpenDialog = frm.QueryInterface(); var shellItemArray = fileOpenDialog.Results; @@ -115,7 +171,8 @@ namespace Avalonia.Win32 results.Add(selected); } } - result = results.ToArray(); + + result = results; } else if (frm.Result is { } shellItem && GetAbsoluteFilePath(shellItem) is { } singleResult) @@ -127,71 +184,14 @@ namespace Avalonia.Win32 } catch (COMException ex) { - throw new Win32Exception(ex.HResult); + var message = new Win32Exception(ex.HResult).Message; + throw new COMException(message, ex); } })!; } - public unsafe Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) - { - return Task.Run(() => - { - 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 options = frm.Options; - options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | DefaultDialogOptions; - frm.SetOptions(options); - var title = dialog.Title ?? ""; - fixed (char* tExt = title) - { - frm.SetTitle(tExt); - } - - if (dialog.Directory != null) - { - 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); - } - } - - var showResult = frm.Show(hWnd); - - if ((uint)showResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED) - { - return result; - } - else if ((uint)showResult != (uint)UnmanagedMethods.HRESULT.S_OK) - { - throw new Win32Exception(showResult); - } - - if (frm.Result is not null) - { - result = GetAbsoluteFilePath(frm.Result); - } - - return result; - } - catch (COMException ex) - { - throw new Win32Exception(ex.HResult); - } - }); - } - - private unsafe string? GetAbsoluteFilePath(IShellItem shellItem) + private static unsafe string? GetAbsoluteFilePath(IShellItem shellItem) { var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH)); if (pszString != IntPtr.Zero) @@ -208,13 +208,13 @@ namespace Avalonia.Win32 return default; } - private unsafe byte[] FiltersToPointer(List? filters, out int lenght) + private static byte[] FiltersToPointer(IReadOnlyList? filters, out int length) { if (filters == null || filters.Count == 0) { - filters = new List + filters = new List { - new FileDialogFilter { Name = "All files", Extensions = new List { "*" } } + FilePickerFileTypes.All }; } @@ -225,13 +225,18 @@ namespace Avalonia.Win32 for (int i = 0; i < filters.Count; i++) { var filter = filters[i]; + if (filter.Patterns is null || !filter.Patterns.Any()) + { + continue; + } + var filterPtr = Marshal.AllocHGlobal(size); try { var filterStr = new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name ?? string.Empty, - pszSpec = string.Join(";", filter.Extensions.Select(e => "*." + e)) + pszSpec = string.Join(";", filter.Patterns) }; Marshal.StructureToPtr(filterStr, filterPtr, false); @@ -243,7 +248,7 @@ namespace Avalonia.Win32 } } - lenght = filters.Count; + length = filters.Count; return resultArr; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 8d836ef452..1a868d2325 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -22,6 +22,7 @@ using Avalonia.Win32.WinRT; using Avalonia.Win32.WinRT.Composition; using static Avalonia.Win32.Interop.UnmanagedMethods; using Avalonia.Metadata; +using Avalonia.Platform.Storage; namespace Avalonia.Win32 { @@ -31,7 +32,8 @@ namespace Avalonia.Win32 [Unstable] public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithNativeControlHost, - ITopLevelImplWithTextInputMethod + ITopLevelImplWithTextInputMethod, + ITopLevelImplWithStorageProvider { private static readonly List s_instances = new List(); @@ -152,6 +154,7 @@ namespace Avalonia.Win32 } Screen = new ScreenImpl(); + StorageProvider = new Win32StorageProvider(this); _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition); s_instances.Add(this); @@ -1385,6 +1388,8 @@ namespace Avalonia.Win32 public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current; + public IStorageProvider StorageProvider { get; } + private class WindowImplPlatformHandle : IPlatformNativeSurfaceHandle { private readonly WindowImpl _owner;