Skip to content

Commit

Permalink
Add Visual Tree Helper (Based on #1820) (#1845)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephane Delcroix <stephane@delcroix.org>
  • Loading branch information
drasticactions and StephaneDelcroix committed Jul 29, 2021
1 parent dfaa042 commit aa817b5
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 7 deletions.
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Microsoft.Maui.Controls
{
public partial class Application : Element, IResourcesProvider, IApplicationController, IElementConfiguration<Application>
public partial class Application : Element, IResourcesProvider, IApplicationController, IElementConfiguration<Application>, IVisualTreeElement
{
readonly WeakEventManager _weakEventManager = new WeakEventManager();
Task<IDictionary<string, object>> _propertiesTask;
Expand Down Expand Up @@ -437,5 +437,7 @@ protected internal virtual void CleanUp()

NavigationProxy = null;
}

IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => this.Windows;
}
}
6 changes: 5 additions & 1 deletion src/Controls/src/Core/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Microsoft.Maui.Controls
{
public abstract partial class Element : BindableObject, IElement, INameScope, IElementController
public abstract partial class Element : BindableObject, IElement, INameScope, IElementController, IVisualTreeElement
{
public static readonly BindableProperty MenuProperty = BindableProperty.CreateAttached(nameof(Menu), typeof(Menu), typeof(Element), null);

Expand Down Expand Up @@ -319,6 +319,10 @@ public new void SetDynamicResource(BindableProperty property, string key)
base.SetDynamicResource(property, key);
}

IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => LogicalChildren;

IVisualTreeElement IVisualTreeElement.GetVisualParent() => this.Parent;

protected override void OnBindingContextChanged()
{
this.PropagateBindingContext(LogicalChildrenInternal, (child, bc) =>
Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/ItemsView.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Maui.Controls.Internals;

Expand Down
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Size ILayoutManager.ArrangeChildren(Rectangle childBounds)
}
}

public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IFrameworkElement
public abstract class Layout : View, ILayout, ILayoutController, IPaddingElement, IFrameworkElement, IVisualTreeElement
{
public static readonly BindableProperty IsClippedToBoundsProperty =
BindableProperty.Create(nameof(IsClippedToBounds), typeof(bool), typeof(Layout), false);
Expand Down Expand Up @@ -122,6 +122,8 @@ public bool CascadeInputTransparent

public void ForceLayout() => SizeAllocated(Width, Height);

IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => Children.ToList().AsReadOnly();

[Obsolete("OnSizeRequest is obsolete as of version 2.2.0. Please use OnMeasure instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed override SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint)
Expand Down
5 changes: 4 additions & 1 deletion src/Controls/src/Core/Layout/Layout.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;

namespace Microsoft.Maui.Controls
{
[ContentProperty(nameof(Children))]
public abstract class Layout : View, Microsoft.Maui.ILayout, IList<IView>, IBindableLayout, IPaddingElement
public abstract class Layout : View, Microsoft.Maui.ILayout, IList<IView>, IBindableLayout, IPaddingElement, IVisualTreeElement
{
protected ILayoutManager _layoutManager;
public ILayoutManager LayoutManager => _layoutManager ??= CreateLayoutManager();
Expand Down Expand Up @@ -152,5 +153,7 @@ Thickness IPaddingElement.PaddingDefaultValueCreator()
{
return new Thickness(0);
}

IReadOnlyList<IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => Children.Cast<IVisualTreeElement>().ToList().AsReadOnly();
}
}
5 changes: 4 additions & 1 deletion src/Controls/src/Core/Shell/ShellContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace Microsoft.Maui.Controls
{
[ContentProperty(nameof(Content))]
public class ShellContent : BaseShellItem, IShellContentController
public class ShellContent : BaseShellItem, IShellContentController, IVisualTreeElement
{
static readonly BindablePropertyKey MenuItemsPropertyKey =
BindableProperty.CreateReadOnly(nameof(MenuItems), typeof(MenuItemCollection), typeof(ShellContent), null,
Expand Down Expand Up @@ -309,5 +309,8 @@ static void ApplyQueryAttributes(object content, IDictionary<string, string> que
}
}
}

IReadOnlyList<Maui.IVisualTreeElement> GetVisualChildren() => new List<Maui.IVisualTreeElement> { ((IShellContentController)this).Page }.AsReadOnly();

}
}
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Shell/ShellItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public TabBar()

[ContentProperty(nameof(Items))]
[EditorBrowsable(EditorBrowsableState.Never)]
public class ShellItem : ShellGroupItem, IShellItemController, IElementConfiguration<ShellItem>, IPropertyPropagationController
public class ShellItem : ShellGroupItem, IShellItemController, IElementConfiguration<ShellItem>, IPropertyPropagationController, IVisualTreeElement
{
#region PropertyKeys

Expand Down Expand Up @@ -310,5 +310,7 @@ internal override void SendDisappearing()
CurrentItem.SendDisappearing();
}
}

IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => Items.ToList().AsReadOnly();
}
}
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Shell/ShellSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class Tab : ShellSection

