Skip to content

Commit 8fe155c

Browse files
authored
Feature: Added an action to open File Explorers properties window (#16031)
1 parent 57c029e commit 8fe155c

File tree

10 files changed

+101
-10
lines changed

10 files changed

+101
-10
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,4 @@ WindowsCreateString
120120
WindowsDeleteString
121121
IPreviewHandler
122122
AssocQueryString
123-
GetModuleHandle
123+
ShellExecuteEx
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2024 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using System.Runtime.InteropServices;
5+
using Windows.Win32;
6+
using Windows.Win32.UI.Shell;
7+
8+
namespace Files.App.Actions
9+
{
10+
internal sealed class OpenClassicPropertiesAction : ObservableObject, IAction
11+
{
12+
private readonly IContentPageContext context;
13+
14+
public string Label
15+
=> "OpenClassicProperties".GetLocalizedResource();
16+
17+
public string Description
18+
=> "OpenClassicPropertiesDescription".GetLocalizedResource();
19+
20+
public RichGlyph Glyph
21+
=> new(themedIconStyle: "App.ThemedIcons.Properties");
22+
23+
public HotKey HotKey
24+
=> new(Keys.Enter, KeyModifiers.AltShift);
25+
26+
public bool IsExecutable =>
27+
context.PageType is not ContentPageTypes.Home &&
28+
(context.HasSelection && context.SelectedItems.Count == 1 ||
29+
!context.HasSelection && context.PageType is not ContentPageTypes.SearchResults);
30+
31+
public OpenClassicPropertiesAction()
32+
{
33+
context = Ioc.Default.GetRequiredService<IContentPageContext>();
34+
35+
context.PropertyChanged += Context_PropertyChanged;
36+
}
37+
38+
public Task ExecuteAsync(object? parameter = null)
39+
{
40+
if (context.HasSelection && context?.SelectedItem?.ItemPath is not null)
41+
ExecuteShellCommand(context.SelectedItem.ItemPath);
42+
else if (context?.Folder?.ItemPath is not null)
43+
ExecuteShellCommand(context.Folder.ItemPath);
44+
45+
return Task.CompletedTask;
46+
}
47+
48+
private unsafe void ExecuteShellCommand(string itemPath)
49+
{
50+
SHELLEXECUTEINFOW info = default;
51+
info.cbSize = (uint)Marshal.SizeOf(info);
52+
info.nShow = 5; // SW_SHOW
53+
info.fMask = 0x0000000C;
54+
55+
var verb = "properties";
56+
fixed (char* cVerb = verb)
57+
info.lpVerb = cVerb;
58+
59+
fixed (char* lpFile = itemPath)
60+
info.lpFile = lpFile;
61+
62+
PInvoke.ShellExecuteEx(ref info);
63+
}
64+
65+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
66+
{
67+
switch (e.PropertyName)
68+
{
69+
case nameof(IContentPageContext.PageType):
70+
case nameof(IContentPageContext.HasSelection):
71+
case nameof(IContentPageContext.Folder):
72+
OnPropertyChanged(nameof(IsExecutable));
73+
break;
74+
}
75+
}
76+
}
77+
}

src/Files.App/Data/Commands/Manager/CommandCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public enum CommandCodes
105105
OpenInVSCode,
106106
OpenRepoInVSCode,
107107
OpenProperties,
108+
OpenClassicProperties,
108109
OpenSettings,
109110
OpenTerminal,
110111
OpenTerminalAsAdmin,

src/Files.App/Data/Commands/Manager/CommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public IRichCommand this[HotKey hotKey]
110110
public IRichCommand OpenInVSCode => commands[CommandCodes.OpenInVSCode];
111111
public IRichCommand OpenRepoInVSCode => commands[CommandCodes.OpenRepoInVSCode];
112112
public IRichCommand OpenProperties => commands[CommandCodes.OpenProperties];
113+
public IRichCommand OpenClassicProperties => commands[CommandCodes.OpenClassicProperties];
113114
public IRichCommand OpenSettings => commands[CommandCodes.OpenSettings];
114115
public IRichCommand OpenTerminal => commands[CommandCodes.OpenTerminal];
115116
public IRichCommand OpenTerminalAsAdmin => commands[CommandCodes.OpenTerminalAsAdmin];
@@ -299,6 +300,7 @@ public IEnumerator<IRichCommand> GetEnumerator() =>
299300
[CommandCodes.OpenInVSCode] = new OpenInVSCodeAction(),
300301
[CommandCodes.OpenRepoInVSCode] = new OpenRepoInVSCodeAction(),
301302
[CommandCodes.OpenProperties] = new OpenPropertiesAction(),
303+
[CommandCodes.OpenClassicProperties] = new OpenClassicPropertiesAction(),
302304
[CommandCodes.OpenSettings] = new OpenSettingsAction(),
303305
[CommandCodes.OpenTerminal] = new OpenTerminalAction(),
304306
[CommandCodes.OpenTerminalAsAdmin] = new OpenTerminalAsAdminAction(),

src/Files.App/Data/Commands/Manager/ICommandManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public interface ICommandManager : IEnumerable<IRichCommand>
9595
IRichCommand OpenInVSCode { get; }
9696
IRichCommand OpenRepoInVSCode { get; }
9797
IRichCommand OpenProperties { get; }
98+
IRichCommand OpenClassicProperties { get; }
9899
IRichCommand OpenSettings { get; }
99100
IRichCommand OpenTerminal { get; }
100101
IRichCommand OpenTerminalAsAdmin { get; }

src/Files.App/Data/Commands/Manager/IModifiableCommandManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ public interface IModifiableCommandManager : IEnumerable<IRichCommand>
1111

1212
IRichCommand PasteItem { get; }
1313
IRichCommand DeleteItem { get; }
14+
IRichCommand OpenProperties { get; }
1415
}
1516
}

