diff --git a/src/Controls/docs/Microsoft.Maui.Controls/ItemsView.xml b/src/Controls/docs/Microsoft.Maui.Controls/ItemsView.xml index 6000f868b8ba..ca97036c131f 100644 --- a/src/Controls/docs/Microsoft.Maui.Controls/ItemsView.xml +++ b/src/Controls/docs/Microsoft.Maui.Controls/ItemsView.xml @@ -31,28 +31,6 @@ To be added. - - - - - - Method - - Microsoft.Maui.Controls.Core - 2.0.0.0 - - - System.Void - - - - - - To be added. - To be added. - To be added. - - @@ -530,28 +508,6 @@ To be added. - - - - - - Method - - Microsoft.Maui.Controls.Core - 2.0.0.0 - - - System.Void - - - - - - To be added. - To be added. - To be added. - - diff --git a/src/Controls/src/Core/Application/Application.cs b/src/Controls/src/Core/Application/Application.cs index 7783a8403a35..53b7d3b9674d 100644 --- a/src/Controls/src/Core/Application/Application.cs +++ b/src/Controls/src/Core/Application/Application.cs @@ -464,7 +464,7 @@ internal void RemoveWindow(Window window) if (window is Element windowElement) { - RemoveLogicalChildInternal(windowElement); + RemoveLogicalChild(windowElement); } _windows.Remove(window); @@ -518,7 +518,7 @@ void AddWindow(Window window) if (window is Element windowElement) { - AddLogicalChildInternal(windowElement); + AddLogicalChild(windowElement); } if (window is NavigableElement ne) diff --git a/src/Controls/src/Core/BindableObject.cs b/src/Controls/src/Core/BindableObject.cs index add82a38d079..628833ba160e 100644 --- a/src/Controls/src/Core/BindableObject.cs +++ b/src/Controls/src/Core/BindableObject.cs @@ -264,17 +264,12 @@ public static void SetInheritedBindingContext(BindableObject bindable, object va protected virtual void OnBindingContextChanged() { BindingContextChanged?.Invoke(this, EventArgs.Empty); + if (Shell.GetBackButtonBehavior(this) is BackButtonBehavior buttonBehavior) SetInheritedBindingContext(buttonBehavior, BindingContext); if (Shell.GetSearchHandler(this) is SearchHandler searchHandler) SetInheritedBindingContext(searchHandler, BindingContext); - - if (Shell.GetTitleView(this) is View titleView) - SetInheritedBindingContext(titleView, BindingContext); - - if (FlyoutBase.GetContextFlyout(this) is BindableObject contextFlyout) - SetInheritedBindingContext(contextFlyout, BindingContext); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) diff --git a/src/Controls/src/Core/BindableObjectExtensions.cs b/src/Controls/src/Core/BindableObjectExtensions.cs index 3adbbb5ed109..67be12f5ca75 100644 --- a/src/Controls/src/Core/BindableObjectExtensions.cs +++ b/src/Controls/src/Core/BindableObjectExtensions.cs @@ -85,6 +85,18 @@ internal static bool TrySetDynamicThemeColor( return false; } + internal static void AddRemoveLogicalChildren(this BindableObject bindable, object oldValue, object newValue) + { + if (!(bindable is Element owner)) + return; + + if (oldValue is Element oldView) + owner.RemoveLogicalChild(oldView); + + if (newValue is Element newView) + owner.AddLogicalChild(newView); + } + internal static bool TrySetAppTheme( this BindableObject self, string lightResourceKey, diff --git a/src/Controls/src/Core/Border/Border.cs b/src/Controls/src/Core/Border/Border.cs index 013827e80a05..1411fe4c4636 100644 --- a/src/Controls/src/Core/Border/Border.cs +++ b/src/Controls/src/Core/Border/Border.cs @@ -64,8 +64,7 @@ void NotifyStrokeShapeChanges() if (strokeShape is VisualElement visualElement) { - SetInheritedBindingContext(visualElement, BindingContext); - visualElement.Parent = this; + AddLogicalChild(visualElement); _strokeShapeChanged ??= (sender, e) => OnPropertyChanged(nameof(StrokeShape)); _strokeShapeProxy ??= new(); _strokeShapeProxy.Subscribe(visualElement, _strokeShapeChanged); @@ -78,8 +77,7 @@ void StopNotifyingStrokeShapeChanges() if (strokeShape is VisualElement visualElement) { - SetInheritedBindingContext(visualElement, null); - visualElement.Parent = null; + RemoveLogicalChild(visualElement); _strokeShapeProxy?.Unsubscribe(); } } @@ -268,12 +266,12 @@ public static void ContentChanged(BindableObject bindable, object oldValue, obje { if (oldValue is Element oldElement) { - border.RemoveLogicalChildInternal(oldElement); + border.RemoveLogicalChild(oldElement); } if (newValue is Element newElement) { - border.AddLogicalChildInternal(newElement); + border.AddLogicalChild(newElement); } } diff --git a/src/Controls/src/Core/Cells/Cell.cs b/src/Controls/src/Core/Cells/Cell.cs index 4dcaa1416d70..c63c4a94d386 100644 --- a/src/Controls/src/Core/Cells/Cell.cs +++ b/src/Controls/src/Core/Cells/Cell.cs @@ -243,7 +243,7 @@ public void SendDisappearing() void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) { - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IElementController)this).LogicalChildren); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } void OnContextActionsChanged(object sender, NotifyCollectionChangedEventArgs e) diff --git a/src/Controls/src/Core/Cells/ViewCell.cs b/src/Controls/src/Core/Cells/ViewCell.cs index e45ea9ba2d42..052a60d55340 100644 --- a/src/Controls/src/Core/Cells/ViewCell.cs +++ b/src/Controls/src/Core/Cells/ViewCell.cs @@ -23,7 +23,7 @@ public View View if (_view != null) { - RemoveLogicalChildInternal(_view); + RemoveLogicalChild(_view); _view.ComputedConstraint = LayoutConstraint.None; } @@ -32,7 +32,7 @@ public View View if (_view != null) { _view.ComputedConstraint = LayoutConstraint.Fixed; - AddLogicalChildInternal(_view); + AddLogicalChild(_view); } ForceUpdateSize(); diff --git a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs index 54ecabf566d1..3494df3abe9d 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs @@ -284,10 +284,10 @@ void Reset() void SetControllers() { var list = new List(); - var logicalChildren = ((IElementController)Element).LogicalChildren; - for (var i = 0; i < logicalChildren.Count; i++) + var pages = Tabbed.InternalChildren; + for (var i = 0; i < pages.Count; i++) { - var child = logicalChildren[i]; + var child = pages[i]; var v = child as Page; if (v == null) continue; diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs index fea1bd729e7d..e53cdf1e686c 100644 --- a/src/Controls/src/Core/Element/Element.cs +++ b/src/Controls/src/Core/Element/Element.cs @@ -146,7 +146,12 @@ void SetupChildren() _logicalChildrenReadonly ??= new ReadOnlyCollection(LogicalChildrenInternalBackingStore); } - internal void InsertLogicalChildInternal(int index, Element element) + /// + /// Inserts an to the logical children at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The to insert into the logical children. + public void InsertLogicalChild(int index, Element element) { if (element is null) { @@ -159,7 +164,11 @@ internal void InsertLogicalChildInternal(int index, Element element) OnChildAdded(element); } - internal void AddLogicalChildInternal(Element element) + /// + /// Adds an to the logical children. + /// + /// The to add to the logical children. + public void AddLogicalChild(Element element) { if (element is null) { @@ -172,7 +181,15 @@ internal void AddLogicalChildInternal(Element element) OnChildAdded(element); } - internal bool RemoveLogicalChildInternal(Element element) + /// + /// Removes the first occurrence of a specific from the logical children. + /// + /// The to remove. + /// + /// true if item was successfully removed from the logical children; + /// otherwise, false. This method also returns false if is not found. + /// + public bool RemoveLogicalChild(Element element) { if (element is null) { @@ -182,16 +199,19 @@ internal bool RemoveLogicalChildInternal(Element element) if (LogicalChildrenInternalBackingStore is null) return false; - var oldLogicalIndex = LogicalChildrenInternalBackingStore.IndexOf(element); - if (oldLogicalIndex < 0) + var index = LogicalChildrenInternalBackingStore.IndexOf(element); + if (index < 0) return false; - RemoveLogicalChildInternal(element, oldLogicalIndex); + RemoveLogicalChild(element, index); return true; } - internal void ClearLogicalChildren() + /// + /// Removes all s. + /// + public void ClearLogicalChildren() { if (LogicalChildrenInternalBackingStore is null) return; @@ -202,41 +222,18 @@ internal void ClearLogicalChildren() // Reverse for-loop, so children can be removed while iterating for (int i = LogicalChildrenInternalBackingStore.Count - 1; i >= 0; i--) { - RemoveLogicalChildInternal(LogicalChildrenInternalBackingStore[i], i); + RemoveLogicalChild(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) + internal bool RemoveLogicalChild(Element element, int index) { LogicalChildrenInternalBackingStore.Remove(element); - OnChildRemoved(element, oldLogicalIndex); + OnChildRemoved(element, index); return true; } - internal IEnumerable AllChildren - { - get - { - foreach (var child in LogicalChildrenInternal) - yield return child; - - var childrenNotDrawnByThisElement = ChildrenNotDrawnByThisElement; - if (childrenNotDrawnByThisElement is not null) - { - foreach (var child in childrenNotDrawnByThisElement) - yield return child; - } - } - } - - // return null by default so we don't need to foreach over an empty collection in OnPropertyChanged - internal virtual IEnumerable ChildrenNotDrawnByThisElement => null; - internal bool Owned { get; set; } internal Element ParentOverride @@ -286,48 +283,50 @@ void IElementDefinition.AddResourcesChangedListener(Action SetParent(value); + } - OnPropertyChanging(); + void SetParent(Element value) + { + if (RealParent == value) + return; - if (_parentOverride == null) - OnParentChangingCore(Parent, value); + OnPropertyChanging(nameof(Parent)); - if (RealParent != null) - { - ((IElementDefinition)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged); + if (_parentOverride == null) + OnParentChangingCore(Parent, value); - if (value != null && (RealParent is Layout || RealParent is IControlTemplated)) - Application.Current?.FindMauiContext()?.CreateLogger()?.LogWarning($"{this} is already a child of {RealParent}. Remove {this} from {RealParent} before adding to {value}."); - } + if (RealParent != null) + { + ((IElementDefinition)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged); - RealParent = value; - if (RealParent != null) - { - OnParentResourcesChanged(RealParent.GetMergedResources()); - ((IElementDefinition)RealParent).AddResourcesChangedListener(OnParentResourcesChanged); - } + if (value != null && (RealParent is Layout || RealParent is IControlTemplated)) + Application.Current?.FindMauiContext()?.CreateLogger()?.LogWarning($"{this} is already a child of {RealParent}. Remove {this} from {RealParent} before adding to {value}."); + } - object context = value?.BindingContext; - if (value != null) - { - value.SetChildInheritedBindingContext(this, context); - } - else - { - SetInheritedBindingContext(this, null); - } + RealParent = value; + if (RealParent != null) + { + OnParentResourcesChanged(RealParent.GetMergedResources()); + ((IElementDefinition)RealParent).AddResourcesChangedListener(OnParentResourcesChanged); + } - OnParentSet(); + object context = value?.BindingContext; + if (value != null) + { + value.SetChildInheritedBindingContext(this, context); + } + else + { + SetInheritedBindingContext(this, null); + } - if (_parentOverride == null) - OnParentChangedCore(); + OnParentSet(); - OnPropertyChanged(); - } + if (_parentOverride == null) + OnParentChangedCore(); + + OnPropertyChanged(nameof(Parent)); } internal bool IsTemplateRoot { get; set; } @@ -456,7 +455,7 @@ protected override void OnBindingContextChanged() protected virtual void OnChildAdded(Element child) { - child.Parent = this; + child.SetParent(this); child.ApplyBindings(skipBindingContext: false, fromBindingContextChanged: true); @@ -471,7 +470,7 @@ protected virtual void OnChildAdded(Element child) protected virtual void OnChildRemoved(Element child, int oldLogicalIndex) { - child.Parent = null; + child.SetParent(null); ChildRemoved?.Invoke(this, new ElementEventArgs(child)); @@ -495,16 +494,6 @@ protected override void OnPropertyChanged([CallerMemberName] string propertyName Handler?.UpdateValue(propertyName); - var childrenNotDrawnByThisElement = ChildrenNotDrawnByThisElement; - if (childrenNotDrawnByThisElement is not null) - { - foreach (var logicalChildren in childrenNotDrawnByThisElement) - { - if (logicalChildren is IPropertyPropagationController controller) - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { logicalChildren }); - } - } - if (_effects?.Count > 0) { var args = new PropertyChangedEventArgs(propertyName); diff --git a/src/Controls/src/Core/Element/Element_StyleSheets.cs b/src/Controls/src/Core/Element/Element_StyleSheets.cs index 7f10b4e74db7..19a4967d1458 100644 --- a/src/Controls/src/Core/Element/Element_StyleSheets.cs +++ b/src/Controls/src/Core/Element/Element_StyleSheets.cs @@ -54,17 +54,15 @@ internal void ApplyStyleSheets() ApplyStyleSheets(sheets, this); } - void ApplyStyleSheets(List sheets, Element element) + void ApplyStyleSheets(List sheets, IVisualTreeElement element) { - if (element == null) - return; - for (var i = (sheets?.Count ?? 0) - 1; i >= 0; i--) { - ((IStyle)sheets[i]).Apply(element); + if (element is BindableObject bo) + ((IStyle)sheets[i]).Apply(bo); } - foreach (Element child in element.AllChildren) + foreach (var child in element.GetVisualChildren()) { var mergedSheets = sheets; var resourceProvider = child as IResourcesProvider; diff --git a/src/Controls/src/Core/FormattedString.cs b/src/Controls/src/Core/FormattedString.cs index 03a3e5c5e266..da9198be4628 100644 --- a/src/Controls/src/Core/FormattedString.cs +++ b/src/Controls/src/Core/FormattedString.cs @@ -44,7 +44,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) var bo = item as Span; if (bo != null) { - bo.Parent = null; + bo.Parent?.RemoveLogicalChild(bo); bo.PropertyChanging -= OnItemPropertyChanging; bo.PropertyChanged -= OnItemPropertyChanged; } @@ -59,7 +59,7 @@ void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) var bo = item as Span; if (bo != null) { - bo.Parent = this; + this.AddLogicalChild(bo); bo.PropertyChanging += OnItemPropertyChanging; bo.PropertyChanged += OnItemPropertyChanged; } diff --git a/src/Controls/src/Core/Items/ItemsView.cs b/src/Controls/src/Core/Items/ItemsView.cs index 405addd738d6..fc2a3ce343bb 100644 --- a/src/Controls/src/Core/Items/ItemsView.cs +++ b/src/Controls/src/Core/Items/ItemsView.cs @@ -108,14 +108,6 @@ public int RemainingItemsThreshold set => SetValue(RemainingItemsThresholdProperty, value); } - /// - public void AddLogicalChild(Element element) => - AddLogicalChildInternal(element); - - /// - public void RemoveLogicalChild(Element element) - => RemoveLogicalChildInternal(element); - internal static readonly BindableProperty InternalItemsLayoutProperty = BindableProperty.Create(nameof(ItemsLayout), typeof(IItemsLayout), typeof(ItemsView), LinearItemsLayout.Vertical, propertyChanged: OnInternalItemsLayoutPropertyChanged); diff --git a/src/Controls/src/Core/Layout/Layout.cs b/src/Controls/src/Core/Layout/Layout.cs index 6acd909886d0..7d903547105e 100644 --- a/src/Controls/src/Core/Layout/Layout.cs +++ b/src/Controls/src/Core/Layout/Layout.cs @@ -59,16 +59,16 @@ public IView this[int index] if (old is Element oldElement) { - oldElement.Parent = null; - VisualDiagnostics.OnChildRemoved(this, oldElement, index); + RemoveLogicalChild(oldElement, index); } - _children[index] = value; - if (value is Element newElement) { - newElement.Parent = this; - VisualDiagnostics.OnChildAdded(this, newElement); + InsertLogicalChild(index, newElement); + } + else + { + _children[index] = value; } OnUpdate(index, value, old); @@ -132,22 +132,18 @@ public void Add(IView child) return; var index = _children.Count; - _children.Add(child); + + if (child is Element element) + InsertLogicalChild(index, element); + else + _children.Add(child); OnAdd(index, child); } public void Clear() { - for (var index = Count - 1; index >= 0; index--) - { - if (this[index] is Element element) - { - OnChildRemoved(element, index); - } - } - - _children.Clear(); + ClearLogicalChildren(); OnClear(); } @@ -171,7 +167,10 @@ public void Insert(int index, IView child) if (child == null) return; - _children.Insert(index, child); + if (child is Element element) + InsertLogicalChild(index, element); + else + _children.Insert(index, child); OnInsert(index, child); } @@ -202,7 +201,10 @@ public void RemoveAt(int index) var child = _children[index]; - _children.RemoveAt(index); + if (child is Element element) + RemoveLogicalChild(element, index); + else + _children.RemoveAt(index); OnRemove(index, child); } @@ -210,12 +212,6 @@ public void RemoveAt(int index) protected virtual void OnAdd(int index, IView view) { NotifyHandler(nameof(ILayoutHandler.Add), index, view); - - // Take care of the Element internal bookkeeping - if (view is Element element) - { - OnChildAdded(element); - } } protected virtual void OnClear() @@ -226,23 +222,11 @@ protected virtual void OnClear() protected virtual void OnRemove(int index, IView view) { NotifyHandler(nameof(ILayoutHandler.Remove), index, view); - - // Take care of the Element internal bookkeeping - if (view is Element element) - { - OnChildRemoved(element, index); - } } protected virtual void OnInsert(int index, IView view) { NotifyHandler(nameof(ILayoutHandler.Insert), index, view); - - // Take care of the Element internal bookkeeping - if (view is Element element) - { - OnChildAdded(element); - } } protected virtual void OnUpdate(int index, IView view, IView oldView) diff --git a/src/Controls/src/Core/ListView/ListView.cs b/src/Controls/src/Core/ListView/ListView.cs index 43806fb90eae..feeaf23ce456 100644 --- a/src/Controls/src/Core/ListView/ListView.cs +++ b/src/Controls/src/Core/ListView/ListView.cs @@ -17,10 +17,11 @@ namespace Microsoft.Maui.Controls /// public class ListView : ItemsView, IListViewController, IElementConfiguration, IVisualTreeElement { - readonly List _logicalChildren = new List(); - IReadOnlyList IVisualTreeElement.GetVisualChildren() => _logicalChildren; - - internal override IEnumerable ChildrenNotDrawnByThisElement => _logicalChildren; + // The ListViewRenderer has some odd behavior with LogicalChildren + // https://github.com/xamarin/Xamarin.Forms/pull/12057 + // Ideally we'd fix the ListViewRenderer so we don't have this separation + readonly List _visualChildren = new List(); + IReadOnlyList IVisualTreeElement.GetVisualChildren() => _visualChildren; /// Bindable property for . public static readonly BindableProperty IsPullToRefreshEnabledProperty = BindableProperty.Create("IsPullToRefreshEnabled", typeof(bool), typeof(ListView), false); @@ -445,7 +446,7 @@ protected override void SetupContent(Cell content, int index) if (content != null) { - _logicalChildren.Add(content); + _visualChildren.Add(content); content.Parent = this; VisualDiagnostics.OnChildAdded(this, content); } @@ -457,10 +458,10 @@ protected override void UnhookContent(Cell content) if (content == null) return; - var index = _logicalChildren.IndexOf(content); + var index = _visualChildren.IndexOf(content); if (index == -1) return; - _logicalChildren.RemoveAt(index); + _visualChildren.RemoveAt(index); content.Parent = null; VisualDiagnostics.OnChildRemoved(this, content, index); diff --git a/src/Controls/src/Core/Menu/FlyoutBase.cs b/src/Controls/src/Core/Menu/FlyoutBase.cs index 8e1e1a1e7b58..2dd9b5b5aa13 100644 --- a/src/Controls/src/Core/Menu/FlyoutBase.cs +++ b/src/Controls/src/Core/Menu/FlyoutBase.cs @@ -7,12 +7,7 @@ public abstract class FlyoutBase : Element, IFlyout public static readonly BindableProperty ContextFlyoutProperty = BindableProperty.CreateAttached("ContextFlyout", typeof(FlyoutBase), typeof(FlyoutBase), null, propertyChanged: (bo, oldV, newV) => { - if (oldV is BindableObject oldMenu) - VisualElement.SetInheritedBindingContext(oldMenu, null); - - if (newV is BindableObject newMenu) - VisualElement.SetInheritedBindingContext(newMenu, bo.BindingContext); - + bo.AddRemoveLogicalChildren(oldV, newV); }); public static void SetContextFlyout(BindableObject b, FlyoutBase value) diff --git a/src/Controls/src/Core/Menu/MenuBarItem.cs b/src/Controls/src/Core/Menu/MenuBarItem.cs index 4dffe30b430d..2e9e700b3fe9 100644 --- a/src/Controls/src/Core/Menu/MenuBarItem.cs +++ b/src/Controls/src/Core/Menu/MenuBarItem.cs @@ -59,7 +59,7 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - AddLogicalChildInternal((Element)item); + AddLogicalChild((Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Add), index, item); } @@ -91,14 +91,14 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - InsertLogicalChildInternal(index, (Element)item); + InsertLogicalChild(index, (Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Insert), index, item); } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = RemoveLogicalChildInternal((Element)item, index); + var result = RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); return result; @@ -107,7 +107,7 @@ public bool Remove(IMenuElement item) public void RemoveAt(int index) { var item = _menus[index]; - RemoveLogicalChildInternal((Element)item, index); + RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); } diff --git a/src/Controls/src/Core/Menu/MenuBarTracker.cs b/src/Controls/src/Core/Menu/MenuBarTracker.cs index 46391e1017a6..6ef49c05f252 100644 --- a/src/Controls/src/Core/Menu/MenuBarTracker.cs +++ b/src/Controls/src/Core/Menu/MenuBarTracker.cs @@ -18,7 +18,6 @@ public MenuBarTracker() : this(null, null) public MenuBarTracker(Element parent, string handlerProperty) { _menuBar = new MenuBar(); - _menuBar.Parent = parent; _parent = parent; _handlerProperty = handlerProperty; CollectionChanged += OnMenuBarItemCollectionChanged; @@ -28,6 +27,15 @@ void OnMenuBarItemCollectionChanged(object sender, EventArgs e) { _menuBar.SyncMenuBarItemsFromPages(ToolbarItems); + if (_menuBar.Count == 0) + { + _menuBar.Parent?.RemoveLogicalChild(_menuBar); + } + else if (_menuBar.Parent != _parent) + { + _parent.AddLogicalChild(_menuBar); + } + if (_handlerProperty != null) { _parent?.Handler?.UpdateValue(_handlerProperty); diff --git a/src/Controls/src/Core/Menu/MenuFlyout.cs b/src/Controls/src/Core/Menu/MenuFlyout.cs index 94d4788b3f49..70c72eaddee4 100644 --- a/src/Controls/src/Core/Menu/MenuFlyout.cs +++ b/src/Controls/src/Core/Menu/MenuFlyout.cs @@ -31,7 +31,7 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - AddLogicalChildInternal((Element)item); + AddLogicalChild((Element)item); NotifyHandler(nameof(IMenuFlyoutHandler.Add), index, item); // Take care of the Element internal bookkeeping @@ -69,14 +69,14 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - InsertLogicalChildInternal(index, (Element)item); + InsertLogicalChild(index, (Element)item); NotifyHandler(nameof(IMenuFlyoutHandler.Insert), index, item); } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = RemoveLogicalChildInternal((Element)item, index); + var result = RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); return result; @@ -85,7 +85,7 @@ public bool Remove(IMenuElement item) public void RemoveAt(int index) { var item = _menus[index]; - RemoveLogicalChildInternal((Element)item, index); + RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); } diff --git a/src/Controls/src/Core/Menu/MenuFlyoutSubItem.cs b/src/Controls/src/Core/Menu/MenuFlyoutSubItem.cs index 50229af2d5d5..4b417c308582 100644 --- a/src/Controls/src/Core/Menu/MenuFlyoutSubItem.cs +++ b/src/Controls/src/Core/Menu/MenuFlyoutSubItem.cs @@ -34,7 +34,7 @@ public IMenuElement this[int index] public void Add(IMenuElement item) { var index = _menus.Count; - AddLogicalChildInternal((Element)item); + AddLogicalChild((Element)item); NotifyHandler(nameof(IMenuBarItemHandler.Add), index, item); } @@ -66,14 +66,14 @@ public int IndexOf(IMenuElement item) public void Insert(int index, IMenuElement item) { - InsertLogicalChildInternal(index, (Element)item); + InsertLogicalChild(index, (Element)item); NotifyHandler(nameof(IMenuFlyoutSubItemHandler.Insert), index, item); } public bool Remove(IMenuElement item) { var index = _menus.IndexOf(item); - var result = RemoveLogicalChildInternal((Element)item, index); + var result = RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); return result; @@ -82,7 +82,7 @@ public bool Remove(IMenuElement item) public void RemoveAt(int index) { var item = _menus[index]; - RemoveLogicalChildInternal((Element)item, index); + RemoveLogicalChild((Element)item, index); NotifyHandler(nameof(IMenuFlyoutHandler.Remove), index, item); } diff --git a/src/Controls/src/Core/NavigationPage/NavigationPage.cs b/src/Controls/src/Core/NavigationPage/NavigationPage.cs index 8550e3cba743..7dcf97e50b0d 100644 --- a/src/Controls/src/Core/NavigationPage/NavigationPage.cs +++ b/src/Controls/src/Core/NavigationPage/NavigationPage.cs @@ -40,7 +40,8 @@ public partial class NavigationPage : Page, IPageContainer, IBarElement, I public static readonly BindableProperty IconColorProperty = BindableProperty.CreateAttached("IconColor", typeof(Color), typeof(NavigationPage), null); /// Bindable property for attached property TitleView. - public static readonly BindableProperty TitleViewProperty = BindableProperty.CreateAttached("TitleView", typeof(View), typeof(NavigationPage), null, propertyChanging: TitleViewPropertyChanging); + public static readonly BindableProperty TitleViewProperty = BindableProperty.CreateAttached("TitleView", typeof(View), typeof(NavigationPage), null, + propertyChanging: TitleViewPropertyChanging, propertyChanged: (bo, oldV, newV) => bo.AddRemoveLogicalChildren(oldV, newV)); static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null, propertyChanged: OnCurrentPageChanged); @@ -158,11 +159,6 @@ static void TitleViewPropertyChanging(BindableObject bindable, object oldValue, { page.SetTitleView((View)oldValue, (View)newValue); } - else if (oldValue != null) - { - var oldElem = (View)oldValue; - oldElem.Parent = null; - } } /// diff --git a/src/Controls/src/Core/Page/Page.cs b/src/Controls/src/Core/Page/Page.cs index 2d096190e3fc..982ca26b5b4d 100644 --- a/src/Controls/src/Core/Page/Page.cs +++ b/src/Controls/src/Core/Page/Page.cs @@ -156,26 +156,6 @@ 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 - { - var titleviewPart1TheShell = Shell.GetTitleView(this); - var titleViewPart2TheNavBar = NavigationPage.GetTitleView(this); - - if (titleviewPart1TheShell != null) - yield return titleviewPart1TheShell; - - if (titleViewPart2TheNavBar != null) - yield return titleViewPart2TheNavBar; - } - } - bool ISafeAreaView.IgnoreSafeArea => !On().UsingSafeArea(); Thickness ISafeAreaView2.SafeAreaInsets @@ -309,7 +289,7 @@ protected virtual void LayoutChildren(double x, double y, double width, double h area.Height = Math.Max(0, area.Height); } - List elements = ((IElementController)this).LogicalChildren.ToList(); + IList elements = this.InternalChildren; foreach (Element element in elements) { var child = element as VisualElement; @@ -395,7 +375,7 @@ protected void UpdateChildrenLayout() if (!ShouldLayoutChildren()) return; - var logicalChildren = ((IElementController)this).LogicalChildren; + var logicalChildren = this.InternalChildren; var startingLayout = new List(logicalChildren.Count); foreach (Element el in logicalChildren) { @@ -435,7 +415,7 @@ internal virtual void OnChildMeasureInvalidated(VisualElement child, Invalidatio } else { - var logicalChildren = ((IElementController)this).LogicalChildren; + var logicalChildren = this.InternalChildren; for (var i = 0; i < logicalChildren.Count; i++) { var v = logicalChildren[i] as VisualElement; @@ -548,30 +528,37 @@ void InternalChildrenOnCollectionChanged(object sender, NotifyCollectionChangedE var item = (Element)e.OldItems[i]; if (item is VisualElement visual) visual.MeasureInvalidated -= OnChildMeasureInvalidated; - OnChildRemoved(item, e.OldStartingIndex + i); + + RemoveLogicalChild(item); } } if (e.NewItems != null) { + int index = e.NewStartingIndex; + foreach (Element item in e.NewItems) { + int insertIndex = index; + if (insertIndex < 0) + insertIndex = InternalChildren.IndexOf(item); + if (item is VisualElement visual) - OnInternalAdded(visual); + { + visual.MeasureInvalidated += OnChildMeasureInvalidated; + + InsertLogicalChild(insertIndex, visual); + InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged); + } else - OnChildAdded(item); + InsertLogicalChild(insertIndex, item); + + if (index >= 0) + index++; } } } - void OnInternalAdded(VisualElement view) - { - view.MeasureInvalidated += OnChildMeasureInvalidated; - - OnChildAdded(view); - InvalidateMeasureInternal(InvalidationTrigger.MeasureChanged); - } - void OnPageBusyChanged() { if (!_hasAppeared) @@ -598,7 +585,7 @@ void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEvent bool ShouldLayoutChildren() { - var logicalChildren = ((IElementController)this).LogicalChildren; + var logicalChildren = this.InternalChildren; if (logicalChildren.Count == 0 || Width <= 0 || Height <= 0 || !IsPlatformStateConsistent) return false; @@ -631,12 +618,6 @@ public IPlatformElementConfiguration On() where T : IConfigPlatform internal void SetTitleView(View oldTitleView, View newTitleView) { - if (oldTitleView != null) - oldTitleView.Parent = null; - - if (newTitleView != null) - newTitleView.Parent = this; - _titleView = newTitleView; } diff --git a/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs b/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs index 4c9fc490b87f..04067f4eace0 100644 --- a/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs +++ b/src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.cs @@ -157,7 +157,7 @@ async Task SyncPlatformModalStackAsync() } var page = await PopModalPlatformAsync(animated); - page.Parent = null; + page.Parent?.RemoveLogicalChild(page); syncAgain = true; } @@ -234,7 +234,7 @@ async Task SyncPlatformModalStackAsync() (isPlatformReady && !syncing) ? PopModalPlatformAsync(animated) : Task.CompletedTask; await popTask; - modal.Parent = null; + modal.Parent?.RemoveLogicalChild(modal); _window.OnModalPopped(modal); if (FireLifeCycleEvents) @@ -255,7 +255,7 @@ public async Task PushModalAsync(Page modal, bool animated) var previousPage = CurrentPage; _modalPages.Add(new NavigationStepRequest(modal, true, animated)); - modal.Parent = _window; + _window.AddLogicalChild(modal); if (FireLifeCycleEvents) { diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index 9440ba3acf61..ae1e7154d328 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -106,6 +106,10 @@ Microsoft.Maui.Controls.IValueConverter.Convert(object? value, System.Type! targ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! targetType, object? parameter, System.Globalization.CultureInfo! culture) -> object? ~static Microsoft.Maui.Controls.GridExtensions.Add(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int left, int right, int top, int bottom) -> void ~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(string! key, object! value) -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(System.Collections.Generic.KeyValuePair item) -> void @@ -127,4 +131,8 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].set -> Microsoft.Maui.Controls.ShellNavigationQueryParameters.TryGetValue(string! key, out object! value) -> bool Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Collections.Generic.ICollection! ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task -~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task \ No newline at end of file +~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index f8f1f0acb145..d03deaa0ff6e 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -108,6 +108,10 @@ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.PhoneFlyoutPageRenderer.WillRotate(UIKit.UIInterfaceOrientation toInterfaceOrientation, double duration) -> void ~virtual Microsoft.Maui.Controls.Handlers.Items.ItemsViewController.DetermineCellReuseId(Foundation.NSIndexPath indexPath) -> string override Microsoft.Maui.Controls.Handlers.Items.ItemsViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(string! key, object! value) -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(System.Collections.Generic.KeyValuePair item) -> void @@ -129,4 +133,8 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].set -> Microsoft.Maui.Controls.ShellNavigationQueryParameters.TryGetValue(string! key, out object! value) -> bool Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Collections.Generic.ICollection! ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task -~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task \ No newline at end of file +~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index f8f1f0acb145..d03deaa0ff6e 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -108,6 +108,10 @@ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.PhoneFlyoutPageRenderer.WillRotate(UIKit.UIInterfaceOrientation toInterfaceOrientation, double duration) -> void ~virtual Microsoft.Maui.Controls.Handlers.Items.ItemsViewController.DetermineCellReuseId(Foundation.NSIndexPath indexPath) -> string override Microsoft.Maui.Controls.Handlers.Items.ItemsViewHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(string! key, object! value) -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(System.Collections.Generic.KeyValuePair item) -> void @@ -129,4 +133,8 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].set -> Microsoft.Maui.Controls.ShellNavigationQueryParameters.TryGetValue(string! key, out object! value) -> bool Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Collections.Generic.ICollection! ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task -~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task \ No newline at end of file +~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 41e62a8610f3..fca94df92b88 100644 --- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -113,3 +113,11 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Coll ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task ~static readonly Microsoft.Maui.Controls.InputView.IsTextPredictionEnabledProperty -> Microsoft.Maui.Controls.BindableProperty +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index c14625882eef..24a36e892306 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -107,6 +107,10 @@ Microsoft.Maui.Controls.IValueConverter.Convert(object? value, System.Type! targ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! targetType, object? parameter, System.Globalization.CultureInfo! culture) -> object? ~static Microsoft.Maui.Controls.GridExtensions.Add(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int left, int right, int top, int bottom) -> void ~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(string! key, object! value) -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(System.Collections.Generic.KeyValuePair item) -> void @@ -127,4 +131,10 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].get -> Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].set -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Collections.Generic.ICollection! ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task -~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task \ No newline at end of file +~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index 0b86d15a5581..8a0d3b8c5b95 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt @@ -94,6 +94,10 @@ Microsoft.Maui.Controls.IValueConverter.Convert(object? value, System.Type! targ Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! targetType, object? parameter, System.Globalization.CultureInfo! culture) -> object? ~static Microsoft.Maui.Controls.GridExtensions.Add(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int left, int right, int top, int bottom) -> void ~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(string! key, object! value) -> void Microsoft.Maui.Controls.ShellNavigationQueryParameters.Add(System.Collections.Generic.KeyValuePair item) -> void @@ -115,4 +119,8 @@ Microsoft.Maui.Controls.ShellNavigationQueryParameters.this[string! key].set -> Microsoft.Maui.Controls.ShellNavigationQueryParameters.TryGetValue(string! key, out object! value) -> bool Microsoft.Maui.Controls.ShellNavigationQueryParameters.Values.get -> System.Collections.Generic.ICollection! ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task -~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task \ No newline at end of file +~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 4752cffc98ec..e7cdaebd6c32 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable *REMOVED*~Microsoft.Maui.Controls.Accelerator.Modifiers.set -> void *REMOVED*~Microsoft.Maui.Controls.Accelerator.Keys.set -> void +Microsoft.Maui.Controls.Element.ClearLogicalChildren() -> void ~Microsoft.Maui.Controls.Accelerator.Key.get -> string *REMOVED*override Microsoft.Maui.Controls.RefreshView.MeasureOverride(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size Microsoft.Maui.Controls.Border.~Border() -> void @@ -65,6 +66,9 @@ Microsoft.Maui.Controls.VisualElement.RefreshIsEnabledProperty() -> void override Microsoft.Maui.Controls.Button.IsEnabledCore.get -> bool override Microsoft.Maui.Controls.ImageButton.IsEnabledCore.get -> bool override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool +~Microsoft.Maui.Controls.Element.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.InsertLogicalChild(int index, Microsoft.Maui.Controls.Element element) -> void +~Microsoft.Maui.Controls.Element.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> bool ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, bool animate, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.Shell.GoToAsync(Microsoft.Maui.Controls.ShellNavigationState state, Microsoft.Maui.Controls.ShellNavigationQueryParameters shellNavigationQueryParameters) -> System.Threading.Tasks.Task ~Microsoft.Maui.Controls.WebView.UserAgent.get -> string @@ -109,4 +113,8 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool Microsoft.Maui.Controls.IValueConverter.Convert(object? value, System.Type! targetType, object? parameter, System.Globalization.CultureInfo! culture) -> object? Microsoft.Maui.Controls.IValueConverter.ConvertBack(object? value, System.Type! targetType, object? parameter, System.Globalization.CultureInfo! culture) -> object? ~static Microsoft.Maui.Controls.GridExtensions.Add(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int left, int right, int top, int bottom) -> void -~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void \ No newline at end of file +~static Microsoft.Maui.Controls.GridExtensions.AddWithSpan(this Microsoft.Maui.Controls.Grid grid, Microsoft.Maui.IView view, int row = 0, int column = 0, int rowSpan = 1, int columnSpan = 1) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.ItemsView.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.AddLogicalChild(Microsoft.Maui.Controls.Element element) -> void +*REMOVED*~Microsoft.Maui.Controls.Shell.RemoveLogicalChild(Microsoft.Maui.Controls.Element element) -> void \ 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 b2855e9ce086..b429f7b07453 100644 --- a/src/Controls/src/Core/Shell/BaseShellItem.cs +++ b/src/Controls/src/Core/Shell/BaseShellItem.cs @@ -26,10 +26,6 @@ public class BaseShellItem : NavigableElement, IPropertyPropagationController, I const string DefaultFlyoutItemLayoutStyle = "Default_FlyoutItemLayoutStyle"; protected private ObservableCollection DeclaredChildren { get; } = new ObservableCollection(); - readonly ObservableCollection _logicalChildren = new ObservableCollection(); - - private protected override IList LogicalChildrenInternalBackingStore - => _logicalChildren; #region PropertyKeys @@ -262,43 +258,9 @@ internal static void Propagate(BindableProperty property, BindableObject from, B to.SetValue(property, from.GetValue(property)); } - internal void AddLogicalChild(Element element) - { - if (element == null) - { - return; - } - - if (_logicalChildren.Contains(element)) - return; - - _logicalChildren.Add(element); - element.Parent = this; - OnChildAdded(element); - VisualDiagnostics.OnChildAdded(this, element); - } - - internal 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); - } - void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) { - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IElementController)this).LogicalChildren); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } EffectiveFlowDirection _effectiveFlowDirection = default(EffectiveFlowDirection); diff --git a/src/Controls/src/Core/Shell/Shell.cs b/src/Controls/src/Core/Shell/Shell.cs index 1f3f93756796..9c40a8755ff8 100644 --- a/src/Controls/src/Core/Shell/Shell.cs +++ b/src/Controls/src/Core/Shell/Shell.cs @@ -714,40 +714,6 @@ public Task GoToAsync(ShellNavigationState state, bool animate, ShellNavigationQ return _navigationManager.GoToAsync(state, animate, false, parameters: new ShellRouteParameters(shellNavigationQueryParameters)); } - public void AddLogicalChild(Element element) - { - if (element == null) - { - return; - } - - if (_logicalChildren.Contains(element)) - return; - - _logicalChildren.Add(element); - element.Parent = this; - OnChildAdded(element); - VisualDiagnostics.OnChildAdded(this, element); - } - - public void RemoveLogicalChild(Element element) - { - if (element == null) - { - return; - } - - element.Parent = null; - - var oldLogicalIndex = _logicalChildren.IndexOf(element); - if (oldLogicalIndex == -1) - return; - - _logicalChildren.RemoveAt(oldLogicalIndex); - OnChildRemoved(element, oldLogicalIndex); - VisualDiagnostics.OnChildRemoved(this, element, oldLogicalIndex); - } - /// Bindable property for . public static readonly BindableProperty CurrentItemProperty = BindableProperty.Create(nameof(CurrentItem), typeof(ShellItem), typeof(Shell), null, BindingMode.TwoWay, @@ -818,12 +784,6 @@ public void RemoveLogicalChild(Element element) ShellFlyoutItemsManager _flyoutManager; Page _previousPage; - ObservableCollection _logicalChildren - = new ObservableCollection(); - - private protected override IList LogicalChildrenInternalBackingStore - => _logicalChildren; - /// public Shell() { @@ -837,7 +797,6 @@ public Shell() Navigation = new NavigationImpl(this); Route = Routing.GenerateImplicitRoute("shell"); Initialize(); - InternalChildren.CollectionChanged += OnInternalChildrenCollectionChanged; if (Application.Current != null) { @@ -1336,27 +1295,8 @@ static void OnFlyoutFooterTemplateChanging(BindableObject bindable, object oldVa shell.OnFlyoutFooterTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue); } - static void OnTitleViewChanged(BindableObject bindable, object oldValue, object newValue) - { - if (!(bindable is Element owner)) - return; - - var oldView = (View)oldValue; - var newView = (View)newValue; - var shell = bindable as Shell; - - if (oldView != null) - { - shell?.RemoveLogicalChild(oldView); - oldView.Parent = null; - } - - if (newView != null) - { - newView.Parent = owner; - shell?.AddLogicalChild(newView); - } - } + static void OnTitleViewChanged(BindableObject bindable, object oldValue, object newValue) => + bindable.AddRemoveLogicalChildren(oldValue, newValue); internal FlyoutBehavior GetEffectiveFlyoutBehavior() { @@ -1468,18 +1408,6 @@ ShellAppearance GetAppearanceForPivot(Element pivot) return null; } - - void OnInternalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.NewItems != null) - foreach (Element element in e.NewItems) - AddLogicalChild(element); - - if (e.OldItems != null) - foreach (Element element in e.OldItems) - RemoveLogicalChild(element); - } - void NotifyFlyoutBehaviorObservers() { if (CurrentItem == null || GetVisiblePage() == null) @@ -1498,7 +1426,7 @@ void OnFlyoutHeaderChanged(object oldVal, object newVal) FlyoutHeaderTemplate, ref _flyoutHeaderView, newVal, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild); } @@ -1508,7 +1436,7 @@ void OnFlyoutHeaderTemplateChanged(DataTemplate oldValue, DataTemplate newValue) newValue, ref _flyoutHeaderView, FlyoutHeader, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild, this); } @@ -1519,7 +1447,7 @@ void OnFlyoutFooterChanged(object oldVal, object newVal) FlyoutFooterTemplate, ref _flyoutFooterView, newVal, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild); } @@ -1529,7 +1457,7 @@ void OnFlyoutFooterTemplateChanged(DataTemplate oldValue, DataTemplate newValue) newValue, ref _flyoutFooterView, FlyoutFooter, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild, this); } @@ -1589,13 +1517,7 @@ Element WalkToPage(Element element) void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) { - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IElementController)this).LogicalChildren); - if (FlyoutHeaderView != null) - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { FlyoutHeaderView }); - if (FlyoutFooterView != null) - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { FlyoutFooterView }); - if (FlyoutContentView != null) - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { FlyoutContentView }); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } protected override void LayoutChildren(double x, double y, double width, double height) @@ -1663,7 +1585,7 @@ void OnFlyoutContentChanged(object oldVal, object newVal) FlyoutContentTemplate, ref _flyoutContentView, newVal, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild); } @@ -1673,7 +1595,7 @@ void OnFlyoutContentTemplateChanged(DataTemplate oldValue, DataTemplate newValue newValue, ref _flyoutContentView, FlyoutContent, - RemoveLogicalChild, + (element) => RemoveLogicalChild(element), AddLogicalChild, this); } diff --git a/src/Controls/src/Core/Shell/ShellItem.cs b/src/Controls/src/Core/Shell/ShellItem.cs index 0f13e7d41277..65c7d92a9ec2 100644 --- a/src/Controls/src/Core/Shell/ShellItem.cs +++ b/src/Controls/src/Core/Shell/ShellItem.cs @@ -132,7 +132,7 @@ bool IShellItemController.ShowTabs #region IPropertyPropagationController void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) { - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, Items); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } #endregion diff --git a/src/Controls/src/Core/Shell/ShellSection.cs b/src/Controls/src/Core/Shell/ShellSection.cs index 8201e545891a..5b3723b6c5e2 100644 --- a/src/Controls/src/Core/Shell/ShellSection.cs +++ b/src/Controls/src/Core/Shell/ShellSection.cs @@ -194,7 +194,7 @@ event NotifyCollectionChangedEventHandler IShellSectionController.ItemsCollectio #region IPropertyPropagationController void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) { - PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, Items); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } #endregion @@ -1215,8 +1215,6 @@ ShellNavigationState GetUpdatedStatus(IReadOnlyList stack) } } - IReadOnlyList IVisualTreeElement.GetVisualChildren() => AllChildren.ToList(); - #nullable enable // This code only runs for shell bits that are running through a proper // ShellHandler diff --git a/src/Controls/src/Core/StyleSheets/Style.cs b/src/Controls/src/Core/StyleSheets/Style.cs index aac7cec0b463..df0e8ab63b53 100644 --- a/src/Controls/src/Core/StyleSheets/Style.cs +++ b/src/Controls/src/Core/StyleSheets/Style.cs @@ -81,7 +81,7 @@ public void Apply(VisualElement styleable, bool inheriting = false) } } - foreach (var child in styleable.LogicalChildrenInternal) + foreach (var child in ((IVisualTreeElement)styleable).GetVisualChildren()) { var ve = child as VisualElement; if (ve == null) diff --git a/src/Controls/src/Core/SwipeView/SwipeView.cs b/src/Controls/src/Core/SwipeView/SwipeView.cs index f195e88f6edb..99fe3132d42b 100644 --- a/src/Controls/src/Core/SwipeView/SwipeView.cs +++ b/src/Controls/src/Core/SwipeView/SwipeView.cs @@ -419,7 +419,7 @@ void UpdateLogicalChildren() return; foreach (var swipeItem in swipeItems) - AddLogicalChildInternal((Element)swipeItem); + AddLogicalChild((Element)swipeItem); } SwipeItems? GetSwipeItemsByDirection(SwipeDirection? swipeDirection) diff --git a/src/Controls/src/Core/VisualElement/VisualElement.Platform.cs b/src/Controls/src/Core/VisualElement/VisualElement.Platform.cs index 07a3ecd8f7b7..5286c701323c 100644 --- a/src/Controls/src/Core/VisualElement/VisualElement.Platform.cs +++ b/src/Controls/src/Core/VisualElement/VisualElement.Platform.cs @@ -22,7 +22,7 @@ partial void HandlePlatformUnloadedLoaded() _loadedUnloadedToken = null; // Window and this VisualElement both have a handler to work with - if (Window?.Handler?.PlatformView != null && + if (Window?.Handler?.PlatformView is not null && Handler?.PlatformView is PlatformView view) { if (view.IsLoaded()) @@ -40,7 +40,7 @@ partial void HandlePlatformUnloadedLoaded() { // My handler is still set but the window handler isn't set. // This means I'm starting to detach from the platform window - // So we wait for the platform detatch events to fire before calling + // So we wait for the platform detach events to fire before calling // OnUnloaded if (Handler?.PlatformView is PlatformView detachingView && detachingView.IsLoaded()) diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs index 6d1a12d382ec..d52e6b57f0f4 100644 --- a/src/Controls/src/Core/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/VisualElement/VisualElement.cs @@ -1847,6 +1847,10 @@ void OnLoadedCore() _isLoadedFired = true; _loaded?.Invoke(this, EventArgs.Empty); + + // If the user is also watching unloaded we need to verify + // unloaded is still correctly being watched for. + UpdatePlatformUnloadedLoadedWiring(Window); } void OnUnloadedCore() @@ -1856,6 +1860,10 @@ void OnUnloadedCore() _isLoadedFired = false; _unloaded?.Invoke(this, EventArgs.Empty); + + // If the user is also watching loaded we need to verify + // loaded is still correctly being watched for. + UpdatePlatformUnloadedLoadedWiring(Window); } static void OnWindowChanged(BindableObject bindable, object? oldValue, object? newValue) @@ -1863,10 +1871,10 @@ static void OnWindowChanged(BindableObject bindable, object? oldValue, object? n if (bindable is not VisualElement visualElement) return; - if (visualElement._watchingPlatformLoaded && oldValue is Window oldWindow) - oldWindow.HandlerChanged -= visualElement.OnWindowHandlerChanged; + var newWindow = (Window?)newValue; + var oldWindow = (Window?)oldValue; - visualElement.UpdatePlatformUnloadedLoadedWiring(newValue as Window); + visualElement.UpdatePlatformUnloadedLoadedWiring(newWindow, oldWindow); visualElement.InvalidateStateTriggers(newValue != null); visualElement._windowChanged?.Invoke(visualElement, EventArgs.Empty); } @@ -1880,18 +1888,18 @@ void OnWindowHandlerChanged(object? sender, EventArgs e) // if the user is watching for them. Otherwise // this will get wired up for every single VE that's on // the screen - void UpdatePlatformUnloadedLoadedWiring(Window? window) + void UpdatePlatformUnloadedLoadedWiring(Window? newWindow, Window? oldWindow = null) { // If I'm not attached to a window and I haven't started watching any platform events // then it's not useful to wire anything up. We will just wait until // This VE gets connected to the xplat Window before wiring up any events - if (!_watchingPlatformLoaded && window == null) + if (!_watchingPlatformLoaded && newWindow is null) return; - if (_unloaded == null && _loaded == null) + if (_unloaded is null && _loaded is null) { - if (window is not null) - window.HandlerChanged -= OnWindowHandlerChanged; + if (newWindow is not null) + newWindow.HandlerChanged -= OnWindowHandlerChanged; #if PLATFORM _loadedUnloadedToken?.Dispose(); @@ -1901,11 +1909,23 @@ void UpdatePlatformUnloadedLoadedWiring(Window? window) _watchingPlatformLoaded = false; return; } + else if (oldWindow is not null) + { + oldWindow.HandlerChanged -= OnWindowHandlerChanged; + _watchingPlatformLoaded = false; + + if (newWindow is null) + { + // This will take care of cleaning up any pending unloaded events + HandlePlatformUnloadedLoaded(); + return; + } + } if (!_watchingPlatformLoaded) { - if (window is not null) - window.HandlerChanged += OnWindowHandlerChanged; + if (newWindow is not null) + newWindow.HandlerChanged += OnWindowHandlerChanged; _watchingPlatformLoaded = true; } diff --git a/src/Controls/src/Core/Window/Window.cs b/src/Controls/src/Core/Window/Window.cs index fb31d65d32cb..5bf70b1ea968 100644 --- a/src/Controls/src/Core/Window/Window.cs +++ b/src/Controls/src/Core/Window/Window.cs @@ -13,7 +13,7 @@ namespace Microsoft.Maui.Controls { [ContentProperty(nameof(Page))] - public partial class Window : NavigableElement, IWindow, IVisualTreeElement, IToolbarElement, IMenuBarElement, IFlowDirectionController, IWindowController + public partial class Window : NavigableElement, IWindow, IToolbarElement, IMenuBarElement, IFlowDirectionController, IWindowController { /// Bindable property for . public static readonly BindableProperty TitleProperty = BindableProperty.Create( @@ -367,7 +367,7 @@ static void FlowDirectionChanged(BindableObject bindable, object oldValue, objec PropertyPropagationExtensions.PropagatePropertyChanged( FlowDirectionProperty.PropertyName, (Element)bindable, - ((IElementController)bindable).LogicalChildren); + ((IVisualTreeElement)bindable).GetVisualChildren()); } bool IFlowDirectionController.ApplyEffectiveFlowDirectionToChildContainer => true; @@ -412,8 +412,6 @@ internal void OnModalPopped(Page modalPage) ModalPopped?.Invoke(this, args); Application?.NotifyOfWindowModalEvent(args); - VisualDiagnostics.OnChildRemoved(this, modalPage, index); - #if WINDOWS this.Handler?.UpdateValue(nameof(IWindow.TitleBarDragRectangles)); this.Handler?.UpdateValue(nameof(ITitledElement.Title)); @@ -434,7 +432,6 @@ internal void OnModalPushed(Page modalPage) var args = new ModalPushedEventArgs(modalPage); ModalPushed?.Invoke(this, args); Application?.NotifyOfWindowModalEvent(args); - VisualDiagnostics.OnChildAdded(this, modalPage); #if WINDOWS this.Handler?.UpdateValue(nameof(IWindow.TitleBarDragRectangles)); @@ -570,12 +567,6 @@ private protected override void OnHandlerChangingCore(HandlerChangingEventArgs a } } - // Currently this returns MainPage + ModalStack - // Depending on how we want this to show up inside LVT - // we might want to change this to only return the currently visible page - IReadOnlyList IVisualTreeElement.GetVisualChildren() => - _visualChildren; - static void OnPageChanging(BindableObject bindable, object oldValue, object newValue) { if (oldValue is Page oldPage) @@ -588,7 +579,7 @@ void OnPageChanged(Page? oldPage, Page? newPage) { _menuBarTracker.Target = null; _visualChildren.Remove(oldPage); - RemoveLogicalChildInternal(oldPage); + RemoveLogicalChild(oldPage); oldPage.HandlerChanged -= OnPageHandlerChanged; oldPage.HandlerChanging -= OnPageHandlerChanging; } @@ -599,7 +590,7 @@ void OnPageChanged(Page? oldPage, Page? newPage) if (newPage != null) { _visualChildren.Add(newPage); - AddLogicalChildInternal(newPage); + AddLogicalChild(newPage); newPage.NavigationProxy.Inner = NavigationProxy; _menuBarTracker.Target = newPage; diff --git a/src/Controls/tests/Core.UnitTests/ListViewTests.cs b/src/Controls/tests/Core.UnitTests/ListViewTests.cs index 83eaccad42ce..d1258346ddef 100644 --- a/src/Controls/tests/Core.UnitTests/ListViewTests.cs +++ b/src/Controls/tests/Core.UnitTests/ListViewTests.cs @@ -552,7 +552,7 @@ public void UncollectableHeaderReferences() { list.ItemsSource = i % 2 > 0 ? newList1 : newList2; - // grab a header just so we can be sure its reailized + // grab a header just so we can be sure its realized var header = list.TemplatedItems.GetGroup(0).HeaderContent; } @@ -560,7 +560,7 @@ public void UncollectableHeaderReferences() GC.WaitForPendingFinalizers(); // use less or equal because mono will keep the last header var alive no matter what - Assert.True(TestCell.NumberOfCells <= 6); + Assert.True(TestCell.NumberOfCells <= 6, $"{TestCell.NumberOfCells} <= 6"); var keepAlive = list.ToString(); } diff --git a/src/Controls/tests/Core.UnitTests/PageTests.cs b/src/Controls/tests/Core.UnitTests/PageTests.cs index 2430d32cda13..1eb2bcaacf1e 100644 --- a/src/Controls/tests/Core.UnitTests/PageTests.cs +++ b/src/Controls/tests/Core.UnitTests/PageTests.cs @@ -559,5 +559,25 @@ public void SendDisappearingToChildrenPageFirst() Assert.True(sentNav); Assert.True(sent); } + + [Fact] + public void LogicalChildrenDontAddToPagesInternalChildren() + { + var page = new ContentPage() + { + Content = new VerticalStackLayout() + }; + + var window = new TestWindow(page); + + var customControl = new VerticalStackLayout(); + Shell.SetTitleView(page, new VerticalStackLayout()); + page.AddLogicalChild(customControl); + + Assert.Equal(window, customControl.Window); + Assert.Single(page.InternalChildren); + Assert.Contains(customControl, page.LogicalChildrenInternal); + Assert.Contains(customControl, ((IVisualTreeElement)page).GetVisualChildren()); + } } } diff --git a/src/Controls/tests/Core.UnitTests/ShellTests.cs b/src/Controls/tests/Core.UnitTests/ShellTests.cs index b71ba795fa59..68045f920352 100644 --- a/src/Controls/tests/Core.UnitTests/ShellTests.cs +++ b/src/Controls/tests/Core.UnitTests/ShellTests.cs @@ -771,7 +771,9 @@ public async Task TitleViewLogicalChild() Shell.SetTitleView(page, layout); - Assert.Contains(layout, page.ChildrenNotDrawnByThisElement); + Assert.Contains(layout, page.LogicalChildren); + Assert.DoesNotContain(layout, page.InternalChildren); + Assert.Contains(layout, ((IVisualTreeElement)page).GetVisualChildren()); } [Fact] diff --git a/src/Controls/tests/Core.UnitTests/TabbedFormUnitTests.cs b/src/Controls/tests/Core.UnitTests/TabbedFormUnitTests.cs index a0dcbbfffcf4..bfe8c07ff224 100644 --- a/src/Controls/tests/Core.UnitTests/TabbedFormUnitTests.cs +++ b/src/Controls/tests/Core.UnitTests/TabbedFormUnitTests.cs @@ -28,5 +28,22 @@ public void TestConstructor() Assert.Empty(page.Children); } + + [Fact] + public void LogicalAndInternalChildrenMaintainOrder() + { + TabbedPage tabbedPage = new TabbedPage(); + + ContentPage page1 = new ContentPage(); + ContentPage page2 = new ContentPage(); + + tabbedPage.Children.Add(page2); + tabbedPage.Children.Insert(0, page1); + tabbedPage.Children.Remove(page1); + tabbedPage.Children.Insert(0, page1); + + Assert.Equal(tabbedPage.LogicalChildren[0], tabbedPage.InternalChildren[0]); + Assert.Equal(tabbedPage.LogicalChildren[1], tabbedPage.InternalChildren[1]); + } } } diff --git a/src/Controls/tests/Core.UnitTests/VisualTreeHelperTests.cs b/src/Controls/tests/Core.UnitTests/VisualTreeHelperTests.cs index c05c37ae33d9..1085d16a7639 100644 --- a/src/Controls/tests/Core.UnitTests/VisualTreeHelperTests.cs +++ b/src/Controls/tests/Core.UnitTests/VisualTreeHelperTests.cs @@ -26,7 +26,7 @@ public void Dispose() void OnVisualTreeChanged(object? sender, VisualTreeChangeEventArgs e) { - Assert.True(e.ChildIndex >= 0, "Visual Tree inaccurate when OnVisualTreeChanged called"); + Assert.True(e.ChildIndex >= 0,$"Visual Tree inaccurate when OnVisualTreeChanged called. ChildIndex: {e.ChildIndex}"); _treeEvents.Add((sender as Element, e)); VisualTreeChanged?.Invoke(sender as Element, e); } diff --git a/src/Controls/tests/Core.UnitTests/WindowsTests.cs b/src/Controls/tests/Core.UnitTests/WindowsTests.cs index 57723186319f..f45d5fb2d14f 100644 --- a/src/Controls/tests/Core.UnitTests/WindowsTests.cs +++ b/src/Controls/tests/Core.UnitTests/WindowsTests.cs @@ -425,6 +425,7 @@ void ValidateSetup(Application app, Page page = null) Assert.Equal(window.LogicalChildrenInternal[0], page); Assert.Single(app.LogicalChildrenInternal); Assert.Single(window.LogicalChildrenInternal); + Assert.Single(window.LogicalChildrenInternal.OfType()); Assert.Equal(app.NavigationProxy, window.NavigationProxy.Inner); Assert.Equal(window.NavigationProxy, page.NavigationProxy.Inner); } diff --git a/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs b/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs index 85f64df0839a..655afc68cc8a 100644 --- a/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs +++ b/src/Controls/tests/DeviceTests/ControlsHandlerTestBase.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -121,7 +122,10 @@ protected Task CreateHandlerAndAddToWindow(IElement view, Func { @@ -355,12 +359,13 @@ async protected Task ValidatePropertyUpdatesAfterInitValue( Assert.Equal(expectedSetValue, nativeVal); } - protected Task OnLoadedAsync(VisualElement frameworkElement, TimeSpan? timeOut = null) + protected async Task OnLoadedAsync(VisualElement frameworkElement, TimeSpan? timeOut = null) { timeOut = timeOut ?? TimeSpan.FromSeconds(2); var source = new TaskCompletionSource(); if (frameworkElement.IsLoaded && frameworkElement.IsLoadedOnPlatform()) { + await Task.Yield(); source.TrySetResult(); } else @@ -378,15 +383,16 @@ protected Task OnLoadedAsync(VisualElement frameworkElement, TimeSpan? timeOut = frameworkElement.Loaded += loaded; } - return HandleLoadedUnloadedIssue(source.Task, timeOut.Value, () => frameworkElement.IsLoaded && frameworkElement.IsLoadedOnPlatform()); + await HandleLoadedUnloadedIssue(source.Task, timeOut.Value, () => frameworkElement.IsLoaded && frameworkElement.IsLoadedOnPlatform()); } - protected Task OnUnloadedAsync(VisualElement frameworkElement, TimeSpan? timeOut = null) + protected async Task OnUnloadedAsync(VisualElement frameworkElement, TimeSpan? timeOut = null) { timeOut = timeOut ?? TimeSpan.FromSeconds(2); var source = new TaskCompletionSource(); if (!frameworkElement.IsLoaded && !frameworkElement.IsLoadedOnPlatform()) { + await Task.Yield(); source.TrySetResult(); } // in the xplat code we switch Loaded to Unloaded if the window property is removed. @@ -411,7 +417,7 @@ protected Task OnUnloadedAsync(VisualElement frameworkElement, TimeSpan? timeOut frameworkElement.Unloaded += unloaded; } - return HandleLoadedUnloadedIssue(source.Task, timeOut.Value, () => !frameworkElement.IsLoaded && !frameworkElement.IsLoadedOnPlatform()); + await HandleLoadedUnloadedIssue(source.Task, timeOut.Value, () => !frameworkElement.IsLoaded && !frameworkElement.IsLoadedOnPlatform()); } // Modal Page's appear to currently not fire loaded/unloaded diff --git a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs index aa24faf6746f..ca42415ebde0 100644 --- a/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/CollectionView/CollectionViewTests.cs @@ -143,7 +143,7 @@ await CreateHandlerAndAddToWindow(layout, async handler => await WaitForUIUpdate(frame, collectionView); frame = collectionView.Frame; -#if WINDOWS +#if WINDOWS || ANDROID // On Windows, the ListView pops in and changes the frame, then actually // loads in the data, which updates it again. So we need to wait for the second // update before checking the size diff --git a/src/Controls/tests/DeviceTests/Elements/VisualElementTests.cs b/src/Controls/tests/DeviceTests/Elements/VisualElementTests.cs index 555571874f7f..9589b1bec7aa 100644 --- a/src/Controls/tests/DeviceTests/Elements/VisualElementTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/VisualElementTests.cs @@ -106,7 +106,7 @@ await CreateHandlerAndAddToWindow(parentLayout, async (handler) = Assert.Equal(1, unloaded); }); - await Task.Delay(1000); + await AssertionExtensions.Wait(() => loaded == 2 && unloaded == 2); Assert.Equal(2, loaded); Assert.Equal(2, unloaded); @@ -154,6 +154,7 @@ await CreateHandlerAndAddToWindow(navPage, async (handler await navPage.PopAsync(); + await AssertionExtensions.Wait(() => loaded == 1 && unloaded == 1); Assert.Equal(1, loaded); Assert.Equal(1, unloaded); }); diff --git a/src/Controls/tests/Xaml.UnitTests/FontImageExtension.xaml.cs b/src/Controls/tests/Xaml.UnitTests/FontImageExtension.xaml.cs index 31d09768fe30..f21119b0ef3a 100644 --- a/src/Controls/tests/Xaml.UnitTests/FontImageExtension.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/FontImageExtension.xaml.cs @@ -24,7 +24,7 @@ class Tests public void FontImageExtension_Positive(bool useCompiledXaml) { var layout = new FontImageExtension(useCompiledXaml); - var tabs = layout.AllChildren; + var tabs = ((IVisualTreeElement)layout).GetVisualChildren(); int i = 0; foreach (var tab in tabs) @@ -53,7 +53,7 @@ public void FontImageExtension_Positive(bool useCompiledXaml) public void FontImageExtension_Negative(bool useCompiledXaml) { var layout = new FontImageExtension(useCompiledXaml); - var tabs = layout.AllChildren; + var tabs = ((IVisualTreeElement)layout).GetVisualChildren(); foreach (var tab in tabs) {