Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Attached properties via CaptureUnmatchedValues #439

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions samples/ControlGallery/Views/Grids.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@page "/grids"

<ContentPage Title="Grids">
<ScrollView>
<VerticalStackLayout Spacing="12">
<Label Text="Grid with GridCells:" />

<Grid ColumnDefinitions="*,*,*" RowDefinitions="*,*,*" HorizontalOptions="LayoutOptions.Center" WidthRequest="300">
<GridCell Row="0" Column="0" ColumnSpan="2" RowSpan="2">
<Frame BackgroundColor="Colors.DarkRed">
<Label Text="1" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</GridCell>
<GridCell Row="0" Column="2">
<Frame BackgroundColor="Colors.DarkSeaGreen">
<Label Text="2" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</GridCell>
<GridCell Row="1" Column="2">
<Frame BackgroundColor="Colors.LightCyan">
<Label Text="3" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</GridCell>
<GridCell Row="2" Column="0">
<Frame BackgroundColor="Colors.DarkGreen">
<Label Text="4" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</GridCell>
<GridCell Row="2" Column="1" ColumnSpan="2">
<Frame BackgroundColor="Colors.DarkViolet">
<Label Text="5" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</GridCell>
</Grid>

<Label Text="Grid with attached properties:" />


<Grid ColumnDefinitions="*,*,*" RowDefinitions="*,*,*" HorizontalOptions="LayoutOptions.Center" WidthRequest="300">
<Frame BackgroundColor="Colors.DarkViolet" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">
<Label Text="1" HorizontalOptions="LayoutOptions.Center" />
</Frame>

<Frame BackgroundColor="Colors.DarkRed" Grid.Row="0" Grid.Column="1">
<Label Text="2" HorizontalOptions="LayoutOptions.Center" />
</Frame>

<Frame BackgroundColor="Colors.DarkSeaGreen" Grid.Row="0" Grid.Column="3">
<Label Text="3" HorizontalOptions="LayoutOptions.Center" />
</Frame>

<Frame BackgroundColor="Colors.LightCyan" Grid.Row="@(1)" Grid.Column=@(1) Grid.ColumnSpan="2" Grid.RowSpan="2">
<Label Text="4" HorizontalOptions="LayoutOptions.Center" />
</Frame>

<Frame BackgroundColor="Colors.DarkGreen" Grid.Row="2" Grid.Column="0">
<Label Text="5" HorizontalOptions="LayoutOptions.Center" />
</Frame>
</Grid>

</VerticalStackLayout>
</ScrollView>
</ContentPage>
17 changes: 9 additions & 8 deletions samples/ControlGallery/Views/PlaygroundList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
<ScrollView>
<StackLayout Padding="10" Spacing="10">
<Label Text="Select a playground..." FontSize="30" FontAttributes="FontAttributes.Bold" />
<BoxView Margin="10" HeightRequest="2" Color="Colors.DarkGray" HorizontalOptions="LayoutOptions.FillAndExpand" />
<Button Text="Common controls" OnClick="@(async () => await NavigationManager.NavigateToAsync("/commoncontrols"))"></Button>
<Button Text="Navigation playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/navigation"))"></Button>
<Button Text="Gesture playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/gestureplayground"))"></Button>
<Button Text="Skia Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/skia"))"></Button>
<Button Text="Picker Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/pickerplayground"))"></Button>
<Button Text="Editor Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/editorplayground"))"></Button>
<Button Text="CollectionView Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/collectionviewplayground"))"></Button>
<BoxView Margin="10" HeightRequest="2" Color="Colors.DarkGray" HorizontalOptions="LayoutOptions.Fill" />
<Button Text="Common controls" OnClick="@(async () => await NavigationManager.NavigateToAsync("/commoncontrols"))" />
<Button Text="Navigation playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/navigation"))" />
<Button Text="Gesture playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/gestureplayground"))" />
<Button Text="Skia Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/skia"))" />
<Button Text="Picker Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/pickerplayground"))" />
<Button Text="Editor Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/editorplayground"))" />
<Button Text="CollectionView Playground" OnClick="@(async () => await NavigationManager.NavigateToAsync("/collectionviewplayground"))" />
<Button Text="Grids" OnClick="@(async () => await NavigationManager.NavigateToAsync("/grids"))" />
</StackLayout>
</ScrollView>
</ContentPage>
11 changes: 6 additions & 5 deletions samples/ControlGallery/Views/ShellPropertiesPlayground.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<ContentPage @ref="page">
<ContentPage @ref="page"
Title="Shell Properties"
Shell.NavBarIsVisible="@navBarVisible"
Shell.TabBarIsVisible="@tabBarVisible"
Shell.TitleColor="@titleColor">

