diff --git a/src/Files.App/Data/Contracts/ICommonDialogService.cs b/src/Files.App/Data/Contracts/ICommonDialogService.cs new file mode 100644 index 000000000000..7362a786e495 --- /dev/null +++ b/src/Files.App/Data/Contracts/ICommonDialogService.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.Data.Contracts +{ + /// + /// Provides service to launch common dialog through Win32API. + /// + public interface ICommonDialogService + { + /// + /// Opens a common dialog called FileOpenDialog through native Win32API. + /// + /// The Window handle that the dialog launches based on. + /// The value that indicates whether the picker is only for folders. + /// The extension filters that the dialog uses to exclude unnecessary files.
The filter must have a pair:[ "Application", ".exe" ] + /// The file that that user chose. + /// + /// NOTE: There's a WinRT API to launch this dialog, but the API doesn't support windows that are launched by those who is in Administrators group or has broader privileges. + /// + /// True if the 'Open' button was clicked; otherwise, false. + bool Open_FileOpenDialog(nint hWnd, bool pickFoldersOnly, string[] filters, Environment.SpecialFolder defaultFolder, out string filePath); + + /// + /// Opens a common dialog called FileSaveDialog through native Win32API. + /// + /// The Window handle that the dialog launches based on. + /// The value that indicates whether the picker is only for folders. + /// The extension filters that the dialog uses to exclude unnecessary files.
The filter must have a pair:[ "Application", ".exe" ] + /// The file that that user chose. + /// + /// NOTE: There's a WinRT API to launch this dialog, but the API doesn't support windows that are launched by those who is in Administrators group or has broader privileges. + /// + /// True if the 'Open' button was clicked; otherwise, false. + bool Open_FileSaveDialog(nint hWnd, bool pickFoldersOnly, string[] filters, Environment.SpecialFolder defaultFolder, out string filePath); + + /// + /// Opens a common dialog called NetworkConnectionDialog through native Win32API. + /// + /// The value indicating whether to hide the check box allowing the user to restore the connection at logon. + /// The value indicating whether restore the connection at logon. + /// The value indicating whether to display a read-only path instead of allowing the user to type in a path. This is only valid if is not . + /// The name of the remote network. + /// The value indicating whether to enter the most recently used paths into the combination box. + /// True if the 'OK' button was clicked; otherwise, false. + bool Open_NetworkConnectionDialog(nint hWind, bool hideRestoreConnectionCheckBox = false, bool persistConnectionAtLogon = false, bool readOnlyPath = false, string? remoteNetworkName = null, bool useMostRecentPath = false); + } +} diff --git a/src/Files.App/Data/Contracts/IFileExplorerService.cs b/src/Files.App/Data/Contracts/IFileExplorerService.cs index 6c5008b1a008..773e1908371c 100644 --- a/src/Files.App/Data/Contracts/IFileExplorerService.cs +++ b/src/Files.App/Data/Contracts/IFileExplorerService.cs @@ -24,20 +24,5 @@ public interface IFileExplorerService /// A that cancels this action. /// A that represents the asynchronous operation. Task OpenInFileExplorerAsync(ILocatableFolder folder, CancellationToken cancellationToken = default); - - /// - /// Awaits the user input and picks single file from the file explorer dialog. - /// - /// The filter to apply when picking files. - /// A that cancels this action. - /// A that represents the asynchronous operation. If successful and a file has been picked, returns , otherwise null. - Task PickSingleFileAsync(IEnumerable? filter, CancellationToken cancellationToken = default); - - /// - /// Awaits the user input and picks single folder from the file explorer dialog. - /// - /// A that cancels this action. - /// A that represents the asynchronous operation. If successful and a folder has been picked, returns , otherwise null. - Task PickSingleFolderAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Files.App/Data/Models/NetworkConnectionDialog.cs b/src/Files.App/Data/Models/NetworkConnectionDialog.cs deleted file mode 100644 index e9d3c35ee553..000000000000 --- a/src/Files.App/Data/Models/NetworkConnectionDialog.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System.Runtime.InteropServices; -using System.Windows.Forms; -using Vanara.Extensions; -using Vanara.InteropServices; -using static Vanara.PInvoke.Mpr; - -namespace Files.App.Data.Models -{ - /// - /// A dialog box that allows the user to browse and connect to network resources. - /// - /// - /// Forked from Vanara. - /// - public sealed class NetworkConnectionDialog : CommonDialog - { - private readonly NETRESOURCE netRes = new(); - private CONNECTDLGSTRUCT dialogOptions; - - /// Initializes a new instance of the class. - public NetworkConnectionDialog() - { - dialogOptions.cbStructure = (uint)Marshal.SizeOf(typeof(CONNECTDLGSTRUCT)); - netRes.dwType = NETRESOURCEType.RESOURCETYPE_DISK; - } - - /// Gets the connected device number. This value is only valid after successfully running the dialog. - /// The connected device number. The value is 1 for A:, 2 for B:, 3 for C:, and so on. If the user made a deviceless connection, the value is –1. - [Browsable(false)] - public int ConnectedDeviceNumber => dialogOptions.dwDevNum; - - /// Gets or sets a value indicating whether to hide the check box allowing the user to restore the connection at logon. - /// true if hiding restore connection check box; otherwise, false. - [DefaultValue(false), Category("Appearance"), Description("Hide the check box allowing the user to restore the connection at logon.")] - public bool HideRestoreConnectionCheckBox - { - get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_HIDE_BOX); - set => dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_HIDE_BOX, value); - } - - /// Gets or sets a value indicating whether restore the connection at logon. - /// true to restore connection at logon; otherwise, false. - [DefaultValue(false), Category("Behavior"), Description("Restore the connection at logon.")] - public bool PersistConnectionAtLogon - { - get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_PERSIST); - set - { - dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_PERSIST, value); - dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_NOT_PERSIST, !value); - } - } - - /// - /// Gets or sets a value indicating whether to display a read-only path instead of allowing the user to type in a path. This is only - /// valid if is not . - /// - /// true to display a read only path; otherwise, false. - [DefaultValue(false), Category("Appearance"), Description("Display a read-only path instead of allowing the user to type in a path.")] - public bool ReadOnlyPath { get; set; } - - /// Gets or sets the name of the remote network. - /// The name of the remote network. - [DefaultValue(null), Category("Behavior"), Description("The value displayed in the path field.")] - public string RemoteNetworkName { get => netRes.lpRemoteName; set => netRes.lpRemoteName = value; } - - /// Gets or sets a value indicating whether to enter the most recently used paths into the combination box. - /// true to use MRU path; otherwise, false. - /// UseMostRecentPath - [DefaultValue(false), Category("Behavior"), Description("Enter the most recently used paths into the combination box.")] - public bool UseMostRecentPath - { - get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_USE_MRU); - set - { - if (value && !string.IsNullOrEmpty(RemoteNetworkName)) - throw new InvalidOperationException($"{nameof(UseMostRecentPath)} cannot be set to true if {nameof(RemoteNetworkName)} has a value."); - - dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_USE_MRU, value); - } - } - - /// - public override void Reset() - { - dialogOptions.dwDevNum = -1; - dialogOptions.dwFlags = 0; - dialogOptions.lpConnRes = IntPtr.Zero; - ReadOnlyPath = false; - } - - /// - protected override bool RunDialog(IntPtr hwndOwner) - { - using var lpNetResource = SafeCoTaskMemHandle.CreateFromStructure(netRes); - - dialogOptions.hwndOwner = hwndOwner; - dialogOptions.lpConnRes = lpNetResource.DangerousGetHandle(); - - if (ReadOnlyPath && !string.IsNullOrEmpty(netRes.lpRemoteName)) - dialogOptions.dwFlags |= CONN_DLG.CONNDLG_RO_PATH; - - var result = WNetConnectionDialog1(dialogOptions); - - dialogOptions.lpConnRes = IntPtr.Zero; - - if (result == unchecked((uint)-1)) - return false; - - result.ThrowIfFailed(); - - return true; - } - } -} diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index afd73bdb454c..26aa4b4a668d 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -164,6 +164,7 @@ public static IHost ConfigureHost() // Services .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Files.App/NativeMethods.txt b/src/Files.App/NativeMethods.txt index fcc7dcca3feb..d7956acc3785 100644 --- a/src/Files.App/NativeMethods.txt +++ b/src/Files.App/NativeMethods.txt @@ -46,6 +46,12 @@ DeleteFileFromApp RemoveDirectoryFromApp GetKeyState CreateDirectoryFromApp +CoCreateInstance +FileOpenDialog +IFileOpenDialog +SHCreateItemFromParsingName +FileSaveDialog +IFileSaveDialog D3D_DRIVER_TYPE D3D_FEATURE_LEVEL ID3D11Device @@ -53,4 +59,4 @@ ID3D11DeviceContext D3D11CreateDevice IDXGIDevice DCompositionCreateDevice -IDCompositionDevice \ No newline at end of file +IDCompositionDevice diff --git a/src/Files.App/Services/CommonDialogService.cs b/src/Files.App/Services/CommonDialogService.cs new file mode 100644 index 000000000000..3b7d19d868ea --- /dev/null +++ b/src/Files.App/Services/CommonDialogService.cs @@ -0,0 +1,284 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Vanara.Extensions; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; + +namespace Files.App.Services +{ + /// + public sealed class CommonDialogService : ICommonDialogService + { + /// + public bool Open_FileOpenDialog(nint hWnd, bool pickFoldersOnly, string[] filters, Environment.SpecialFolder defaultFolder, out string filePath) + { + filePath = string.Empty; + + try + { + unsafe + { + // Get a new instance of the dialog + PInvoke.CoCreateInstance( + typeof(FileOpenDialog).GUID, + null, + CLSCTX.CLSCTX_INPROC_SERVER, + out IFileOpenDialog dialog) + .ThrowOnFailure(); + + if (filters.Length is not 0 && filters.Length % 2 is 0) + { + List extensions = []; + + for (int i = 1; i < filters.Length; i += 2) + { + COMDLG_FILTERSPEC extension; + + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(filters[i]); + extension.pszName = (char*)Marshal.StringToHGlobalUni(filters[i - 1]); + + // Add to the exclusive extension list + extensions.Add(extension); + } + + // Set the file type using the extension list + dialog.SetFileTypes(extensions.ToArray()); + } + + // Get the default shell folder (My Computer) + PInvoke.SHCreateItemFromParsingName( + Environment.GetFolderPath(defaultFolder), + null, + typeof(IShellItem).GUID, + out var directoryShellItem) + .ThrowOnFailure(); + + // Folder picker + if (pickFoldersOnly) + { + dialog.SetOptions(FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS); + } + + // Set the default folder to open in the dialog + dialog.SetFolder((IShellItem)directoryShellItem); + dialog.SetDefaultFolder((IShellItem)directoryShellItem); + + // Show the dialog + dialog.Show(new HWND(hWnd)); + + // Get the file that user chose + dialog.GetResult(out var resultShellItem); + resultShellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out var lpFilePath); + filePath = lpFilePath.ToString(); + + return true; + } + } + catch (Exception ex) + { + App.Logger.LogError(ex, "Failed to open a common dialog called FileOpenDialog."); + + return false; + } + } + + /// + public bool Open_FileSaveDialog(nint hWnd, bool pickFoldersOnly, string[] filters, Environment.SpecialFolder defaultFolder, out string filePath) + { + filePath = string.Empty; + + try + { + unsafe + { + // Get a new instance of the dialog + PInvoke.CoCreateInstance( + typeof(FileSaveDialog).GUID, + null, + CLSCTX.CLSCTX_INPROC_SERVER, + out IFileSaveDialog dialog) + .ThrowOnFailure(); + + if (filters.Length is not 0 && filters.Length % 2 is 0) + { + List extensions = []; + + for (int i = 1; i < filters.Length; i += 2) + { + COMDLG_FILTERSPEC extension; + + extension.pszSpec = (char*)Marshal.StringToHGlobalUni(filters[i]); + extension.pszName = (char*)Marshal.StringToHGlobalUni(filters[i - 1]); + + // Add to the exclusive extension list + extensions.Add(extension); + } + + // Set the file type using the extension list + dialog.SetFileTypes(extensions.ToArray()); + } + + // Get the default shell folder (My Computer) + PInvoke.SHCreateItemFromParsingName( + Environment.GetFolderPath(defaultFolder), + null, + typeof(IShellItem).GUID, + out var directoryShellItem) + .ThrowOnFailure(); + + // Folder picker + if (pickFoldersOnly) + dialog.SetOptions(FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS); + + // Set the default folder to open in the dialog + dialog.SetFolder((IShellItem)directoryShellItem); + dialog.SetDefaultFolder((IShellItem)directoryShellItem); + + // Show the dialog + dialog.Show(new HWND(hWnd)); + + // Get the file that user chose + dialog.GetResult(out var resultShellItem); + resultShellItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out var lpFilePath); + filePath = lpFilePath.ToString(); + + return true; + } + } + catch (Exception ex) + { + App.Logger.LogError(ex, "Failed to open a common dialog called FileSaveDialog."); + + return false; + } + } + + /// + public bool Open_NetworkConnectionDialog(nint hWind, bool hideRestoreConnectionCheckBox = false, bool persistConnectionAtLogon = false, bool readOnlyPath = false, string? remoteNetworkName = null, bool useMostRecentPath = false) + { + using var dialog = new NetworkConnectionDialog() + { + HideRestoreConnectionCheckBox = hideRestoreConnectionCheckBox, + PersistConnectionAtLogon = persistConnectionAtLogon, + ReadOnlyPath = readOnlyPath, + RemoteNetworkName = remoteNetworkName!, + UseMostRecentPath = useMostRecentPath, + }; + + var window = Win32Helper.Win32Window.FromLong(hWind.ToInt64()); + + return dialog.ShowDialog(window) == System.Windows.Forms.DialogResult.OK; + } + + private sealed class NetworkConnectionDialog : CommonDialog + { + private readonly Vanara.PInvoke.Mpr.NETRESOURCE netRes = new(); + private Vanara.PInvoke.Mpr.CONNECTDLGSTRUCT dialogOptions; + + /// + /// Initializes a new instance of the class. + /// + public NetworkConnectionDialog() + { + dialogOptions.cbStructure = (uint)Marshal.SizeOf(typeof(Vanara.PInvoke.Mpr.CONNECTDLGSTRUCT)); + netRes.dwType = Vanara.PInvoke.Mpr.NETRESOURCEType.RESOURCETYPE_DISK; + } + + /// Gets the connected device number. This value is only valid after successfully running the dialog. + /// The connected device number. The value is 1 for A:, 2 for B:, 3 for C:, and so on. If the user made a deviceless connection, the value is –1. + [Browsable(false)] + public int ConnectedDeviceNumber + => dialogOptions.dwDevNum; + + /// Gets or sets a value indicating whether to hide the check box allowing the user to restore the connection at logon. + /// true if hiding restore connection check box; otherwise, false. + [DefaultValue(false), Category("Appearance"), Description("Hide the check box allowing the user to restore the connection at logon.")] + public bool HideRestoreConnectionCheckBox + { + get => dialogOptions.dwFlags.IsFlagSet(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_HIDE_BOX); + set => dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_HIDE_BOX, value); + } + + /// Gets or sets a value indicating whether restore the connection at logon. + /// true to restore connection at logon; otherwise, false. + [DefaultValue(false), Category("Behavior"), Description("Restore the connection at logon.")] + public bool PersistConnectionAtLogon + { + get => dialogOptions.dwFlags.IsFlagSet(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_PERSIST); + set + { + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_PERSIST, value); + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_NOT_PERSIST, !value); + } + } + + /// + /// Gets or sets a value indicating whether to display a read-only path instead of allowing the user to type in a path. This is only + /// valid if is not . + /// + /// true to display a read only path; otherwise, false. + [DefaultValue(false), Category("Appearance"), Description("Display a read-only path instead of allowing the user to type in a path.")] + public bool ReadOnlyPath { get; set; } + + /// Gets or sets the name of the remote network. + /// The name of the remote network. + [DefaultValue(null), Category("Behavior"), Description("The value displayed in the path field.")] + public string RemoteNetworkName { get => netRes.lpRemoteName; set => netRes.lpRemoteName = value; } + + /// Gets or sets a value indicating whether to enter the most recently used paths into the combination box. + /// true to use MRU path; otherwise, false. + /// UseMostRecentPath + [DefaultValue(false), Category("Behavior"), Description("Enter the most recently used paths into the combination box.")] + public bool UseMostRecentPath + { + get => dialogOptions.dwFlags.IsFlagSet(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_USE_MRU); + set + { + if (value && !string.IsNullOrEmpty(RemoteNetworkName)) + throw new InvalidOperationException($"{nameof(UseMostRecentPath)} cannot be set to true if {nameof(RemoteNetworkName)} has a value."); + + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_USE_MRU, value); + } + } + + /// + public override void Reset() + { + dialogOptions.dwDevNum = -1; + dialogOptions.dwFlags = 0; + dialogOptions.lpConnRes = IntPtr.Zero; + ReadOnlyPath = false; + } + + /// + protected override bool RunDialog(IntPtr hwndOwner) + { + using var lpNetResource = Vanara.InteropServices.SafeCoTaskMemHandle.CreateFromStructure(netRes); + + dialogOptions.hwndOwner = hwndOwner; + dialogOptions.lpConnRes = lpNetResource.DangerousGetHandle(); + + if (ReadOnlyPath && !string.IsNullOrEmpty(netRes.lpRemoteName)) + dialogOptions.dwFlags |= Vanara.PInvoke.Mpr.CONN_DLG.CONNDLG_RO_PATH; + + var result = Vanara.PInvoke.Mpr.WNetConnectionDialog1(dialogOptions); + + dialogOptions.lpConnRes = IntPtr.Zero; + + if (result == unchecked((uint)-1)) + return false; + + result.ThrowIfFailed(); + + return true; + } + } + } +} diff --git a/src/Files.App/Services/FileExplorerService.cs b/src/Files.App/Services/FileExplorerService.cs index 9b69e1321c00..96efde0d2411 100644 --- a/src/Files.App/Services/FileExplorerService.cs +++ b/src/Files.App/Services/FileExplorerService.cs @@ -19,53 +19,5 @@ public Task OpenAppFolderAsync(CancellationToken cancellationToken = default) /// public Task OpenInFileExplorerAsync(ILocatableFolder folder, CancellationToken cancellationToken = default) => Launcher.LaunchFolderPathAsync(folder.Path).AsTask(cancellationToken); - - /// - public async Task PickSingleFileAsync(IEnumerable? filter, CancellationToken cancellationToken = default) - { - var filePicker = InitializeWithWindow(new FileOpenPicker()); - - if (filter is not null) - { - filePicker.FileTypeFilter.EnumeratedAdd(filter); - } - else - { - filePicker.FileTypeFilter.Add("*"); - } - - var fileTask = filePicker.PickSingleFileAsync().AsTask(cancellationToken); - var file = await fileTask; - - return file is null ? null : new WindowsStorageFile(file); - } - - // WINUI3 - private FileOpenPicker InitializeWithWindow(FileOpenPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - return obj; - } - - /// - public async Task PickSingleFolderAsync(CancellationToken cancellationToken = default) - { - var folderPicker = InitializeWithWindow(new FolderPicker()); - - folderPicker.FileTypeFilter.Add("*"); - - var folderTask = folderPicker.PickSingleFolderAsync().AsTask(cancellationToken); - var folder = await folderTask; - - return folder is null ? null : new WindowsStorageFolder(folder); - } - - // WINUI3 - private FolderPicker InitializeWithWindow(FolderPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - - return obj; - } } } diff --git a/src/Files.App/Services/NetworkDrivesService.cs b/src/Files.App/Services/NetworkDrivesService.cs index 953de9041d13..e5896e3f4873 100644 --- a/src/Files.App/Services/NetworkDrivesService.cs +++ b/src/Files.App/Services/NetworkDrivesService.cs @@ -12,8 +12,11 @@ namespace Files.App.Services { public sealed class NetworkDrivesService : ObservableObject, INetworkDrivesService { + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); + private readonly static string guid = "::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}"; + private ObservableCollection _Drives; /// public ObservableCollection Drives @@ -108,17 +111,12 @@ public bool DisconnectNetworkDrive(ILocatableFolder drive) /// public Task OpenMapNetworkDriveDialogAsync() { - var hWnd = MainWindow.Instance.WindowHandle.ToInt64(); - return Win32Helper.StartSTATask(() => { - using var ncd = new NetworkConnectionDialog - { - UseMostRecentPath = true, - HideRestoreConnectionCheckBox = false - }; - - return ncd.ShowDialog(Win32Helper.Win32Window.FromLong(hWnd)) == System.Windows.Forms.DialogResult.OK; + return CommonDialogService.Open_NetworkConnectionDialog( + MainWindow.Instance.WindowHandle, + useMostRecentPath: true, + hideRestoreConnectionCheckBox: false); }); } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 56cb01e10418..536cf9cc3678 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3810,4 +3810,20 @@ Show for Git repos Setting where users can choose to display "Open IDE" button for all locations. + + Application extension + This is the friendly name for DLL files. + + + ICO File + This is the friendly name for ICO files. + + + Zip File + This is the friendly name for ZIP files. + + + Bitmap Files + This is the friendly name for bitmap files. + \ No newline at end of file diff --git a/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs index 3021aa78a99c..827f4c9d2758 100644 --- a/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/CreateShortcutDialogViewModel.cs @@ -108,12 +108,6 @@ private Task SelectDestination() return Task.CompletedTask; } - private FolderPicker InitializeWithWindow(FolderPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - return obj; - } - private async Task CreateShortcutAsync() { string? destinationName; diff --git a/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs index 64d410ef3840..4d18c9f98203 100644 --- a/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/DecompressArchiveDialogViewModel.cs @@ -10,6 +10,8 @@ namespace Files.App.ViewModels.Dialogs { public sealed class DecompressArchiveDialogViewModel : ObservableObject { + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); + private readonly IStorageFile archive; public StorageFolder DestinationFolder { get; private set; } @@ -60,21 +62,12 @@ public DecompressArchiveDialogViewModel(IStorageFile archive) private async Task SelectDestinationAsync() { - FolderPicker folderPicker = InitializeWithWindow(new FolderPicker()); - folderPicker.FileTypeFilter.Add("*"); - - DestinationFolder = await folderPicker.PickSingleFolderAsync(); + CommonDialogService.Open_FileOpenDialog(MainWindow.Instance.WindowHandle, true, [], Environment.SpecialFolder.Desktop, out var filePath); + DestinationFolder = await StorageHelpers.ToStorageItem(filePath); DestinationFolderPath = (DestinationFolder is not null) ? DestinationFolder.Path : DefaultDestinationFolderPath(); } - // WINUI3 - private FolderPicker InitializeWithWindow(FolderPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - return obj; - } - private string DefaultDestinationFolderPath() { return Path.Combine(Path.GetDirectoryName(archive.Path), Path.GetFileNameWithoutExtension(archive.Path)); diff --git a/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs b/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs index 39e49d55de1b..74b833b3ad51 100644 --- a/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs +++ b/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs @@ -7,13 +7,13 @@ using Microsoft.UI.Xaml.Navigation; using TagLib; using Windows.Storage; -using Windows.Storage.FileProperties; -using Windows.Storage.Pickers; namespace Files.App.ViewModels.Properties { public abstract class BasePropertiesPage : Page, IDisposable { + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); + public IShellPage AppInstance = null; public BaseProperties BaseProperties { get; set; } @@ -81,29 +81,28 @@ protected override void OnNavigatedTo(NavigationEventArgs e) ViewModel.EditAlbumCoverCommand = new RelayCommand(async () => { - FileOpenPicker filePicker = new FileOpenPicker(); - filePicker.FileTypeFilter.Add(".jpg"); - filePicker.FileTypeFilter.Add(".jpeg"); - filePicker.FileTypeFilter.Add(".bmp"); - filePicker.FileTypeFilter.Add(".png"); - - var parentWindowId = np.Window.AppWindow.Id; - var handle = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId); - WinRT.Interop.InitializeWithWindow.Initialize(filePicker, handle); + var hWnd = Microsoft.UI.Win32Interop.GetWindowFromWindowId(np.Window.AppWindow.Id); - StorageFile file = await filePicker.PickSingleFileAsync(); + string[] extensions = + [ + "BitmapFiles".GetLocalizedResource(), "*.bmp", + "JPEG", "*.jpg;*.jpeg", + "PNG", "*.png", + ]; - if (file is not null) + var result = CommonDialogService.Open_FileOpenDialog(hWnd, false, extensions, Environment.SpecialFolder.Desktop, out var filePath); + if (result) { ViewModel.IsAblumCoverModified = true; - ViewModel.ModifiedAlbumCover = new Picture(file.Path); + ViewModel.ModifiedAlbumCover = new Picture(filePath); - var result = await FileThumbnailHelper.GetIconAsync( - file.Path, + var iconData = await FileThumbnailHelper.GetIconAsync( + filePath, Constants.ShellIconSizes.ExtraLarge, false, IconOptions.UseCurrentScale); - ViewModel.IconData = result; + + ViewModel.IconData = iconData; } }); diff --git a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs index 4cfdc9c2052e..c0b704129c87 100644 --- a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs +++ b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs @@ -3,11 +3,14 @@ using System.IO; using Windows.Storage.Pickers; using Microsoft.UI.Windowing; +using System.Windows.Input; namespace Files.App.ViewModels.Properties { public sealed class CustomizationViewModel : ObservableObject { + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); + private static string DefaultIconDllFilePath => Path.Combine(Constants.UserEnvironmentPaths.SystemRootPath, "System32", "SHELL32.dll"); @@ -41,8 +44,8 @@ public IconFileInfo? SelectedDllIcon } } - public IRelayCommand RestoreDefaultIconCommand { get; private set; } - public IAsyncRelayCommand OpenFilePickerCommand { get; private set; } + public ICommand RestoreDefaultIconCommand { get; private set; } + public ICommand OpenFilePickerCommand { get; private set; } public CustomizationViewModel(IShellPage appInstance, BaseProperties baseProperties, AppWindow appWindow) { @@ -66,40 +69,31 @@ public CustomizationViewModel(IShellPage appInstance, BaseProperties basePropert // Get default LoadIconsForPath(IconResourceItemPath); - RestoreDefaultIconCommand = new RelayCommand(ExecuteRestoreDefaultIcon); - OpenFilePickerCommand = new AsyncRelayCommand(ExecuteOpenFilePickerAsync); + RestoreDefaultIconCommand = new RelayCommand(ExecuteRestoreDefaultIconCommand); + OpenFilePickerCommand = new RelayCommand(ExecuteOpenFilePickerCommand); } - private void ExecuteRestoreDefaultIcon() + private void ExecuteRestoreDefaultIconCommand() { SelectedDllIcon = null; _isIconChanged = true; } - private async Task ExecuteOpenFilePickerAsync() + private void ExecuteOpenFilePickerCommand() { - // Initialize picker - FileOpenPicker picker = new() - { - SuggestedStartLocation = PickerLocationId.ComputerFolder, - ViewMode = PickerViewMode.Thumbnail, - }; - - picker.FileTypeFilter.Add(".dll"); - picker.FileTypeFilter.Add(".exe"); - picker.FileTypeFilter.Add(".ico"); - - // WINUI3: Create and initialize new window var parentWindowId = _appWindow.Id; - var handle = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId); - WinRT.Interop.InitializeWithWindow.Initialize(picker, handle); - - // Open picker - var file = await picker.PickSingleFileAsync(); - if (file is null) - return; - - LoadIconsForPath(file.Path); + var hWnd = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId); + + string[] extensions = + [ + "ApplicationExtension".GetLocalizedResource(), "*.dll", + "Application".GetLocalizedResource(), "*.exe", + "IcoFileCapitalized".GetLocalizedResource(), "*.ico", + ]; + + var result = CommonDialogService.Open_FileOpenDialog(hWnd, false, extensions, Environment.SpecialFolder.MyComputer, out var filePath); + if (result) + LoadIconsForPath(filePath); } public async Task UpdateIcon() diff --git a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs index 36ccb3cdce31..f67dde9f838a 100644 --- a/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs +++ b/src/Files.App/ViewModels/Settings/AdvancedViewModel.cs @@ -17,6 +17,7 @@ namespace Files.App.ViewModels.Settings public sealed class AdvancedViewModel : ObservableObject { private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); private readonly IFileTagsSettingsService fileTagsSettingsService = Ioc.Default.GetRequiredService(); @@ -204,11 +205,10 @@ private async Task ImportSettingsAsync() private async Task ExportSettingsAsync() { - FileSavePicker filePicker = InitializeWithWindow(new FileSavePicker()); - filePicker.FileTypeChoices.Add("Zip File", [".zip"]); - filePicker.SuggestedFileName = $"Files_{AppLifecycleHelper.AppVersion}"; + string[] extensions = ["ZipFileCapitalized".GetLocalizedResource(), "*.zip" ]; + CommonDialogService.Open_FileSaveDialog(MainWindow.Instance.WindowHandle, false, extensions, Environment.SpecialFolder.Desktop, out var filePath); - StorageFile file = await filePicker.PickSaveFileAsync(); + var file = await StorageHelpers.ToStorageItem(filePath); if (file is not null) { try diff --git a/src/Files.App/ViewModels/Settings/AppearanceViewModel.cs b/src/Files.App/ViewModels/Settings/AppearanceViewModel.cs index c2636af506c6..450edf51748a 100644 --- a/src/Files.App/ViewModels/Settings/AppearanceViewModel.cs +++ b/src/Files.App/ViewModels/Settings/AppearanceViewModel.cs @@ -14,6 +14,7 @@ namespace Files.App.ViewModels.Settings public sealed class AppearanceViewModel : ObservableObject { private IAppThemeModeService AppThemeModeService { get; } = Ioc.Default.GetRequiredService(); + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); private readonly IUserSettingsService UserSettingsService; private readonly IResourcesService ResourcesService; @@ -80,30 +81,29 @@ public AppearanceViewModel(IUserSettingsService userSettingsService, IResourcesS UpdateSelectedResource(); - SelectImageCommand = new AsyncRelayCommand(SelectBackgroundImage); + SelectImageCommand = new RelayCommand(SelectBackgroundImage); RemoveImageCommand = new RelayCommand(RemoveBackgroundImage); } /// /// Opens a file picker to select a background image /// - private async Task SelectBackgroundImage() + private void SelectBackgroundImage() { - var filePicker = new FileOpenPicker - { - ViewMode = PickerViewMode.Thumbnail, - SuggestedStartLocation = PickerLocationId.PicturesLibrary, - FileTypeFilter = { ".png", ".bmp", ".jpg", ".jpeg", ".jfif", ".gif", ".tiff", ".tif", ".webp" } - }; - - // WINUI3: Create and initialize new window - var parentWindowId = MainWindow.Instance.AppWindow.Id; - var handle = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId); - WinRT.Interop.InitializeWithWindow.Initialize(filePicker, handle); - - var file = await filePicker.PickSingleFileAsync(); - if (file is not null) - AppThemeBackgroundImageSource = file.Path; + string[] extensions = + [ + "BitmapFiles".GetLocalizedResource(), "*.bmp;*.dib", + "JPEG", "*.jpg;*.jpeg;*.jpe;*.jfif", + "GIF", "*.gif", + "TIFF", "*.tif;*.tiff", + "PNG", "*.png", + "HEIC", "*.heic;*.hif", + "WEBP", "*.webp", + ]; + + var result = CommonDialogService.Open_FileOpenDialog(MainWindow.Instance.WindowHandle, false, extensions, Environment.SpecialFolder.MyPictures, out var filePath); + if (result) + AppThemeBackgroundImageSource = filePath; } /// diff --git a/src/Files.App/ViewModels/Settings/GeneralViewModel.cs b/src/Files.App/ViewModels/Settings/GeneralViewModel.cs index cfa12ba59c89..628261675472 100644 --- a/src/Files.App/ViewModels/Settings/GeneralViewModel.cs +++ b/src/Files.App/ViewModels/Settings/GeneralViewModel.cs @@ -15,12 +15,13 @@ namespace Files.App.ViewModels.Settings public sealed class GeneralViewModel : ObservableObject, IDisposable { private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); private bool disposed; private ReadOnlyCollection addFlyoutItemsSource; - public AsyncRelayCommand ChangePageCommand { get; } + public RelayCommand ChangePageCommand { get; } public RelayCommand RemovePageCommand { get; } public RelayCommand AddPageCommand { get; } public RelayCommand RestartCommand { get; } @@ -89,7 +90,7 @@ public int SelectedAppLanguageIndex public GeneralViewModel() { - ChangePageCommand = new AsyncRelayCommand(ChangePageAsync); + ChangePageCommand = new RelayCommand(ChangePageAsync); RemovePageCommand = new RelayCommand(RemovePage); AddPageCommand = new RelayCommand(async (path) => await AddPageAsync(path)); RestartCommand = new RelayCommand(DoRestartAsync); @@ -315,25 +316,11 @@ public bool AlwaysOpenDualPaneInNewTab } } - private async Task ChangePageAsync() + private void ChangePageAsync() { - var folderPicker = InitializeWithWindow(new FolderPicker()); - folderPicker.FileTypeFilter.Add("*"); - StorageFolder folder = await folderPicker.PickSingleFolderAsync(); - - if (folder is not null) - { - if (SelectedPageIndex >= 0) - PagesOnStartupList[SelectedPageIndex] = new PageOnStartupViewModel(folder.Path); - } - } - - // WINUI3 - private FolderPicker InitializeWithWindow(FolderPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - - return obj; + var result = CommonDialogService.Open_FileOpenDialog(MainWindow.Instance.WindowHandle, true, [], Environment.SpecialFolder.Desktop, out var filePath); + if (result && SelectedPageIndex >= 0) + PagesOnStartupList[SelectedPageIndex] = new PageOnStartupViewModel(filePath); } private void RemovePage(PageOnStartupViewModel page) @@ -345,15 +332,12 @@ private async Task AddPageAsync(string path = null) { if (string.IsNullOrWhiteSpace(path)) { - var folderPicker = InitializeWithWindow(new FolderPicker()); - folderPicker.FileTypeFilter.Add("*"); + CommonDialogService.Open_FileOpenDialog(MainWindow.Instance.WindowHandle, true, [], Environment.SpecialFolder.Desktop, out var filePath); - var folder = await folderPicker.PickSingleFolderAsync(); - if (folder is not null) - path = folder.Path; + path = filePath; } - if (path is not null && PagesOnStartupList is not null) + if (!string.IsNullOrEmpty(path) && PagesOnStartupList is not null) PagesOnStartupList.Add(new PageOnStartupViewModel(path)); } diff --git a/src/Files.App/Views/Properties/LibraryPage.xaml.cs b/src/Files.App/Views/Properties/LibraryPage.xaml.cs index 3eccdf7d8e9f..e00ec2f4d373 100644 --- a/src/Files.App/Views/Properties/LibraryPage.xaml.cs +++ b/src/Files.App/Views/Properties/LibraryPage.xaml.cs @@ -16,11 +16,14 @@ using System.Threading.Tasks; using System.Windows.Input; using Windows.Storage.Pickers; +using Windows.Storage; namespace Files.App.Views.Properties { public sealed partial class LibraryPage : BasePropertiesPage, INotifyPropertyChanged { + private ICommonDialogService CommonDialogService { get; } = Ioc.Default.GetRequiredService(); + public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") @@ -100,10 +103,11 @@ protected override void Properties_Loaded(object sender, RoutedEventArgs e) private async Task AddLocationAsync() { - var folderPicker = InitializeWithWindow(new FolderPicker()); - folderPicker.FileTypeFilter.Add("*"); + var result = CommonDialogService.Open_FileOpenDialog(MainWindow.Instance.WindowHandle, true, [], Environment.SpecialFolder.Desktop, out var filePath); + if (!result) + return; - var folder = await folderPicker.PickSingleFolderAsync(); + var folder = await StorageHelpers.ToStorageItem(filePath); if (folder is not null && !Folders.Any((f) => string.Equals(folder.Path, f.Path, StringComparison.OrdinalIgnoreCase))) { bool isDefault = Folders.Count == 0; @@ -115,13 +119,6 @@ private async Task AddLocationAsync() } } - // WINUI3 - private FolderPicker InitializeWithWindow(FolderPicker obj) - { - WinRT.Interop.InitializeWithWindow.Initialize(obj, MainWindow.Instance.WindowHandle); - return obj; - } - private void SetDefaultLocation() { int index = SelectedFolderIndex;