Skip to content
Closed
13 changes: 12 additions & 1 deletion src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Maui.Controls.Sample"
x:Class="Maui.Controls.Sample.MainPage"
xmlns:local="clr-namespace:Maui.Controls.Sample">
x:DataType="local:MainPage">
<VerticalStackLayout x:Name="myLayout">
<Label x:Name="info" Text="Click a button" VerticalOptions="Center"/>
<Button Text="Clear Grid" Clicked="ClearGrid_Clicked"/>
<Button Text="Generate Grid" Clicked="Button_Clicked"/>
<Entry x:Name="BatchSize" Text="15"/>
<Button Text="Batch Generate Grid" Clicked="BatchGenerate_ClickedAsync"/>

<HorizontalStackLayout x:Name="myGridWrapper">
<Grid x:Name="contentGrid"/>
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
102 changes: 100 additions & 2 deletions src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,107 @@
namespace Maui.Controls.Sample;
using System;
using System.Diagnostics;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample;

public partial class MainPage : ContentPage
{
const int rowCount = 30;
const int columnCount = 30;

public MainPage()
{
InitializeComponent();
}
}

private void ClearGrid_Clicked(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();

contentGrid.Clear();

sw.Stop();

info.Text = $"Clearing grid took: {sw.ElapsedMilliseconds} ms";
}

private void Button_Clicked(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();
contentGrid.Clear();

for (int n = 0; n < rowCount; n++)
{
contentGrid.RowDefinitions.Add(new RowDefinition());
}

for (int n = 0; n < columnCount; n++)
{
contentGrid.ColumnDefinitions.Add(new ColumnDefinition());
}

int i = 0;
Label[] views = new Label[rowCount * columnCount];

for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{

for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Label view = new Label() { Text = $"[{columnIndex}x{rowIndex}]" };
// Button view = new() { Text = $"[{columnIndex}x{rowIndex}]" };
// contentGrid.Add(view, column: columnIndex, row: rowIndex);

views[i] = view;
i++;

contentGrid.SetRow(view, rowIndex);
contentGrid.SetRowSpan(view, 1);

contentGrid.SetColumn(view, columnIndex);
contentGrid.SetColumnSpan(view, 1);
}
}

contentGrid.AddBulk(views);

sw.Stop();
info.Text = $"Grid was created in: {sw.ElapsedMilliseconds} ms";
}

private async void BatchGenerate_ClickedAsync(object sender, EventArgs e)
{
Stopwatch sw = Stopwatch.StartNew();

long sumMs = 0;
int batchSize = int.Parse(BatchSize.Text);

for (int i = 0; i < batchSize; i++)
{
sw.Reset();
sw.Start();

contentGrid.Clear();

for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Label view = new() { Text = $"[{columnIndex}x{rowIndex}]" };
//Button view = new() { Text = $"[{columnIndex}x{rowIndex}]" };
contentGrid.Add(view, column: columnIndex, row: rowIndex);
}
}

sw.Stop();
sumMs += sw.ElapsedMilliseconds;

int runs = i + 1;

info.Text = $"Grid was created {runs} times and it took {sumMs} ms in total. Avg run took {Math.Round(sumMs / (double)runs, 2)} ms";

await Task.Delay(1000).ConfigureAwait(true);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:Maui.Controls.Sample.Platform">

<maui:MauiWinUIApplication.Resources>
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
</maui:MauiWinUIApplication.Resources>
</maui:MauiWinUIApplication>
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Controls.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<GitInfoReportImportance>high</GitInfoReportImportance>
<MauiGenerateResourceDesigner>true</MauiGenerateResourceDesigner>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591;RS0041;RS0026;RS0027;RS0022</NoWarn>
<NoWarn>$(NoWarn);CS1591;RS0041;RS0026;RS0027;RS0016;RS0017;RS0022</NoWarn>
<NoWarn Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen' or $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">$(NoWarn);CA1420</NoWarn>
</PropertyGroup>

Expand Down
2 changes: 2 additions & 0 deletions src/Controls/src/Core/Element/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public string AutomationId
}
}