[ContentProperty(nameof(Items))]
[EditorBrowsable(EditorBrowsableState.Never)]
public class ShellSection : ShellGroupItem, IShellSectionController, IPropertyPropagationController
public class ShellSection : ShellGroupItem, IShellSectionController, IPropertyPropagationController, IVisualTreeElement
{
#region PropertyKeys

Expand Down Expand Up @@ -1205,5 +1205,7 @@ ShellNavigationState GetUpdatedStatus(IReadOnlyList<Page> stack)
return ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack);
}
}

IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => AllChildren.ToList().AsReadOnly();
}
}
12 changes: 12 additions & 0 deletions src/Controls/tests/Core.UnitTests/ShellTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,5 +1318,17 @@ public void SendStructureChangedFiresWhenAddingItems()
Assert.Greater(count, previousCount, "StructureChanged not fired when adding Shell Content");
}

[Test]
public void VisualTreeHelperCount()
{
Shell shell = new Shell();
shell.Items.Add(CreateShellItem());
shell.CurrentItem.Items.Add(CreateShellSection());
shell.CurrentItem.CurrentItem.Items.Add(CreateShellContent());
var shellCount = (shell as IVisualTreeElement).GetVisualChildren();
var shellItemCount = (shell.CurrentItem as IVisualTreeElement).GetVisualChildren();
Assert.AreEqual(shellCount.Count, 1);
Assert.AreEqual(shellItemCount.Count, 2);
}
}
}
74 changes: 74 additions & 0 deletions src/Controls/tests/Core.UnitTests/VisualTreeHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Graphics;
using NSubstitute;
using NUnit.Framework;

namespace Microsoft.Maui.Controls.Core.UnitTests
{
[TestFixture]
public class VisualTreeHelperTests : BaseTestFixture
{
[Test]
public void VerticalStackLayoutChildren()
{
var verticalStackLayout = new VerticalStackLayout() { IsPlatformEnabled = true };
verticalStackLayout.Children.Add(new Label());
verticalStackLayout.Children.Add(new Button());
Assert.AreEqual(verticalStackLayout.Children.Count, (verticalStackLayout as IVisualTreeElement).GetVisualChildren().Count);
}

[Test]
public void HorizontalStackLayoutChildren()
{
var horizontalStackLayout = new HorizontalStackLayout() { IsPlatformEnabled = true };
horizontalStackLayout.Children.Add(new Label());
horizontalStackLayout.Children.Add(new Button());
Assert.AreEqual(horizontalStackLayout.Children.Count, (horizontalStackLayout as IVisualTreeElement).GetVisualChildren().Count);
}

[Test]
public void StackLayoutChildren()
{
var stackLayout = new StackLayout() { IsPlatformEnabled = true };
stackLayout.Children.Add(new Label());
stackLayout.Children.Add(new Button());
Assert.AreEqual(stackLayout.Children.Count, (stackLayout as IVisualTreeElement).GetVisualChildren().Count);
}

[Test]
public void ApplicationChildren()
{
var app = new Application();
var iapp = app as IApplication;
var page = new ContentPage();

app.MainPage = page;

var window = iapp.CreateWindow(null);

var appChildren = (app as IVisualTreeElement).GetVisualChildren();
var windowChildren = (window as IVisualTreeElement).GetVisualChildren();
Assert.Greater(appChildren.Count, 0);
Assert.IsTrue(appChildren[0] is IWindow);
Assert.AreEqual(windowChildren.Count, 1);
}

[Test]
public void VisualElementParent()
{
var app = new Application();
var iapp = app as IApplication;
var page = new ContentPage();
app.MainPage = page;
var window = iapp.CreateWindow(null);
var appParent = (app as IVisualTreeElement).GetVisualParent();
var windowParent = (window as IVisualTreeElement).GetVisualParent();
Assert.IsNull(appParent);
Assert.IsNotNull(windowParent);
}
}
}
13 changes: 13 additions & 0 deletions src/Core/src/Core/IVisualTreeElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Microsoft.Maui
{
public interface IVisualTreeElement
{
IReadOnlyList<IVisualTreeElement> GetVisualChildren();

IVisualTreeElement? GetVisualParent();
}
}

0 comments on commit aa817b5

Please sign in to comment.