Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 100 additions & 18 deletions src/Controls/src/Core/Handlers/Shell/ShellSectionHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public partial class ShellSectionHandler : ElementHandler<ShellSection, WFrame>,

StackNavigationManager? _navigationManager;
WeakReference? _lastShell;
List<ShellContent>? _subscribedItems;

public ShellSectionHandler() : base(Mapper, CommandMapper)
{
Expand Down Expand Up @@ -53,11 +54,19 @@ public override void SetVirtualView(Maui.IElement view)
{
if (_shellSection != null)
{
((IShellSectionController)_shellSection).RemoveDisplayedPageObserver(this);
((IShellSectionController)_shellSection).NavigationRequested -= OnNavigationRequested;

((IShellSectionController)_shellSection).ItemsCollectionChanged -= OnItemsCollectionChanged;

if (_subscribedItems != null)
{
foreach (var item in _subscribedItems)
{
item.PropertyChanged -= OnShellContentPropertyChanged;
}
_subscribedItems.Clear();
}

if (_lastShell?.Target is IShellController shell)
{
shell.RemoveAppearanceObserver(this);
Expand All @@ -84,42 +93,67 @@ public override void SetVirtualView(Maui.IElement view)
_shellSection = (ShellSection)view;
if (_shellSection != null)
{
_subscribedItems ??= new List<ShellContent>();
_subscribedItems.Clear();

((IShellSectionController)_shellSection).NavigationRequested += OnNavigationRequested;
((IShellSectionController)_shellSection).ItemsCollectionChanged += OnItemsCollectionChanged;

foreach (var item in ((IShellSectionController)_shellSection).GetItems())
{
item.PropertyChanged += OnShellContentPropertyChanged;
_subscribedItems.Add(item);
}

var shell = _shellSection.FindParentOfType<Shell>() as IShellController;
if (shell != null)
{
_lastShell = new WeakReference(shell);
shell.AddAppearanceObserver(this, _shellSection);
}

// AddDisplayedPageObserver immediately invokes the callback with the current page,
// but at that point PendingNavigationTask is already set from MapCurrentItem
// (which runs via base.SetVirtualView above), so it is safely skipped.
((IShellSectionController)_shellSection).AddDisplayedPageObserver(this, OnDisplayedPageChanged);
}
}

// Called when ShellSection.DisplayedPage changes — covers both content changes and
// navigation pushes. We only act on content changes (stack depth == 1, no pending nav).
void OnDisplayedPageChanged(Page page)
// Called when ShellContent.Content changes on a specific tab — forces Windows to
// rebuild the navigation stack so the new page becomes visible.
// Using PropertyChanged on each ShellContent (rather than AddDisplayedPageObserver)
// avoids false triggers during tab switches: OnCurrentItemChanged (the BindableProperty
// propertyChanged callback) fires BEFORE the handler's MapCurrentItem (which fires via
// the PropertyChanged event), so PendingNavigationTask is not yet set when
// DisplayedPage changes during a tab switch.
void OnShellContentPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (VirtualView is null)
{
return;
}

if (e.PropertyName != ShellContent.ContentProperty.PropertyName)
{
return;
}

if (sender is not ShellContent shellContent)
{
return;
}

// Only sync when the changed content belongs to the active tab at root level.
if (shellContent != VirtualView.CurrentItem)
{
return;
}

// Push/pop navigation is handled by OnNavigationRequested; skip those cases.
if (VirtualView.Stack.Count > 1)
{
return;
}

// Tab switches are handled by MapCurrentItem which sets PendingNavigationTask first
// (because the property mapper fires before the propertyChanged callback that calls
// UpdateDisplayedPage). Skip when another navigation is already in flight.
if (VirtualView.PendingNavigationTask != null)
{
return;
}

// ContentCache has been updated by OnContentChanged at this point, so
// SyncNavigationStack will correctly pick up the new page via GetOrCreateContent().
SyncNavigationStack(false, null);
}

Expand All @@ -130,8 +164,50 @@ void OnNavigationRequested(object? sender, NavigationRequestedEventArgs e)

void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (_shellSection is null)
if (_shellSection is null || _subscribedItems is null)
{
return;
}

// Handle Reset action - collection has been completely cleared and refilled
if (e.Action == NotifyCollectionChangedAction.Reset)
{
// Unsubscribe from all previously subscribed items
foreach (var item in _subscribedItems)
{
item.PropertyChanged -= OnShellContentPropertyChanged;
}
_subscribedItems.Clear();

// Subscribe to all items currently in the section
foreach (var item in ((IShellSectionController)_shellSection).GetItems())
{
item.PropertyChanged += OnShellContentPropertyChanged;
_subscribedItems.Add(item);
}
}
else
{
// Handle Remove action
if (e.OldItems != null)
{
foreach (ShellContent item in e.OldItems)
{
item.PropertyChanged -= OnShellContentPropertyChanged;
_subscribedItems.Remove(item);
}
}

// Handle Add action
if (e.NewItems != null)
{
foreach (ShellContent item in e.NewItems)
{
item.PropertyChanged += OnShellContentPropertyChanged;
_subscribedItems.Add(item);
}
}
}

if (_shellSection.Parent is ShellItem shellItem && shellItem.Handler is ShellItemHandler shellItemHandler)
{
Expand Down Expand Up @@ -187,8 +263,14 @@ protected override void ConnectHandler(WFrame platformView)
protected override void DisconnectHandler(WFrame platformView)
{
if (_shellSection != null)
((IShellSectionController)_shellSection).RemoveDisplayedPageObserver(this);

{
foreach (var item in ((IShellSectionController)_shellSection).GetItems())
{
item.PropertyChanged -= OnShellContentPropertyChanged;
}
_subscribedItems?.Clear();
}

_navigationManager?.Disconnect(VirtualView, platformView);
base.DisconnectHandler(platformView);
}
Expand Down
Loading