| File: Menu\MenuItemTracker.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#nullable disable using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace Microsoft.Maui.Controls { internal abstract class MenuItemTracker<TMenuItem> where TMenuItem : BaseMenuItem { int _flyoutDetails; WeakReference<Page> _target; List<WeakReference<Page>> _additionalTargets = new(); public MenuItemTracker() { } protected abstract IList<TMenuItem> GetMenuItems(Page page); protected abstract IComparer<TMenuItem> CreateComparer(); public IEnumerable<Page> AdditionalTargets { get { foreach (var target in _additionalTargets) { if (target.TryGetTarget(out var page)) yield return page; } } set { _additionalTargets.Clear(); if (value is not null) { foreach (var page in value) { _additionalTargets.Add(new(page)); } } } } public bool HaveFlyoutPage { get { return _flyoutDetails > 0; } } public bool SeparateFlyoutPage { get; set; } public Page Target { get => _target is not null && _target.TryGetTarget(out var target) ? target : null; set { var target = Target; if (target == value) return; UntrackTarget(target); _target = value is null ? null : new(value); if (value != null) TrackTarget(value); EmitCollectionChanged(); } } public IList<TMenuItem> ToolbarItems { get { if (Target == null) return Array.Empty<TMenuItem>(); // I realize this is sorting on every single get but we don't have // a mechanism in place currently to invalidate a stored version of this List<TMenuItem> returnValue = GetCurrentToolbarItems(Target); if (AdditionalTargets != null) foreach (var item in AdditionalTargets) foreach (var menuItem in GetMenuItems(item)) if (!returnValue.Contains(menuItem)) returnValue.Add(menuItem); returnValue.Sort(CreateComparer()); return returnValue; } } public event EventHandler CollectionChanged; void EmitCollectionChanged() => CollectionChanged?.Invoke(this, EventArgs.Empty); List<TMenuItem> GetCurrentToolbarItems(Page page) { var result = new List<TMenuItem>(); result.AddRange(GetMenuItems(page)); if (page is FlyoutPage) { var flyoutDetail = (FlyoutPage)page; if (SeparateFlyoutPage) { if (flyoutDetail.IsPresented) { if (flyoutDetail.Flyout != null) result.AddRange(GetCurrentToolbarItems(flyoutDetail.Flyout)); } else { if (flyoutDetail.Detail != null) result.AddRange(GetCurrentToolbarItems(flyoutDetail.Detail)); } } else { if (flyoutDetail.Flyout != null) result.AddRange(GetCurrentToolbarItems(flyoutDetail.Flyout)); if (flyoutDetail.Detail != null) result.AddRange(GetCurrentToolbarItems(flyoutDetail.Detail)); } } else if (page is Shell shell) { if (shell.GetCurrentShellPage() is Page shellPage && shellPage != shell) result.AddRange(GetCurrentToolbarItems(shellPage)); } else if (page is IPageContainer<Page>) { var container = (IPageContainer<Page>)page; if (container.CurrentPage != null && container.CurrentPage != container) result.AddRange(GetCurrentToolbarItems(container.CurrentPage)); } return result; } void OnChildAdded(object sender, ElementEventArgs eventArgs) { var page = eventArgs.Element as Page; if (page == null) return; RegisterChildPage(page); } void OnChildRemoved(object sender, ElementEventArgs eventArgs) { var page = eventArgs.Element as Page; if (page == null) return; UnregisterChildPage(page); } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) { EmitCollectionChanged(); } void OnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { if (propertyChangedEventArgs.PropertyName == NavigationPage.CurrentPageProperty.PropertyName || propertyChangedEventArgs.PropertyName == FlyoutPage.IsPresentedProperty.PropertyName || propertyChangedEventArgs.PropertyName == "Detail" || propertyChangedEventArgs.PropertyName == "Flyout") { EmitCollectionChanged(); } PagePropertyChanged?.Invoke(sender, propertyChangedEventArgs); } public event EventHandler<PropertyChangedEventArgs> PagePropertyChanged; public event EventHandler<EventArgs> PageAppearing; void RegisterChildPage(Page page) { if (page is FlyoutPage) _flyoutDetails++; ((ObservableCollection<TMenuItem>)GetMenuItems(page)).CollectionChanged += OnCollectionChanged; page.PropertyChanged += OnPropertyChanged; page.Appearing += OnPageAppearing; } void OnPageAppearing(object sender, EventArgs e) { PageAppearing?.Invoke(sender, e); } void TrackTarget(Page page) { if (page == null) return; if (page is FlyoutPage) _flyoutDetails++; ((ObservableCollection<TMenuItem>)GetMenuItems(page)).CollectionChanged += OnCollectionChanged; if (page is Shell shell) { shell.Navigated += OnShellNavigated; shell.Navigating += OnShellNavigating; if (shell.GetCurrentShellPage() is Page currentShellPage) RegisterChildPage(currentShellPage); return; } page.Descendants<Page>().ForEach(RegisterChildPage); page.DescendantAdded += OnChildAdded; page.DescendantRemoved += OnChildRemoved; page.PropertyChanged += OnPropertyChanged; page.Appearing += OnPageAppearing; } void OnShellNavigating(object sender, ShellNavigatingEventArgs e) { if (((Shell)sender).GetCurrentShellPage() is Page page) UnregisterChildPage(page); } void OnShellNavigated(object sender, ShellNavigatedEventArgs e) { if (((Shell)sender).GetCurrentShellPage() is Page page) { UnregisterChildPage(page); RegisterChildPage(page); } EmitCollectionChanged(); } void UnregisterChildPage(Page page) { if (page is FlyoutPage) _flyoutDetails--; ((ObservableCollection<TMenuItem>)GetMenuItems(page)).CollectionChanged -= OnCollectionChanged; page.PropertyChanged -= OnPropertyChanged; page.Appearing -= OnPageAppearing; } void UntrackTarget(Page page) { if (page == null) return; if (page is FlyoutPage) _flyoutDetails--; if (page is Shell shell) { shell.Navigated -= OnShellNavigated; shell.Navigating -= OnShellNavigating; return; } ((ObservableCollection<TMenuItem>)GetMenuItems(page)).CollectionChanged -= OnCollectionChanged; page.Descendants().OfType<Page>().ForEach(UnregisterChildPage); page.DescendantAdded -= OnChildAdded; page.DescendantRemoved -= OnChildRemoved; page.PropertyChanged -= OnPropertyChanged; page.Appearing -= OnPageAppearing; } } }