<ToolbarItems>
<ToolbarItem Text="Help" OnClick="ShowHelp" />
</ToolbarItems>
Expand All @@ -11,10 +16,6 @@
</Background>

<ChildContent>
<ShellProperties NavBarIsVisible="navBarVisible"
TabBarIsVisible="tabBarVisible"
TitleColor="titleColor" />

<StackLayout>
<StackLayout Orientation="StackOrientation.Horizontal">
<Label Text="Enable NavBar: " />
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.MobileBlazorBindings/AttachedPropertyRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.Maui.Controls;
using System;
using System.Collections.Generic;

namespace Microsoft.MobileBlazorBindings
{
public static class AttachedPropertyRegistry
{
internal static readonly Dictionary<string, Action<Element, object>> AttachedPropertyHandlers = new();

public static void RegisterAttachedPropertyHandler(string propertyName, Action<Element, object> handler)
{
AttachedPropertyHandlers[propertyName] = handler;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,26 @@ public static partial class AttributeHelper
{
public static bool GetBool(object value, bool defaultValueIfNull = default)
{
return (value == null)
? defaultValueIfNull
: (string.Equals((string)value, "1", StringComparison.Ordinal));
return value switch
{
null => defaultValueIfNull,
bool bln => bln,
string str when str == "1" => true,
string str when str == "0" => false,
string str when bool.TryParse(str, out var bln) => bln,
_ => throw new NotSupportedException($"Cannot get bool value from {value.GetType().Name} attribute.")
};
}

public static int GetInt(object value, int defaultValueIfNull = default)
{
return (value == null)
? defaultValueIfNull
: int.Parse((string)value, CultureInfo.InvariantCulture);
return value switch
{
null => defaultValueIfNull,
int i => i,
string str => int.Parse(str, CultureInfo.InvariantCulture),
_ => throw new NotSupportedException($"Cannot get int value from {value.GetType().Name} attribute.")
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using System;

namespace Microsoft.MobileBlazorBindings.Elements
{
public static partial class AttributeHelper
{
public static object ObjectToAttribute(object value)
{
if (value == null || value is string || value is int || value is Delegate)
return value;

if (value is bool boolValue)
return boolValue ? "1" : "0";

return value switch
{
double d => DoubleToString(d),
float f => SingleToString(f),
uint ui => UInt32ToString(ui),
Color color => ColorToString(color),
Rect rect => RectToString(rect),
CornerRadius cornerRadius => CornerRadiusToString(cornerRadius),
DateTime dateTime => DateTimeToString(dateTime),
GridLength gridLength => GridLengthToString(gridLength),
LayoutOptions layoutOptions => LayoutOptionsToString(layoutOptions),
Thickness thickness => ThicknessToString(thickness),
TimeSpan timeSpan => TimeSpanToString(timeSpan),
Enum => (int)value,
_ => ObjectToDelegate(value)
};
}

/// <summary>
/// Helper method to serialize objects.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.MobileBlazorBindings/Elements/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.MobileBlazorBindings.Core;
using MC = Microsoft.Maui.Controls;
using System.Collections.Generic;

namespace Microsoft.MobileBlazorBindings.Elements
{
Expand All @@ -12,6 +13,7 @@ public abstract class Element : NativeControlComponentBase
[Parameter] public string AutomationId { get; set; }
[Parameter] public string ClassId { get; set; }
[Parameter] public string StyleId { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> AdditionalProperties { get; set; }

public MC.Element NativeControl => ((Handlers.ElementHandler)ElementHandler).ElementControl;

Expand All @@ -31,6 +33,13 @@ protected override void RenderAttributes(AttributesBuilder builder)
{
builder.AddAttribute(nameof(StyleId), StyleId);
}
if (AdditionalProperties != null)
{
foreach (var keyValue in AdditionalProperties)
{
builder.AddAttribute(keyValue.Key, AttributeHelper.ObjectToAttribute(keyValue.Value));
}
}
}
}
}
16 changes: 16 additions & 0 deletions src/Microsoft.MobileBlazorBindings/Elements/Grid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@

using Microsoft.AspNetCore.Components;
using Microsoft.MobileBlazorBindings.Core;
using MC = Microsoft.Maui.Controls;

namespace Microsoft.MobileBlazorBindings.Elements
{
public partial class Grid
{
static partial void RegisterAdditionalHandlers()
{
AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Grid.Column",
(element, value) => MC.Grid.SetColumn(element, AttributeHelper.GetInt(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Grid.ColumnSpan",
(element, value) => MC.Grid.SetColumnSpan(element, AttributeHelper.GetInt(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Grid.Row",
(element, value) => MC.Grid.SetRow(element, AttributeHelper.GetInt(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Grid.RowSpan",
(element, value) => MC.Grid.SetRowSpan(element, AttributeHelper.GetInt(value)));
}

/// <summary>
/// A comma-separated list of column definitions. A column definition can be:
/// Auto-sized with the <c>Auto</c> keyword; A numeric size, such as <c>80.5</c>; Or a relative size, such as <c>*</c>, <c>2*</c>, or <c>3.5*</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public virtual void ApplyAttribute(ulong attributeEventHandlerId, string attribu

public virtual bool ApplyAdditionalAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
{
if (AttachedPropertyRegistry.AttachedPropertyHandlers.TryGetValue(attributeName, out var handler))
{
handler(ElementControl, attributeValue);
return true;
}

return false;
}

Expand Down
42 changes: 42 additions & 0 deletions src/Microsoft.MobileBlazorBindings/Elements/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,48 @@ namespace Microsoft.MobileBlazorBindings.Elements
{
public partial class Shell : Page
{
static partial void RegisterAdditionalHandlers()
{
AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.NavBarIsVisible",
(element, value) => MC.Shell.SetNavBarIsVisible(element, AttributeHelper.GetBool(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.NavBarHasShadow",
(element, value) => MC.Shell.SetNavBarHasShadow(element, AttributeHelper.GetBool(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarIsVisible",
(element, value) => MC.Shell.SetTabBarIsVisible(element, AttributeHelper.GetBool(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.BackgroundColor",
(element, value) => MC.Shell.SetBackgroundColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.DisabledColor",
(element, value) => MC.Shell.SetDisabledColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.ForegroundColor",
(element, value) => MC.Shell.SetForegroundColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarBackgroundColor",
(element, value) => MC.Shell.SetTabBarBackgroundColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarDisabledColor",
(element, value) => MC.Shell.SetTabBarDisabledColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarForegroundColor",
(element, value) => MC.Shell.SetTabBarForegroundColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarTitleColor",
(element, value) => MC.Shell.SetTabBarTitleColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TabBarUnselectedColor",
(element, value) => MC.Shell.SetTabBarUnselectedColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.TitleColor",
(element, value) => MC.Shell.SetTitleColor(element, AttributeHelper.StringToColor(value)));

AttachedPropertyRegistry.RegisterAttachedPropertyHandler("Shell.UnselectedColor",
(element, value) => MC.Shell.SetUnselectedColor(element, AttributeHelper.StringToColor(value)));
}

[Parameter] public RenderFragment FlyoutHeader { get; set; }

[Parameter] public EventCallback<MC.ShellNavigatedEventArgs> OnNavigated { get; set; }
Expand Down