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 4a2f51397069..fd4a3a832e62 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 cc6e178658f1..a491c3df8d70 100644
--- a/src/Files.App/Strings/en-US/Resources.resw
+++ b/src/Files.App/Strings/en-US/Resources.resw
@@ -3814,4 +3814,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 05eaf6328782..fa75bcd22c14 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;
@@ -78,30 +79,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;