src/Files.App/Data/Commands/Manager/ModifiableCommandManager.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License. See the LICENSE.
33

44
using System.Collections.Frozen;
5-
using System.Collections.Immutable;
65

76
namespace Files.App.Data.Commands
87
{
@@ -17,6 +16,7 @@ internal sealed class ModifiableCommandManager : IModifiableCommandManager
1716
public IRichCommand None => ModifiableCommands[CommandCodes.None];
1817
public IRichCommand PasteItem => ModifiableCommands[CommandCodes.PasteItem];
1918
public IRichCommand DeleteItem => ModifiableCommands[CommandCodes.DeleteItem];
19+
public IRichCommand OpenProperties => ModifiableCommands[CommandCodes.OpenProperties];
2020

2121
public ModifiableCommandManager()
2222
{
@@ -35,6 +35,9 @@ public ModifiableCommandManager()
3535
[CommandCodes.DeleteItem] = new ModifiableCommand(Commands.DeleteItem, new() {
3636
{ KeyModifiers.Shift, Commands.DeleteItemPermanently }
3737
}),
38+
[CommandCodes.OpenProperties] = new ModifiableCommand(Commands.OpenProperties, new() {
39+
{ KeyModifiers.Shift, Commands.OpenClassicProperties }
40+
}),
3841
}.ToFrozenDictionary();
3942
}
4043
}

src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,10 +490,10 @@ public static List<ContextMenuFlyoutItemViewModel> GetBaseItemMenuItems(
490490
IsVisible = itemsSelected,
491491
IsPrimary = true,
492492
}.Build(),
493-
new ContextMenuFlyoutItemViewModelBuilder(Commands.OpenProperties)
493+
new ContextMenuFlyoutItemViewModelBuilder(ModifiableCommands.OpenProperties)
494494
{
495495
IsPrimary = true,
496-
IsVisible = Commands.OpenProperties.IsExecutable
496+
IsVisible = ModifiableCommands.OpenProperties.IsExecutable
497497
}.Build(),
498498
new ContextMenuFlyoutItemViewModelBuilder(Commands.OpenParentFolder).Build(),
499499
new ContextMenuFlyoutItemViewModelBuilder(Commands.PinFolderToSidebar)

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,9 +3063,15 @@
30633063
<data name="OpenProperties" xml:space="preserve">
30643064
<value>Open properties</value>
30653065
</data>
3066+
<data name="OpenClassicProperties" xml:space="preserve">
3067+
<value>Open File Explorer properties</value>
3068+
</data>
30663069
<data name="OpenPropertiesDescription" xml:space="preserve">
30673070
<value>Open properties window</value>
30683071
</data>
3072+
<data name="OpenClassicPropertiesDescription" xml:space="preserve">
3073+
<value>Open File Explorer properties window</value>
3074+
</data>
30693075
<data name="Locals" xml:space="preserve">
30703076
<value>Locals</value>
30713077
</data>

src/Files.App/UserControls/InnerNavigationToolbar.xaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@
243243
AccessKey="O"
244244
AccessKeyInvoked="AppBarButton_AccessKeyInvoked"
245245
AutomationProperties.AutomationId="InnerNavigationToolbarPropertiesButton"
246-
Command="{x:Bind Commands.OpenProperties, Mode=OneWay}"
247-
IsEnabled="{x:Bind Commands.OpenProperties.IsExecutable, Mode=OneWay}"
248-
KeyboardAcceleratorTextOverride="{x:Bind Commands.OpenProperties.HotKeyText, Mode=OneWay}"
249-
Label="{x:Bind Commands.OpenProperties.Label}"
246+
Command="{x:Bind ModifiableCommands.OpenProperties, Mode=OneWay}"
247+
IsEnabled="{x:Bind ModifiableCommands.OpenProperties.IsExecutable, Mode=OneWay}"
248+
KeyboardAcceleratorTextOverride="{x:Bind ModifiableCommands.OpenProperties.HotKeyText, Mode=OneWay}"
249+
Label="{x:Bind ModifiableCommands.OpenProperties.Label}"
250250
LabelPosition="Collapsed"
251-
ToolTipService.ToolTip="{x:Bind Commands.OpenProperties.LabelWithHotKey, Mode=OneWay}">
252-
<controls:ThemedIcon Style="{x:Bind Commands.OpenProperties.ThemedIconStyle, Mode=OneTime}" />
251+
ToolTipService.ToolTip="{x:Bind ModifiableCommands.OpenProperties.LabelWithHotKey, Mode=OneWay}">
252+
<controls:ThemedIcon Style="{x:Bind ModifiableCommands.OpenProperties.ThemedIconStyle, Mode=OneTime}" />
253253
</AppBarButton>
254254

255255
<!-- (Divider) -->

0 commit comments

Comments
 (0)