diff --git a/src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs b/src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs index efde71d2c6ec..a079cbeea77a 100644 --- a/src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs +++ b/src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs @@ -19,6 +19,7 @@ public partial class TabbedPage NavigationRootManager? _navigationRootManager; WFrame? _navigationFrame; bool _connectedToHandler; + Page? _displayedPage; WFrame NavigationFrame => _navigationFrame ?? throw new ArgumentNullException(nameof(NavigationFrame)); IMauiContext MauiContext => this.Handler?.MauiContext ?? throw new InvalidOperationException("MauiContext cannot be null here"); @@ -177,6 +178,7 @@ void OnHandlerDisconnected(ElementHandler? elementHandler) _navigationView = null; _navigationRootManager = null; _navigationFrame = null; + _displayedPage = null; } void OnTabbedPageAppearing(object? sender, EventArgs e) @@ -255,8 +257,15 @@ void OnSelectedMenuItemChanged(NavigationView sender, NavigationViewSelectionCha void NavigateToPage(Page page) { - FrameNavigationOptions navOptions = new FrameNavigationOptions(); + if (_displayedPage == page) + return; + + // Detach content from old page to prevent "Element is already the child of another element" error + if (NavigationFrame.Content is WPage oldPage && oldPage.Content is WContentPresenter oldPresenter) + oldPresenter.Content = null; + CurrentPage = page; + FrameNavigationOptions navOptions = new FrameNavigationOptions(); navOptions.IsNavigationStackEnabled = false; NavigationFrame.NavigateToType(typeof(WPage), null, navOptions); } @@ -270,7 +279,7 @@ void UpdateCurrentPageContent() void UpdateCurrentPageContent(WPage page) { - if (MauiContext == null) + if (MauiContext == null || _displayedPage == CurrentPage) return; WContentPresenter? presenter; @@ -297,6 +306,7 @@ void UpdateCurrentPageContent(WPage page) return; presenter.Content = _currentPage.ToPlatform(MauiContext); + _displayedPage = CurrentPage; } void OnNavigated(object sender, UI.Xaml.Navigation.NavigationEventArgs e) diff --git a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs index 0c049fb7924a..d39a6f75dec0 100644 --- a/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui; using Microsoft.Maui.Controls; @@ -12,7 +13,10 @@ using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; using Xunit; +using WContentPresenter = Microsoft.UI.Xaml.Controls.ContentPresenter; +using WFrame = Microsoft.UI.Xaml.Controls.Frame; using WFrameworkElement = Microsoft.UI.Xaml.FrameworkElement; +using WPage = Microsoft.UI.Xaml.Controls.Page; using WSolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush; namespace Microsoft.Maui.DeviceTests @@ -197,5 +201,39 @@ await AssertionExtensions.AssertTabItemTextDoesNotContainColor( tabText, iconColor, tabbedPage.FindMauiContext()); } } + + [Fact(DisplayName = "Issue 32824 - Tab Switch Clears Old Content To Prevent Crash")] + public async Task TabSwitchClearsOldContentToPreventCrash() + { + // https://github.com/dotnet/maui/issues/32824 + // When switching tabs, the old ContentPresenter.Content must be cleared + // before navigation to prevent "Element is already the child of another element" crash. + SetupBuilder(); + + var page1 = new ContentPage { Title = "Tab 1", Content = new Label { Text = "Page 1" } }; + var page2 = new ContentPage { Title = "Tab 2", Content = new Label { Text = "Page 2" } }; + var tabbedPage = new TabbedPage { Children = { page1, page2 } }; + + await CreateHandlerAndAddToWindow(tabbedPage, handler => + { + var frame = typeof(TabbedPage) + .GetField("_navigationFrame", BindingFlags.NonPublic | BindingFlags.Instance) + ?.GetValue(tabbedPage) as WFrame; + + Assert.NotNull(frame); + + var oldPresenter = (frame.Content as WPage)?.Content as WContentPresenter; + Assert.NotNull(oldPresenter); + Assert.NotNull(oldPresenter.Content); + + // Switch tabs - oldPresenter.Content should be cleared before navigation + tabbedPage.CurrentPage = page2; + + //old presenter content must be null to prevent crash + Assert.Null(oldPresenter.Content); + + return Task.CompletedTask; + }); + } } }