public bool IsPlatformViewNew { get; set; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is terrible. It should be internal if possible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a way to make this internal if it is a property on IElement.

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

I don't think I can simulate it that way because I need to set IsPlatformViewNew to true and then back to false:

an alternative which I would consider the best (in an ideal world) would be to pass a flag to:

_mapper.UpdateProperties(this, VirtualView);

but unfortunately then I would need to propagate that flag here:

action?.Invoke(viewHandler, virtualView);

and that would mean changing the way mappers are defined:

protected readonly Dictionary<string, Action<IElementHandler, IElement>> _mapper = new(StringComparer.Ordinal);

which seems like a very invasive thing to do (with many and many changes required). That's why I went with the IView.IsPlatformViewNew flag to avoid making changes to mappers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a way to make this internal if it is a property on IElement.

Is there a way this could detect if Handler was null and became non-null for the first time? Then maybe we wouldn't need a new bool property?

I was thinking about this a bit more and one alternative idea would be: Do not add Element.IsPlatformViewNew but rather add two new mapper calls:

  • InitializingNewElement
  • NoLongerInitializingNewElement

but I would still need to store the state somewhere and mappers are "state-less" AFAIK.


/// <summary>Gets or sets a value used to identify a collection of semantically similar elements.</summary>
/// <value>A string that represents the collection the element belongs to.</value>
/// <remarks>Use the class id property to collect together elements into semantically similar groups for identification in UI testing and in theme engines.</remarks>
Expand Down
32 changes: 32 additions & 0 deletions src/Controls/src/Core/Layout/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,28 @@ public void Add(IView child)
OnAdd(index, child);
}

/// <summary>
/// Adds a child view to the end of this layout.
/// </summary>
/// <param name="children">The child view to add.</param>
public void AddBulk(IView[] children)
{

var index = _children.Count;
_children.AddRange(children);

foreach (IView child in children)
{
if (child is Element element)
{
AddLogicalChild(element);
}
}


OnAddBulk(index, children);
}

/// <summary>
/// Clears all child views from this layout.
/// </summary>
Expand Down Expand Up @@ -295,6 +317,16 @@ protected virtual void OnAdd(int index, IView view)
NotifyHandler(nameof(ILayoutHandler.Add), index, view);
}

/// <summary>
/// Invoked when <see cref="AddBulk(IView[])"/> is called and notifies the handler associated to this layout.
/// </summary>
/// <param name="index">The index at which the child view was inserted.</param>
/// <param name="views">The child view which was inserted.</param>
protected virtual void OnAddBulk(int index, IView[] views)
{
Handler?.Invoke(nameof(ILayoutHandler.AddBulk), new Maui.Handlers.LayoutHandlerBulkUpdate(index, views));
}

/// <summary>
/// Invoked when <see cref="Clear"/> is called and notifies the handler associated to this layout.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,24 @@ public static void SetAutomationPropertiesAutomationId(this FrameworkElement Con

if (!_defaultAutomationPropertiesAccessibilityView.HasValue)
{
_defaultAutomationPropertiesAccessibilityView = currentValue = (AccessibilityView)Control.GetValue(NativeAutomationProperties.AccessibilityViewProperty);
if (Element.IsPlatformViewNew)
{
_defaultAutomationPropertiesAccessibilityView = AccessibilityView.Content;
}
else
{
_defaultAutomationPropertiesAccessibilityView = currentValue = (AccessibilityView)Control.GetValue(NativeAutomationProperties.AccessibilityViewProperty);
}
}

var newValue = _defaultAutomationPropertiesAccessibilityView;

var elemValue = (bool?)Element.GetValue(AutomationProperties.IsInAccessibleTreeProperty);
bool? elemValue = null;

if (!Element.IsPlatformViewNew)
{
elemValue = (bool?)Element.GetValue(AutomationProperties.IsInAccessibleTreeProperty);
}

