diff --git a/src/Files.App/Actions/Navigation/NextTabAction.cs b/src/Files.App/Actions/Navigation/NextTabAction.cs index 9a34d91a7ddf..b533d4d3cc22 100644 --- a/src/Files.App/Actions/Navigation/NextTabAction.cs +++ b/src/Files.App/Actions/Navigation/NextTabAction.cs @@ -9,6 +9,7 @@ namespace Files.App.Actions internal sealed partial class NextTabAction : ObservableObject, IAction { private readonly IMultitaskingContext multitaskingContext; + private readonly IContentPageContext contentPageContext = Ioc.Default.GetRequiredService(); public string Label => Strings.NextTab.GetLocalizedResource(); @@ -37,7 +38,7 @@ public async Task ExecuteAsync(object? parameter = null) await Task.Delay(500); // Focus the content of the selected tab item (needed for keyboard navigation) - (multitaskingContext.CurrentTabItem.TabItemContent as Control)?.Focus(FocusState.Programmatic); + contentPageContext.ShellPage!.PaneHolder.FocusActivePane(); } private void MultitaskingContext_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Actions/Navigation/PreviousTabAction.cs b/src/Files.App/Actions/Navigation/PreviousTabAction.cs index c393ae86055b..dbbb49ec4882 100644 --- a/src/Files.App/Actions/Navigation/PreviousTabAction.cs +++ b/src/Files.App/Actions/Navigation/PreviousTabAction.cs @@ -9,6 +9,7 @@ namespace Files.App.Actions internal sealed partial class PreviousTabAction : ObservableObject, IAction { private readonly IMultitaskingContext multitaskingContext; + private readonly IContentPageContext contentPageContext = Ioc.Default.GetRequiredService(); public string Label => Strings.PreviousTab.GetLocalizedResource(); @@ -40,7 +41,7 @@ public async Task ExecuteAsync(object? parameter = null) await Task.Delay(500); // Focus the content of the selected tab item (needed for keyboard navigation) - (multitaskingContext.CurrentTabItem.TabItemContent as Control)?.Focus(FocusState.Programmatic); + contentPageContext.ShellPage!.PaneHolder.FocusActivePane(); } private void MultitaskingContext_PropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Files.App/Data/Contracts/IShellPanesPage.cs b/src/Files.App/Data/Contracts/IShellPanesPage.cs index 91632061bbcf..917b7b57110d 100644 --- a/src/Files.App/Data/Contracts/IShellPanesPage.cs +++ b/src/Files.App/Data/Contracts/IShellPanesPage.cs @@ -69,6 +69,11 @@ public interface IShellPanesPage : IDisposable, INotifyPropertyChanged /// public void FocusActivePane(); + /// + /// Locks the active pane. + /// + public void LockActivePane(); + /// /// Gets open panes. /// diff --git a/src/Files.App/UserControls/TabBar/TabBar.xaml b/src/Files.App/UserControls/TabBar/TabBar.xaml index 2e02a641a7d2..94613713f1c2 100644 --- a/src/Files.App/UserControls/TabBar/TabBar.xaml +++ b/src/Files.App/UserControls/TabBar/TabBar.xaml @@ -145,7 +145,7 @@ x:Name="SplitPaneMenuItem" x:Load="{x:Bind Commands.SplitPaneHorizontally.IsExecutable, Mode=OneWay}" Text="{helpers:ResourceString Name=SplitPane}"> - + - + @@ -699,6 +702,7 @@ diff --git a/src/Files.App/Views/MainPage.xaml.cs b/src/Files.App/Views/MainPage.xaml.cs index f8f71da7d6b2..da9dc95347b8 100644 --- a/src/Files.App/Views/MainPage.xaml.cs +++ b/src/Files.App/Views/MainPage.xaml.cs @@ -162,7 +162,6 @@ public async void MultitaskingControl_CurrentInstanceChanged(object? sender, Cur // Focus the content of the selected tab item (this also avoids an issue where the Omnibar sometimes steals the focus) await Task.Delay(100); ContentPageContext.ShellPage!.PaneHolder.FocusActivePane(); - } private void PaneHolder_PropertyChanged(object? sender, PropertyChangedEventArgs e) @@ -477,5 +476,12 @@ private void SettingsButton_AccessKeyInvoked(UIElement sender, AccessKeyInvokedE if (VisualTreeHelper.GetOpenPopupsForXamlRoot(MainWindow.Instance.Content.XamlRoot).Any()) args.Handled = true; } + + private void Page_PointerReleased(object sender, PointerRoutedEventArgs e) + { + // Workaround for issue where clicking an empty area in the window (toolbar, title bar etc) prevents keyboard + // shortcuts from working properly, see https://github.com/microsoft/microsoft-ui-xaml/issues/6467 + DispatcherQueue.TryEnqueue(() => ContentPageContext.ShellPage?.PaneHolder.FocusActivePane()); + } } } \ No newline at end of file diff --git a/src/Files.App/Views/ShellPanesPage.xaml.cs b/src/Files.App/Views/ShellPanesPage.xaml.cs index 204bdde6d1cd..f2d8d2a3524b 100644 --- a/src/Files.App/Views/ShellPanesPage.xaml.cs +++ b/src/Files.App/Views/ShellPanesPage.xaml.cs @@ -20,6 +20,7 @@ public sealed partial class ShellPanesPage : Page, IShellPanesPage, ITabBarItemC // Dependency injections private IGeneralSettingsService GeneralSettingsService { get; } = Ioc.Default.GetRequiredService(); + private IContentPageContext ContentPageContext { get; } = Ioc.Default.GetRequiredService(); private AppModel AppModel { get; } = Ioc.Default.GetRequiredService(); // Constants @@ -228,6 +229,17 @@ public bool IsCurrentInstance } } + private bool _IsActivePaneLocked; + public bool IsActivePaneLocked + { + get => _IsActivePaneLocked; + set + { + if (_IsActivePaneLocked != value) + _IsActivePaneLocked = value; + } + } + // Events public static event EventHandler? CurrentInstanceChanged; @@ -376,6 +388,16 @@ public void FocusActivePane() GetPane(0)?.Focus(FocusState.Programmatic); else GetPane(1)?.Focus(FocusState.Programmatic); + + // Focus file list + if (ActivePane is BaseShellPage baseShellPage) + baseShellPage.ContentPage?.ItemManipulationModel.FocusFileList(); + } + + /// + public void LockActivePane() + { + IsActivePaneLocked = true; } /// @@ -656,9 +678,16 @@ private void Pane_Loaded(object sender, RoutedEventArgs e) private void Pane_GettingFocus(UIElement sender, GettingFocusEventArgs args) { - // Workaround for https://github.com/files-community/Files/issues/15397 - if (args?.NewFocusedElement is not null && args.NewFocusedElement is not (ListViewItem or GridViewItem or ListView or GridView or TextBox)) + // Cancel focus attempts while the active pane is locked during layout changes. + // Pane locking occurs in LayoutModeChangeRequested() in ShellViewModel.cs. + // Focus is restored in RefreshItem() in BaseLayoutPage.cs when file loading completes. + // See https://github.com/files-community/Files/issues/15397 + // See https://github.com/files-community/Files/issues/16530 + if (IsActivePaneLocked) + { + IsActivePaneLocked = false; args.TryCancel(); + } } private void Pane_ContentChanged(object? sender, TabBarItemParameter e)