diff --git a/src/Controls/src/Core/ShellToolbar.cs b/src/Controls/src/Core/ShellToolbar.cs index 84af6e3fffee..f887035a275d 100644 --- a/src/Controls/src/Core/ShellToolbar.cs +++ b/src/Controls/src/Core/ShellToolbar.cs @@ -1,5 +1,8 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Text; using System.Windows.Input; @@ -10,8 +13,8 @@ namespace Microsoft.Maui.Controls internal class ShellToolbar : Toolbar { Shell _shell; - Page _currentPage; - BackButtonBehavior _backButtonBehavior; + Page? _currentPage; + BackButtonBehavior? _backButtonBehavior; ToolbarTracker _toolbarTracker = new ToolbarTracker(); #if WINDOWS MenuBarTracker _menuBarTracker = new MenuBarTracker(); @@ -77,7 +80,7 @@ internal void ApplyChanges() _menuBarTracker.Target = _shell; #endif - Page previousPage = null; + Page? previousPage = null; if (stack.Count > 1) previousPage = stack[stack.Count - 1]; @@ -95,13 +98,6 @@ internal void ApplyChanges() BackButtonVisible = backButtonVisible && stack.Count > 1; BackButtonEnabled = _backButtonBehavior?.IsEnabled ?? true; - if (_backButtonBehavior?.Command != null) - { - BackButtonEnabled = - BackButtonEnabled && - _backButtonBehavior.Command.CanExecute(_backButtonBehavior.CommandParameter); - } - UpdateTitle(); if (_currentPage != null && @@ -131,7 +127,6 @@ internal void ApplyChanges() DynamicOverflowEnabled = PlatformConfiguration.WindowsSpecific.Page.GetToolbarDynamicOverflowEnabled(currentPage); } - ICommand _backButtonCommand; void UpdateBackbuttonBehavior() { var bbb = Shell.GetBackButtonBehavior(_currentPage); @@ -140,37 +135,20 @@ void UpdateBackbuttonBehavior() return; if (_backButtonBehavior != null) - _backButtonBehavior.PropertyChanged -= OnBackButtonPropertyChanged; + _backButtonBehavior.PropertyChanged -= OnBackButtonCommandPropertyChanged; _backButtonBehavior = bbb; if (_backButtonBehavior != null) - _backButtonBehavior.PropertyChanged += OnBackButtonPropertyChanged; - - void OnBackButtonPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - ApplyChanges(); - - if (_backButtonBehavior.Command == _backButtonCommand) - return; - - if (_backButtonCommand != null) - _backButtonCommand.CanExecuteChanged -= OnBackButtonCanExecuteChanged; - - _backButtonCommand = _backButtonBehavior.Command; - - if (_backButtonCommand != null) - _backButtonCommand.CanExecuteChanged += OnBackButtonCanExecuteChanged; - } + _backButtonBehavior.PropertyChanged += OnBackButtonCommandPropertyChanged; + } - void OnBackButtonCanExecuteChanged(object sender, EventArgs e) - { - BackButtonEnabled = - _backButtonCommand.CanExecute(_backButtonBehavior.CommandParameter); - } + void OnBackButtonCommandPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + ApplyChanges(); } - void OnCurrentPagePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + void OnCurrentPagePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.Is(Page.TitleProperty)) UpdateTitle(); @@ -195,7 +173,7 @@ internal void UpdateTitle() return; } - Page currentPage = _shell.GetCurrentShellPage() as Page; + Page? currentPage = _shell.GetCurrentShellPage() as Page; if (currentPage?.IsSet(Page.TitleProperty) == true) { Title = currentPage.Title ?? String.Empty; diff --git a/src/Controls/tests/Core.UnitTests/ShellToolbarTests.cs b/src/Controls/tests/Core.UnitTests/ShellToolbarTests.cs index e8dabe174eef..a48763678e84 100644 --- a/src/Controls/tests/Core.UnitTests/ShellToolbarTests.cs +++ b/src/Controls/tests/Core.UnitTests/ShellToolbarTests.cs @@ -101,6 +101,50 @@ public async Task BackButtonDisabledWhenCommandDisabled() Assert.True(testShell.Toolbar.BackButtonEnabled); } + [Fact] + public async Task BackButtonBehaviorCommandFromPoppedPageIsCorrectlyUnsubscribedFrom() + { + var firstPage = new ContentPage(); + var secondPage = new ContentPage(); + bool canExecute = true; + var backButtonBehavior = new BackButtonBehavior(); + TestShell testShell = new TestShell(firstPage); + + await testShell.Navigation.PushAsync(secondPage); + + Shell.SetBackButtonBehavior(secondPage, backButtonBehavior); + + backButtonBehavior.Command = new Command(() => { }, () => canExecute); + + await testShell.Navigation.PopAsync(); + + canExecute = false; + (backButtonBehavior.Command as Command).ChangeCanExecute(); + + Assert.True(testShell.Toolbar.BackButtonEnabled); + } + + [Fact] + public async Task BackButtonUpdatesWhenSetToNewCommand() + { + var firstPage = new ContentPage(); + var secondPage = new ContentPage(); + bool canExecute = true; + var backButtonBehavior = new BackButtonBehavior(); + TestShell testShell = new TestShell(firstPage); + + await testShell.Navigation.PushAsync(secondPage); + + Shell.SetBackButtonBehavior(secondPage, backButtonBehavior); + + backButtonBehavior.Command = new Command(() => { }, () => true); + Assert.True(testShell.Toolbar.BackButtonEnabled); + backButtonBehavior.Command = new Command(() => { }, () => false); + Assert.False(testShell.Toolbar.BackButtonEnabled); + backButtonBehavior.Command = null; + Assert.True(testShell.Toolbar.BackButtonEnabled); + } + [Fact] public async Task ShellToolbarUpdatesFromNewBackButtonBehavior() { diff --git a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.cs b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.cs index d8bf55da859e..b8ffeddb44c7 100644 --- a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.cs @@ -596,6 +596,45 @@ await CreateHandlerAndAddToWindow(shell, async (handler) => }); } + [Fact(DisplayName = "Navigate to Root with BackButtonBehavior no Crash")] + public async Task NavigateToRootWithBackButtonBehaviorNoCrash() + { + SetupBuilder(); + + var shell = await CreateShellAsync(shell => + { + shell.CurrentItem = new ContentPage(); + }); + + await CreateHandlerAndAddToWindow(shell, async (handler) => + { + Assert.False(IsBackButtonVisible(shell.Handler)); + var secondPage = new ContentPage(); + await shell.Navigation.PushAsync(secondPage); + Assert.True(IsBackButtonVisible(shell.Handler)); + + bool canExecute = true; + Command command = new Command(() => { }, () => canExecute); + BackButtonBehavior behavior = new BackButtonBehavior() + { + IsVisible = false, + Command = command + }; + + Shell.SetBackButtonBehavior(secondPage, behavior); + Assert.False(IsBackButtonVisible(shell.Handler)); + behavior.IsVisible = true; + NavigationPage.SetHasBackButton(shell.CurrentPage, true); + + await shell.Navigation.PopToRootAsync(false); + await Task.Delay(100); + canExecute = false; + + command.ChangeCanExecute(); + Assert.NotNull(shell.Handler); + }); + } + protected Task CreateShellAsync(Action action) => InvokeOnMainThreadAsync(() => {