Skip to content

Commit dd703ff

Browse files
praveenkumarkarunanithiPureWeen
authored andcommitted
[windows] Fixed Rapid change of selected tab results in crash. (#33113)
### Root Cause On Windows, `TabbedPage` uses WinUI `Frame` navigation to display each tab’s content. Each tab’s platform view (native UI element) is hosted inside a WinUI `Page` through a `ContentPresenter`. During rapid tab switching, the platform view is reassigned to a new WinUI `Page` while still attached to the previous one. WinUI does not allow a UI element to exist in two visual trees simultaneously, which causes the crash. ### Description of Change Added a `_displayedPage` tracking field to maintain the correct displayed MAUI Page during navigation, allowing `NavigateToPage` to skip early if the requested page is already displayed and avoid redundant navigation. The method also now clears the previous WinUI Page’s `ContentPresenter` to explicitly detach the platform view and prevent the “element already has a parent” WinUI error. In `UpdateCurrentPageContent`, a skip guard avoids reassigning content that is already set, while `_displayedPage` is updated only after successful content assignment. Finally, OnHandlerDisconnected resets `_displayedPage` to null to ensure proper cleanup and prevent stale references. ### Issues Fixed Fixes #32824 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/4ae17a81-a399-4cd6-856d-ca4938cb683b" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/5927a164-22d8-45bc-a910-003bfbc1927a" /> |
1 parent b611772 commit dd703ff

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

src/Controls/src/Core/TabbedPage/TabbedPage.Windows.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public partial class TabbedPage
1919
NavigationRootManager? _navigationRootManager;
2020
WFrame? _navigationFrame;
2121
bool _connectedToHandler;
22+
Page? _displayedPage;
2223
WFrame NavigationFrame => _navigationFrame ?? throw new ArgumentNullException(nameof(NavigationFrame));
2324
IMauiContext MauiContext => this.Handler?.MauiContext ?? throw new InvalidOperationException("MauiContext cannot be null here");
2425

@@ -177,6 +178,7 @@ void OnHandlerDisconnected(ElementHandler? elementHandler)
177178
_navigationView = null;
178179
_navigationRootManager = null;
179180
_navigationFrame = null;
181+
_displayedPage = null;
180182
}
181183

182184
void OnTabbedPageAppearing(object? sender, EventArgs e)
@@ -255,8 +257,15 @@ void OnSelectedMenuItemChanged(NavigationView sender, NavigationViewSelectionCha
255257

256258
void NavigateToPage(Page page)
257259
{
258-
FrameNavigationOptions navOptions = new FrameNavigationOptions();
260+
if (_displayedPage == page)
261+
return;
262+
263+
// Detach content from old page to prevent "Element is already the child of another element" error
264+
if (NavigationFrame.Content is WPage oldPage && oldPage.Content is WContentPresenter oldPresenter)
265+
oldPresenter.Content = null;
266+
259267
CurrentPage = page;
268+
FrameNavigationOptions navOptions = new FrameNavigationOptions();
260269
navOptions.IsNavigationStackEnabled = false;
261270
NavigationFrame.NavigateToType(typeof(WPage), null, navOptions);
262271
}
@@ -270,7 +279,7 @@ void UpdateCurrentPageContent()
270279

271280
void UpdateCurrentPageContent(WPage page)
272281
{
273-
if (MauiContext == null)
282+
if (MauiContext == null || _displayedPage == CurrentPage)
274283
return;
275284

276285
WContentPresenter? presenter;
@@ -297,6 +306,7 @@ void UpdateCurrentPageContent(WPage page)
297306
return;
298307

299308
presenter.Content = _currentPage.ToPlatform(MauiContext);
309+
_displayedPage = CurrentPage;
300310
}
301311

302312
void OnNavigated(object sender, UI.Xaml.Navigation.NavigationEventArgs e)

src/Controls/tests/DeviceTests/Elements/TabbedPage/TabbedPageTests.Windows.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Linq;
33
using System.Text;
44
using System.Threading.Tasks;
5+
using System.Reflection;
56
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Maui;
78
using Microsoft.Maui.Controls;
@@ -12,7 +13,10 @@
1213
using Microsoft.Maui.Hosting;
1314
using Microsoft.Maui.Platform;
1415
using Xunit;
16+
using WContentPresenter = Microsoft.UI.Xaml.Controls.ContentPresenter;
17+
using WFrame = Microsoft.UI.Xaml.Controls.Frame;
1518
using WFrameworkElement = Microsoft.UI.Xaml.FrameworkElement;
19+
using WPage = Microsoft.UI.Xaml.Controls.Page;
1620
using WSolidColorBrush = Microsoft.UI.Xaml.Media.SolidColorBrush;
1721

1822
namespace Microsoft.Maui.DeviceTests
@@ -197,5 +201,39 @@ await AssertionExtensions.AssertTabItemTextDoesNotContainColor(
197201
tabText, iconColor, tabbedPage.FindMauiContext());
198202
}
199203
}
204+
205+
[Fact(DisplayName = "Issue 32824 - Tab Switch Clears Old Content To Prevent Crash")]
206+
public async Task TabSwitchClearsOldContentToPreventCrash()
207+
{
208+
// https://github.com/dotnet/maui/issues/32824
209+
// When switching tabs, the old ContentPresenter.Content must be cleared
210+
// before navigation to prevent "Element is already the child of another element" crash.
211+
SetupBuilder();
212+
213+
var page1 = new ContentPage { Title = "Tab 1", Content = new Label { Text = "Page 1" } };
214+
var page2 = new ContentPage { Title = "Tab 2", Content = new Label { Text = "Page 2" } };
215+
var tabbedPage = new TabbedPage { Children = { page1, page2 } };
216+
217+
await CreateHandlerAndAddToWindow<TabbedViewHandler>(tabbedPage, handler =>
218+
{
219+
var frame = typeof(TabbedPage)
220+
.GetField("_navigationFrame", BindingFlags.NonPublic | BindingFlags.Instance)
221+
?.GetValue(tabbedPage) as WFrame;
222+
223+
Assert.NotNull(frame);
224+
225+
var oldPresenter = (frame.Content as WPage)?.Content as WContentPresenter;
226+
Assert.NotNull(oldPresenter);
227+
Assert.NotNull(oldPresenter.Content);
228+
229+
// Switch tabs - oldPresenter.Content should be cleared before navigation
230+
tabbedPage.CurrentPage = page2;
231+
232+
//old presenter content must be null to prevent crash
233+
Assert.Null(oldPresenter.Content);
234+
235+
return Task.CompletedTask;
236+
});
237+
}
200238
}
201239
}

0 commit comments

Comments
 (0)