diff --git a/src/Controls/src/Core/Application.cs b/src/Controls/src/Core/Application.cs index c566c6616cab..60214eacac18 100644 --- a/src/Controls/src/Core/Application.cs +++ b/src/Controls/src/Core/Application.cs @@ -25,7 +25,6 @@ public partial class Application : Element, IResourcesProvider, IApplicationCont #pragma warning restore CS0612 // Type or member is obsolete IAppIndexingProvider? _appIndexProvider; - ReadOnlyCollection? _logicalChildren; bool _isStarted; static readonly SemaphoreSlim SaveSemaphore = new SemaphoreSlim(1, 1); @@ -111,17 +110,12 @@ public Page? MainPage } } - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCollection(InternalChildren); - /// [EditorBrowsable(EditorBrowsableState.Never)] public NavigationProxy? NavigationProxy { get; private set; } internal IResourceDictionary SystemResources => _systemResources.Value; - ObservableCollection InternalChildren { get; } = new ObservableCollection(); - /// [EditorBrowsable(EditorBrowsableState.Never)] public void SetAppIndexingProvider(IAppIndexingProvider provider) diff --git a/src/Controls/src/Core/Border.cs b/src/Controls/src/Core/Border.cs index 74d21f2f6bb1..5c1cb706da39 100644 --- a/src/Controls/src/Core/Border.cs +++ b/src/Controls/src/Core/Border.cs @@ -13,7 +13,6 @@ namespace Microsoft.Maui.Controls public class Border : View, IContentView, IBorderView, IPaddingElement { float[]? _strokeDashPattern; - ReadOnlyCollection? _logicalChildren; WeakNotifyPropertyChangedProxy? _strokeShapeProxy = null; PropertyChangedEventHandler? _strokeShapeChanged; @@ -26,11 +25,6 @@ public class Border : View, IContentView, IBorderView, IPaddingElement _strokeProxy?.Unsubscribe(); } - internal ObservableCollection InternalChildren { get; } = new(); - - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCollection(InternalChildren); - /// Bindable property for . public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(Border), null, propertyChanged: ContentChanged); @@ -274,17 +268,12 @@ public static void ContentChanged(BindableObject bindable, object oldValue, obje { if (oldValue is Element oldElement) { - int index = border.InternalChildren.IndexOf(oldElement); - if (border.InternalChildren.Remove(oldElement)) - { - border.OnChildRemoved(oldElement, index); - } + border.RemoveLogicalChildInternal(oldElement); } if (newValue is Element newElement) { - border.InternalChildren.Add(newElement); - border.OnChildAdded(newElement); + border.AddLogicalChildInternal(newElement); } } diff --git a/src/Controls/src/Core/Cells/ViewCell.cs b/src/Controls/src/Core/Cells/ViewCell.cs index 3e2d27e1a2a1..e45ea9ba2d42 100644 --- a/src/Controls/src/Core/Cells/ViewCell.cs +++ b/src/Controls/src/Core/Cells/ViewCell.cs @@ -8,8 +8,6 @@ namespace Microsoft.Maui.Controls [ContentProperty("View")] public class ViewCell : Cell { - ReadOnlyCollection _logicalChildren; - View _view; /// @@ -25,7 +23,7 @@ public View View if (_view != null) { - OnChildRemoved(_view, 0); + RemoveLogicalChildInternal(_view); _view.ComputedConstraint = LayoutConstraint.None; } @@ -34,18 +32,12 @@ public View View if (_view != null) { _view.ComputedConstraint = LayoutConstraint.Fixed; - _logicalChildren = new ReadOnlyCollection(new List(new[] { View })); - OnChildAdded(_view); - } - else - { - _logicalChildren = null; + AddLogicalChildInternal(_view); } + ForceUpdateSize(); OnPropertyChanged(); } } - - internal override IReadOnlyList LogicalChildrenInternal => _logicalChildren ?? base.LogicalChildrenInternal; } } \ No newline at end of file diff --git a/src/Controls/src/Core/ContextFlyout.cs b/src/Controls/src/Core/ContextFlyout.cs index 160dd42d38a6..0f0dd6f4cedb 100644 --- a/src/Controls/src/Core/ContextFlyout.cs +++ b/src/Controls/src/Core/ContextFlyout.cs @@ -9,11 +9,10 @@ namespace Microsoft.Maui.Controls public partial class MenuFlyout : FlyoutBase, IMenuFlyout // Same pattern as MenuBarItem { - ReadOnlyCastingList _logicalChildren; - readonly ObservableCollection _menus = new ObservableCollection(); + readonly List _menus = new List(); - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCastingList(_menus); + private protected override IList LogicalChildrenInternalBackingStore + => new CastingList(_menus); public IMenuElement this[int index] { @@ -32,7 +31,7 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - _menus.Add(item); + AddLogicalChildInternal((Element)item); NotifyHandler(nameof(IMenuFlyoutHandler.Add), index, item); // Take care of the Element internal bookkeeping @@ -70,42 +69,24 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - _menus.Insert(index, item); + InsertLogicalChildInternal(index, (Element)item); NotifyHandler(nameof(IMenuFlyoutHandler.Insert), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildAdded(element); - } } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = _menus.Remove(item); + var result = RemoveLogicalChildInternal((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } - return result; } public void RemoveAt(int index) { var item = _menus[index]; - _menus.RemoveAt(index); + RemoveLogicalChildInternal((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Controls/src/Core/Element.cs b/src/Controls/src/Core/Element.cs index 59b674dd9f23..d6d633de21b0 100644 --- a/src/Controls/src/Core/Element.cs +++ b/src/Controls/src/Core/Element.cs @@ -37,6 +37,10 @@ public abstract partial class Element : BindableObject, IElementDefinition, INam string _styleId; + IReadOnlyList _logicalChildrenReadonly; + + IList _internalChildren; + /// public string AutomationId { @@ -56,6 +60,7 @@ public string ClassId get => (string)GetValue(ClassIdProperty); set => SetValue(ClassIdProperty, value); } + /// public IList Effects { @@ -97,7 +102,119 @@ public string StyleId } } - internal virtual IReadOnlyList LogicalChildrenInternal => EmptyChildren; + // Leaving this internal for now. + // If users want to add/remove from this they can use + // AddLogicalChildren and RemoveLogicalChildren on the respective control + // if available. + // + // Ultimately I don't think we'll need these to be virtual but some controls (layout) + // are going to take a more focused effort so I'd rather just do that in a + // separate PR. I don't think there's ever a scenario where a subclass needs + // to replace the backing store. + // If everyone just uses AddLogicalChildren and RemoveLogicalChildren + // and then overrides OnChildAdded/OnChildRemoved + // that should be sufficient + internal IReadOnlyList LogicalChildrenInternal + { + get + { + SetupChildren(); + return _logicalChildrenReadonly; + } + } + + private protected virtual IList LogicalChildrenInternalBackingStore + { + get + { + _internalChildren ??= new List(); + return _internalChildren; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Do not use! This is to be removed! Just used by Hot Reload! To be replaced with IVisualTreeElement!")] + public ReadOnlyCollection LogicalChildren => + new ReadOnlyCollection(new TemporaryWrapper(LogicalChildrenInternal)); + + IReadOnlyList IElementController.LogicalChildren => LogicalChildrenInternal; + + void SetupChildren() + { + _logicalChildrenReadonly ??= new ReadOnlyCollection(LogicalChildrenInternalBackingStore); + } + + internal void InsertLogicalChildInternal(int index, Element element) + { + if (element is null) + { + return; + } + + SetupChildren(); + + LogicalChildrenInternalBackingStore.Insert(index, element); + OnChildAdded(element); + } + + internal void AddLogicalChildInternal(Element element) + { + if (element is null) + { + return; + } + + SetupChildren(); + + LogicalChildrenInternalBackingStore.Add(element); + OnChildAdded(element); + } + + internal bool RemoveLogicalChildInternal(Element element) + { + if (element is null) + { + return false; + } + + if (LogicalChildrenInternalBackingStore is null) + return false; + + var oldLogicalIndex = LogicalChildrenInternalBackingStore.IndexOf(element); + if (oldLogicalIndex < 0) + return false; + + RemoveLogicalChildInternal(element, oldLogicalIndex); + + return true; + } + + internal void ClearLogicalChildren() + { + if (LogicalChildrenInternalBackingStore is null) + return; + + if (LogicalChildrenInternal == EmptyChildren) + return; + + // Reverse for-loop, so children can be removed while iterating + for (int i = LogicalChildrenInternalBackingStore.Count - 1; i >= 0; i--) + { + RemoveLogicalChildInternal(LogicalChildrenInternalBackingStore[i], i); + } + } + + /// + /// This doesn't validate that the oldLogicalIndex is correct, so be sure you're passing in the + /// correct index + /// + internal bool RemoveLogicalChildInternal(Element element, int oldLogicalIndex) + { + LogicalChildrenInternalBackingStore.Remove(element); + OnChildRemoved(element, oldLogicalIndex); + + return true; + } internal IEnumerable AllChildren { @@ -118,14 +235,6 @@ internal IEnumerable AllChildren // return null by default so we don't need to foreach over an empty collection in OnPropertyChanged internal virtual IEnumerable ChildrenNotDrawnByThisElement => null; - IReadOnlyList IElementController.LogicalChildren => LogicalChildrenInternal; - - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Do not use! This is to be removed! Just used by Hot Reload! To be replaced with IVisualTreeElement!")] - public ReadOnlyCollection LogicalChildren => - new ReadOnlyCollection(new TemporaryWrapper(LogicalChildrenInternal)); - internal bool Owned { get; set; } internal Element ParentOverride @@ -200,7 +309,7 @@ public Element Parent ((IElementDefinition)RealParent).AddResourcesChangedListener(OnParentResourcesChanged); } - object context = value != null ? value.BindingContext : null; + object context = value?.BindingContext; if (value != null) { value.SetChildInheritedBindingContext(this, context); @@ -322,7 +431,8 @@ void INameScope.UnregisterName(string name) base.SetDynamicResource(property, key); } - IReadOnlyList IVisualTreeElement.GetVisualChildren() => LogicalChildrenInternal; + IReadOnlyList IVisualTreeElement.GetVisualChildren() + => LogicalChildrenInternal; IVisualTreeElement IVisualTreeElement.GetVisualParent() => this.Parent; diff --git a/src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs b/src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs index 2fdb7348761d..9ac7e6c59aba 100644 --- a/src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs +++ b/src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs @@ -82,10 +82,7 @@ internal void RemoveWindow(Window window) if (window is Element windowElement) { - var oldIndex = InternalChildren.IndexOf(windowElement); - InternalChildren.RemoveAt(oldIndex); - windowElement.Parent = null; - OnChildRemoved(windowElement, oldIndex); + RemoveLogicalChildInternal(windowElement); } _windows.Remove(window); @@ -134,9 +131,7 @@ void AddWindow(Window window) if (window is Element windowElement) { - windowElement.Parent = this; - InternalChildren.Add(windowElement); - OnChildAdded(windowElement); + AddLogicalChildInternal(windowElement); } if (window is NavigableElement ne) diff --git a/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs b/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs index 1312900a42b2..b0060c255ed5 100644 --- a/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs +++ b/src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs @@ -62,7 +62,6 @@ public partial class Window : NavigableElement, IWindow, IVisualTreeElement, ITo nameof(MinimumHeight), typeof(double), typeof(Window), Primitives.Dimension.Minimum); HashSet _overlays = new HashSet(); - ReadOnlyCollection? _logicalChildren; List _visualChildren; Toolbar? _toolbar; MenuBarTracker _menuBarTracker; @@ -95,7 +94,6 @@ public Window() AlertManager = new AlertManager(this); ModalNavigationManager = new ModalNavigationManager(this); Navigation = new NavigationImpl(this); - InternalChildren.CollectionChanged += OnCollectionChanged; #pragma warning disable CA1416 // TODO: VisualDiagnosticsOverlay is supported on android 23.0 and above VisualDiagnosticsOverlay = new VisualDiagnosticsOverlay(this); #pragma warning restore CA1416 @@ -296,11 +294,6 @@ public bool RemoveOverlay(IWindowOverlay overlay) return result; } - internal ObservableCollection InternalChildren { get; } = new ObservableCollection(); - - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCollection(InternalChildren); - internal AlertManager AlertManager { get; } internal ModalNavigationManager ModalNavigationManager { get; } @@ -390,36 +383,6 @@ Window IWindowController.Window Application? Application => Parent as Application; - void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.OldItems != null) - { - for (var i = 0; i < e.OldItems.Count; i++) - { - var item = (Element?)e.OldItems[i]; - - if (item != null) - _visualChildren.Remove(item); - - OnChildRemoved(item, e.OldStartingIndex + i); - } - } - - if (e.NewItems != null) - { - foreach (Element item in e.NewItems) - { - _visualChildren.Add(item); - OnChildAdded(item); - - if (Parent != null && item is Page) - { - SendWindowAppearing(); - } - } - } - } - internal void FinishedAddingWindowToApplication(Application application) { if (Parent != null) @@ -612,7 +575,6 @@ private protected override void OnHandlerChangingCore(HandlerChangingEventArgs a IReadOnlyList IVisualTreeElement.GetVisualChildren() => _visualChildren; - static void OnPageChanging(BindableObject bindable, object oldValue, object newValue) { if (bindable is not Window window) @@ -638,7 +600,8 @@ void OnPageChanged(Page? oldPage, Page? newPage) if (oldPage != null) { _menuBarTracker.Target = null; - InternalChildren.Remove(oldPage); + _visualChildren.Remove(oldPage); + RemoveLogicalChildInternal(oldPage); oldPage.HandlerChanged -= OnPageHandlerChanged; oldPage.HandlerChanging -= OnPageHandlerChanging; } @@ -648,13 +611,16 @@ void OnPageChanged(Page? oldPage, Page? newPage) if (newPage != null) { - InternalChildren.Add(newPage); + _visualChildren.Add(newPage); + AddLogicalChildInternal(newPage); newPage.NavigationProxy.Inner = NavigationProxy; _menuBarTracker.Target = newPage; - } - if (newPage != null) - { + if (Parent != null) + { + SendWindowAppearing(); + } + newPage.HandlerChanged += OnPageHandlerChanged; newPage.HandlerChanging += OnPageHandlerChanging; diff --git a/src/Controls/src/Core/Items/ItemsView.cs b/src/Controls/src/Core/Items/ItemsView.cs index f966bb7bf3c5..405addd738d6 100644 --- a/src/Controls/src/Core/Items/ItemsView.cs +++ b/src/Controls/src/Core/Items/ItemsView.cs @@ -14,7 +14,6 @@ namespace Microsoft.Maui.Controls /// public abstract class ItemsView : View { - List _logicalChildren = new List(); /// Bindable property for . public static readonly BindableProperty EmptyViewProperty = @@ -110,48 +109,12 @@ public int RemainingItemsThreshold } /// - public void AddLogicalChild(Element element) - { - if (element == null) - { - return; - } - - _logicalChildren.Add(element); - element.Parent = this; - OnChildAdded(element); - VisualDiagnostics.OnChildAdded(this, element); - } + public void AddLogicalChild(Element element) => + AddLogicalChildInternal(element); /// public void RemoveLogicalChild(Element element) - { - if (element == null) - { - return; - } - - element.Parent = null; - - if (!_logicalChildren.Contains(element)) - return; - - var oldLogicalIndex = _logicalChildren.IndexOf(element); - _logicalChildren.Remove(element); - OnChildRemoved(element, oldLogicalIndex); - VisualDiagnostics.OnChildRemoved(this, element, oldLogicalIndex); - } - - internal void ClearLogicalChildren() - { - // Reverse for-loop, so children can be removed while iterating - for (int i = _logicalChildren.Count - 1; i >= 0; i--) - { - RemoveLogicalChild(_logicalChildren[i]); - } - } - - internal override IReadOnlyList LogicalChildrenInternal => _logicalChildren.AsReadOnly(); + => RemoveLogicalChildInternal(element); internal static readonly BindableProperty InternalItemsLayoutProperty = BindableProperty.Create(nameof(ItemsLayout), typeof(IItemsLayout), typeof(ItemsView), diff --git a/src/Controls/src/Core/Layout.cs b/src/Controls/src/Core/Layout.cs index e1f21e7130cb..bd84179ac968 100644 --- a/src/Controls/src/Core/Layout.cs +++ b/src/Controls/src/Core/Layout.cs @@ -80,8 +80,6 @@ public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement bool _hasDoneLayout; Size _lastLayoutSize = new Size(-1, -1); - ReadOnlyCollection _logicalChildren; - protected Layout() { //if things were added in base ctor (through implicit styles), the items added aren't properly parented @@ -121,9 +119,10 @@ static void IsClippedToBoundsPropertyChanged(BindableObject bindableObject, obje } } - internal ObservableCollection InternalChildren { get; } = new ObservableCollection(); + private protected override IList LogicalChildrenInternalBackingStore + => InternalChildren; - internal override IReadOnlyList LogicalChildrenInternal => _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection(InternalChildren)); + internal ObservableCollection InternalChildren { get; } = new ObservableCollection(); public event EventHandler LayoutChanged; diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs index 9e92486a2a55..b010a77a565d 100644 --- a/src/Controls/src/Core/Layout/Layout.cs +++ b/src/Controls/src/Core/Layout/Layout.cs @@ -13,8 +13,6 @@ namespace Microsoft.Maui.Controls [ContentProperty(nameof(Children))] public abstract partial class Layout : View, Maui.ILayout, IList, IBindableLayout, IPaddingElement, IVisualTreeElement, ISafeAreaView { - ReadOnlyCastingList _logicalChildren; - protected ILayoutManager _layoutManager; ILayoutManager LayoutManager @@ -40,8 +38,8 @@ static ILayoutManager GetLayoutManagerFromFactory(Layout layout) IList IBindableLayout.Children => _children; - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCastingList(_children); + private protected override IList LogicalChildrenInternalBackingStore + => new CastingList(_children); public int Count => _children.Count; diff --git a/src/Controls/src/Core/MenuBarItem.cs b/src/Controls/src/Core/MenuBarItem.cs index 690398b509aa..4dffe30b430d 100644 --- a/src/Controls/src/Core/MenuBarItem.cs +++ b/src/Controls/src/Core/MenuBarItem.cs @@ -37,11 +37,10 @@ public string Text set => SetValue(TextProperty, value); } - ReadOnlyCastingList _logicalChildren; - readonly ObservableCollection _menus = new ObservableCollection(); + readonly List _menus = new List(); - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCastingList(_menus); + private protected override IList LogicalChildrenInternalBackingStore + => new CastingList(_menus); public IMenuElement this[int index] { @@ -60,14 +59,8 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - _menus.Add(item); + AddLogicalChildInternal((Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Add), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildAdded(element); - } } public void Clear() @@ -98,27 +91,15 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - _menus.Insert(index, item); + InsertLogicalChildInternal(index, (Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Insert), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildAdded(element); - } } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = _menus.Remove(item); - NotifyHandler(nameof(IMenuBarItemHandler.Remove), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } + var result = RemoveLogicalChildInternal((Element)item, index); + NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); return result; } @@ -126,14 +107,8 @@ public bool Remove(IMenuElement item) public void RemoveAt(int index) { var item = _menus[index]; - _menus.RemoveAt(index); - NotifyHandler(nameof(IMenuBarItemHandler.Remove), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } + RemoveLogicalChildInternal((Element)item, index); + NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Controls/src/Core/MenuFlyoutSubItem.cs b/src/Controls/src/Core/MenuFlyoutSubItem.cs index 13e415e5b3bf..50229af2d5d5 100644 --- a/src/Controls/src/Core/MenuFlyoutSubItem.cs +++ b/src/Controls/src/Core/MenuFlyoutSubItem.cs @@ -12,11 +12,10 @@ namespace Microsoft.Maui.Controls { public partial class MenuFlyoutSubItem : MenuFlyoutItem, IMenuFlyoutSubItem { - ReadOnlyCastingList _logicalChildren; - readonly ObservableCollection _menus = new ObservableCollection(); + readonly List _menus = new List(); - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ??= new ReadOnlyCastingList(_menus); + private protected override IList LogicalChildrenInternalBackingStore + => new CastingList(_menus); public IMenuElement this[int index] { @@ -35,14 +34,8 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - _menus.Add(item); + AddLogicalChildInternal((Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Add), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildAdded(element); - } } public void Clear() @@ -73,27 +66,15 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - _menus.Insert(index, item); + InsertLogicalChildInternal(index, (Element)item); NotifyHandler(nameof(IMenuFlyoutSubItemHandler.Insert), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildAdded(element); - } } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = _menus.Remove(item); - NotifyHandler(nameof(IMenuFlyoutSubItemHandler.Remove), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } + var result = RemoveLogicalChildInternal((Element)item, index); + NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); return result; } @@ -101,14 +82,8 @@ public bool Remove(IMenuElement item) public void RemoveAt(int index) { var item = _menus[index]; - _menus.RemoveAt(index); - NotifyHandler(nameof(IMenuFlyoutSubItemHandler.Remove), index, item); - - // Take care of the Element internal bookkeeping - if (item is Element element) - { - OnChildRemoved(element, index); - } + RemoveLogicalChildInternal((Element)item, index); + NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); } IEnumerator IEnumerable.GetEnumerator() diff --git a/src/Controls/src/Core/Page.cs b/src/Controls/src/Core/Page.cs index 2bb7e86f107d..b7d7934dc700 100644 --- a/src/Controls/src/Core/Page.cs +++ b/src/Controls/src/Core/Page.cs @@ -55,8 +55,6 @@ public partial class Page : VisualElement, ILayout, IPageController, IElementCon bool _hasAppeared; private protected bool HasAppeared => _hasAppeared; - ReadOnlyCollection _logicalChildren; - View _titleView; List _pendingActions = new List(); @@ -158,6 +156,11 @@ public bool IgnoresContainerArea [EditorBrowsable(EditorBrowsableState.Never)] public ObservableCollection InternalChildren { get; } = new ObservableCollection(); + // Todo rework Page to use AddLogical/RemoveLogical. + // Rework all the related parts of the code that interact with the `Page` InternalChildren + private protected override IList LogicalChildrenInternalBackingStore => + InternalChildren; + internal override IEnumerable ChildrenNotDrawnByThisElement { get @@ -173,9 +176,6 @@ internal override IEnumerable ChildrenNotDrawnByThisElement } } - internal override IReadOnlyList LogicalChildrenInternal => - _logicalChildren ?? (_logicalChildren = new ReadOnlyCollection(InternalChildren)); - bool ISafeAreaView.IgnoreSafeArea => !On().UsingSafeArea(); public event EventHandler LayoutChanged; diff --git a/src/Controls/src/Core/ReadOnlyCastingList.cs b/src/Controls/src/Core/ReadOnlyCastingList.cs index 70e028891729..a875fd137282 100644 --- a/src/Controls/src/Core/ReadOnlyCastingList.cs +++ b/src/Controls/src/Core/ReadOnlyCastingList.cs @@ -1,6 +1,8 @@ #nullable disable +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Maui.Controls { @@ -33,4 +35,92 @@ public T this[int index] get { return _list[index] as T; } } } + + internal class ReadOnlyCastingReadOnlyList : IReadOnlyList where T : class where TFrom : class + { + readonly IReadOnlyList _readonlyList; + + public ReadOnlyCastingReadOnlyList(IReadOnlyList list) + { + _readonlyList = list; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_readonlyList).GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return new CastingEnumerator(_readonlyList.GetEnumerator()); + } + + public int Count + { + get { return _readonlyList.Count; } + } + + public T this[int index] + { + get { return _readonlyList[index] as T; } + } + } + + class CastingList : IList + where T : class + where TFrom : class + { + readonly IList _list; + + public CastingList(IList list) + { + _list = list; + } + + public T this[int index] + { + get => _list[index] as T; + set => _list[index] = value as TFrom; + } + + public int Count => _list.Count; + + public bool IsReadOnly => _list.IsReadOnly; + + public void Add(T item) => + _list.Add(item as TFrom); + + public void Clear() => _list.Clear(); + + public bool Contains(T item) + => _list.Contains(item as TFrom); + + public void CopyTo(T[] array, int arrayIndex) + { + for (int i = arrayIndex; i < array.Length; i++) + { + array[i] = _list[i] as T; + } + } + + public IEnumerator GetEnumerator() => + new CastingEnumerator(_list.GetEnumerator()); + + public int IndexOf(T item) => + _list.IndexOf(item as TFrom); + + public void Insert(int index, T item) => + _list.Insert(index, item as TFrom); + + public bool Remove(T item) => + _list.Remove(item as TFrom); + + public void RemoveAt(int index) => + _list.RemoveAt(index); + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + } } \ No newline at end of file diff --git a/src/Controls/src/Core/Shell/BaseShellItem.cs b/src/Controls/src/Core/Shell/BaseShellItem.cs index 70ed2d75a379..b2855e9ce086 100644 --- a/src/Controls/src/Core/Shell/BaseShellItem.cs +++ b/src/Controls/src/Core/Shell/BaseShellItem.cs @@ -27,7 +27,9 @@ public class BaseShellItem : NavigableElement, IPropertyPropagationController, I protected private ObservableCollection DeclaredChildren { get; } = new ObservableCollection(); readonly ObservableCollection _logicalChildren = new ObservableCollection(); - internal override IReadOnlyList LogicalChildrenInternal => new ReadOnlyCollection(_logicalChildren); + + private protected override IList LogicalChildrenInternalBackingStore + => _logicalChildren; #region PropertyKeys diff --git a/src/Controls/src/Core/Shell/Shell.cs b/src/Controls/src/Core/Shell/Shell.cs index 0c1112a444fc..436b77b239d2 100644 --- a/src/Controls/src/Core/Shell/Shell.cs +++ b/src/Controls/src/Core/Shell/Shell.cs @@ -795,10 +795,11 @@ public void RemoveLogicalChild(Element element) ShellFlyoutItemsManager _flyoutManager; Page _previousPage; - ObservableCollection _logicalChildren = new ObservableCollection(); + ObservableCollection _logicalChildren + = new ObservableCollection(); - internal override IReadOnlyList LogicalChildrenInternal => - new ReadOnlyCollection(_logicalChildren); + private protected override IList LogicalChildrenInternalBackingStore + => _logicalChildren; /// public Shell() diff --git a/src/Controls/tests/Core.UnitTests/ElementTests.cs b/src/Controls/tests/Core.UnitTests/ElementTests.cs index 3e7136863bee..aae1b53ba97c 100644 --- a/src/Controls/tests/Core.UnitTests/ElementTests.cs +++ b/src/Controls/tests/Core.UnitTests/ElementTests.cs @@ -33,10 +33,8 @@ public IList Children get { return internalChildren; } } - internal override IReadOnlyList LogicalChildrenInternal - { - get { return new ReadOnlyCollection(internalChildren); } - } + private protected override IList LogicalChildrenInternalBackingStore + => internalChildren; readonly ObservableCollection internalChildren = new ObservableCollection(); } diff --git a/src/Controls/tests/Core.UnitTests/MultiBindingTests.cs b/src/Controls/tests/Core.UnitTests/MultiBindingTests.cs index 66d1c92eb0be..2096ba53987f 100644 --- a/src/Controls/tests/Core.UnitTests/MultiBindingTests.cs +++ b/src/Controls/tests/Core.UnitTests/MultiBindingTests.cs @@ -94,7 +94,7 @@ public void TestRelativeSources() IsEnabled = true, IsExpanded = true }; - var cp = expander.Children[0].LogicalChildren[1] as ContentPresenter; + var cp = expander.Children[0].LogicalChildrenInternal[1] as ContentPresenter; Assert.True(cp.IsVisible); expander.IsEnabled = false; Assert.False(cp.IsVisible); diff --git a/src/Controls/tests/Core.UnitTests/ShellFlyoutItemTemplateTests.cs b/src/Controls/tests/Core.UnitTests/ShellFlyoutItemTemplateTests.cs index c1d7fafee2cf..6f43618b60ff 100644 --- a/src/Controls/tests/Core.UnitTests/ShellFlyoutItemTemplateTests.cs +++ b/src/Controls/tests/Core.UnitTests/ShellFlyoutItemTemplateTests.cs @@ -19,7 +19,7 @@ public void FlyoutItemDefaultStylesApplied() shell.Items.Add(shellItem); var element = GetFlyoutItemDataTemplateElement(shell, shellItem); - var label = element.LogicalChildren.OfType