if (elemValue == true)
{
Expand All @@ -66,7 +78,8 @@ public static void SetAutomationPropertiesAutomationId(this FrameworkElement Con
newValue = AccessibilityView.Raw;
}

if (currentValue is null || currentValue != newValue)

if (!Element.IsPlatformViewNew || (Element.IsPlatformViewNew && newValue != AccessibilityView.Content))
{
Control.SetValue(NativeAutomationProperties.AccessibilityViewProperty, newValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ namespace Microsoft.Maui.Controls.Platform
{
internal static class TextBlockExtensions
{
public static void UpdateLineBreakMode(this TextBlock textBlock, Label label) =>
textBlock.SetLineBreakMode(label.LineBreakMode, label.MaxLines);
public static void UpdateLineBreakMode(this TextBlock textBlock, Label label)
{
if (!label.IsPlatformViewNew || label.LineBreakMode != LineBreakMode.WordWrap || label.MaxLines != 0)
{
textBlock.SetLineBreakMode(label.LineBreakMode, label.MaxLines);
}
}

public static void UpdateLineBreakMode(this TextBlock textBlock, LineBreakMode lineBreakMode)
{
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Core/Toolbar/Toolbar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public Toolbar(Maui.IElement parent)
_parent = parent;
}

public bool IsPlatformViewNew { get; set; }
public IEnumerable<ToolbarItem> ToolbarItems { get => _toolbarItems; set => SetProperty(ref _toolbarItems, value); }
public double? BarHeight { get => _barHeight; set => SetProperty(ref _barHeight, value); }
public string BackButtonTitle { get => _backButtonTitle; set => SetProperty(ref _backButtonTitle, value); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Microsoft.Maui.Controls.Core.UnitTests
{
class ApplicationStub : IApplication
{
bool IElement.IsPlatformViewNew { get; set; }
readonly List<IWindow> _windows = new List<IWindow>();

public IElementHandler Handler { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Maui.DeviceTests.Stubs
{
class MauiAppNewWindowStub : IApplication
{
bool IElement.IsPlatformViewNew { get; set; }
readonly IWindow _window;
Window Window => _window as Window;

Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<IsTrimmable>false</IsTrimmable>
<MauiGenerateResourceDesigner>true</MauiGenerateResourceDesigner>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591;RS0041;RS0026;RS0027</NoWarn>
<NoWarn>$(NoWarn);CS1591;RS0041;RS0026;RS0016;RS0017;RS0027</NoWarn>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/Core/IElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public interface IElement
/// Gets the Parent of the Element.
/// </summary>
IElement? Parent { get; }

bool IsPlatformViewNew { get; set; }
}
}
15 changes: 14 additions & 1 deletion src/Core/src/Handlers/Element/ElementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,21 @@ public virtual void SetVirtualView(IElement view)
bool setupPlatformView = oldVirtualView == null;

VirtualView = view;
PlatformView ??= CreatePlatformElement();

bool isNew = false;

if (PlatformView is null)
{
PlatformView = CreatePlatformElement();
isNew = true;
}

VirtualView.IsPlatformViewNew = isNew;

if (VirtualView.Handler != this)
{
VirtualView.Handler = this;
}

// We set the previous virtual view to null after setting it on the incoming virtual view.
// This makes it easier for the incoming virtual view to have influence
Expand All @@ -77,6 +88,8 @@ public virtual void SetVirtualView(IElement view)
}

_mapper.UpdateProperties(this, VirtualView);

VirtualView.IsPlatformViewNew = false;
}

public virtual void UpdateValue(string property)
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Handlers/Layout/ILayoutHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface ILayoutHandler : IViewHandler
new PlatformView PlatformView { get; }

void Add(IView view);
void AddBulk(IView[] view);
void Remove(IView view);
void Clear();
void Insert(int index, IView view);
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/Handlers/Layout/LayoutHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public void Add(IView child)
PlatformView.AddView(child.ToPlatform(MauiContext), targetIndex);
}

public void AddBulk(IView[] views) => throw new NotImplementedException();

public void Remove(IView child)
{
_ = PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Handlers/Layout/LayoutHandler.Standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Maui.Handlers
public partial class LayoutHandler : ViewHandler<ILayout, object>
{
public void Add(IView view) => throw new NotImplementedException();
public void AddBulk(IView[] views) => throw new NotImplementedException();
public void Remove(IView view) => throw new NotImplementedException();
public void Clear() => throw new NotImplementedException();
public void Insert(int index, IView view) => throw new NotImplementedException();
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/Handlers/Layout/LayoutHandler.Tizen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public void Add(IView child)
PlatformView.SetNeedMeasureUpdate();
}

public void AddBulk(IView[] views) => throw new NotImplementedException();

public void Remove(IView child)
{
_ = PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
Expand Down
Loading