Skip to content

Commit

Permalink
Component Generator - generate ElementTemplates and non-generic DataT…
Browse files Browse the repository at this point in the history
…emplates (#51)
  • Loading branch information
Dreamescaper authored Sep 27, 2022
1 parent 382fcdc commit 60c59c6
Show file tree
Hide file tree
Showing 21 changed files with 351 additions and 24 deletions.
8 changes: 8 additions & 0 deletions samples/ControlGallery/Views/SetValues/RadioButtonPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@

<Button Text="Change selection" OnClick="ChangeSelection" />

<Label>RadioButtons with custom ControlTemplate. Selected value: @selectedStringValue</Label>
<RadioButtonGroup @bind-SelectedValue="selectedStringValue">
<RadioButtonTemplated Value=@("String 1") />
<RadioButtonTemplated Value=@("String 2") />
<RadioButtonTemplated Value=@("String 3") />
</RadioButtonGroup>

</VerticalStackLayout>
</ScrollView>
</ContentPage>

@code {
int? selectedIntValue;
ValueEnum selectedEnumValue = ValueEnum.B;
string selectedStringValue;

void ChangeSelection()
{
Expand Down
11 changes: 11 additions & 0 deletions samples/ControlGallery/Views/SetValues/RadioButtonTemplated.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<RadioButton Value="Value" @bind-IsChecked="isChecked">
<ControlTemplate>
<Label TextColor="@(isChecked ? Colors.Green : Colors.Red)" Text="@Value" />
</ControlTemplate>
</RadioButton>

@code {
[Parameter] public string Value { get; set; }

bool isChecked;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

using BlazorBindings.Core;
using BlazorBindings.Maui.Elements;
using BlazorBindings.Maui.Elements.Handlers;
using MC = Microsoft.Maui.Controls;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
Expand All @@ -21,6 +23,15 @@ public partial class CalendarView : BlazorBindings.Maui.Elements.ContentView
{
static CalendarView()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<CalendarView>(nameof(DayNamesTemplate),
(renderer, parent, component) => new ControlTemplatePropertyHandler<XMV.CalendarView>(component,
(x, controlTemplate) => x.DayNamesTemplate = controlTemplate));
ElementHandlerRegistry.RegisterPropertyContentHandler<CalendarView>(nameof(DaysViewTemplate),
(renderer, parent, component) => new ControlTemplatePropertyHandler<XMV.CalendarView>(component,
(x, controlTemplate) => x.DaysViewTemplate = controlTemplate));
ElementHandlerRegistry.RegisterPropertyContentHandler<CalendarView>(nameof(NavigationTemplate),
(renderer, parent, component) => new ControlTemplatePropertyHandler<XMV.CalendarView>(component,
(x, controlTemplate) => x.NavigationTemplate = controlTemplate));
RegisterAdditionalHandlers();
}

Expand All @@ -31,6 +42,9 @@ static CalendarView()
[Parameter] public IList<DayOfWeek> DaysOfWeek { get; set; }
[Parameter] public double? DaysViewHeightRequest { get; set; }
[Parameter] public DateTime? NavigatedDate { get; set; }
[Parameter] public RenderFragment DayNamesTemplate { get; set; }
[Parameter] public RenderFragment DaysViewTemplate { get; set; }
[Parameter] public RenderFragment NavigationTemplate { get; set; }

public new XMV.CalendarView NativeControl => (XMV.CalendarView)((Element)this).NativeControl;

Expand Down Expand Up @@ -89,13 +103,30 @@ protected override void HandleParameter(string name, object value)
NativeControl.NavigatedDate = NavigatedDate ?? (DateTime)XMV.CalendarView.NavigatedDateProperty.DefaultValue;
}
break;
case nameof(DayNamesTemplate):
DayNamesTemplate = (RenderFragment)value;
break;
case nameof(DaysViewTemplate):
DaysViewTemplate = (RenderFragment)value;
break;
case nameof(NavigationTemplate):
NavigationTemplate = (RenderFragment)value;
break;

default:
base.HandleParameter(name, value);
break;
}
}

protected override void RenderAdditionalElementContent(RenderTreeBuilder builder, ref int sequence)
{
base.RenderAdditionalElementContent(builder, ref sequence);
RenderTreeBuilderHelper.AddControlTemplateProperty(builder, sequence++, typeof(CalendarView), DayNamesTemplate);
RenderTreeBuilderHelper.AddControlTemplateProperty(builder, sequence++, typeof(CalendarView), DaysViewTemplate);
RenderTreeBuilderHelper.AddControlTemplateProperty(builder, sequence++, typeof(CalendarView), NavigationTemplate);;
}

static partial void RegisterAdditionalHandlers();
}
}
3 changes: 2 additions & 1 deletion samples/ThirdPartyControlsSample/Properties/Elements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
[assembly: GenerateComponent(typeof(CommunityToolkit.Maui.Views.DrawingView))]

// XCalendar
[assembly: GenerateComponent(typeof(XCalendar.Maui.Views.CalendarView))]
[assembly: GenerateComponent(typeof(XCalendar.Maui.Views.CalendarView),
Exclude = new[] { nameof(XCalendar.Maui.Views.CalendarView.DayNameTemplate), nameof(XCalendar.Maui.Views.CalendarView.DayTemplate) })]
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ public partial class GeneratedPropertyInfo
private static readonly string[] ContentTypes = new[]
{
"Microsoft.Maui.IView",
"Microsoft.Maui.Controls.BaseMenuItem"
"Microsoft.Maui.Controls.BaseMenuItem",
"Microsoft.Maui.Controls.ControlTemplate",
"Microsoft.Maui.Controls.DataTemplate",
};

public bool IsRenderFragmentProperty => Kind == GeneratedPropertyKind.RenderFragment;
public bool IsControlTemplate => _propertyInfo.Type.ToDisplayString() == "Microsoft.Maui.Controls.ControlTemplate";
public bool IsDataTemplate => _propertyInfo.Type.ToDisplayString() == "Microsoft.Maui.Controls.DataTemplate";

public string GetHandleContentProperty()
{
Expand All @@ -27,20 +31,32 @@ public string GetHandleContentProperty()
public string GetContentHandlerRegistration()
{
// ElementHandlerRegistry.RegisterPropertyContentHandler<ContentPage>(nameof(ChildContent),
// _ => new ContentPropertyHandler<MC.ContentPage>((page, value) => page.Content = (MC.View)value));
// (renderer, parent, component) => new ContentPropertyHandler<MC.ContentPage>((page, value) => page.Content = (MC.View)value));

var contentHandler = GetContentHandler();

return @$"
ElementHandlerRegistry.RegisterPropertyContentHandler<{ComponentName}>(nameof({ComponentPropertyName}),
_ => {contentHandler});";
(renderer, parent, component) => {contentHandler});";
}

private string GetContentHandler()
{
var type = (INamedTypeSymbol)_propertyInfo.Type;

if (type.IsGenericType && type.ConstructedFrom.SpecialType == SpecialType.System_Collections_Generic_IList_T)
if (IsControlTemplate)
{
// new ControlTemplatePropertyHandler<MC.TemplatedView>(component, (view, controlTemplate) => view.ControlTemplate = controlTemplate)
var controlTemplateHandlerName = GetTypeNameAndAddNamespace("BlazorBindings.Maui.Elements.Handlers", "ControlTemplatePropertyHandler");
return $"new {controlTemplateHandlerName}<{MauiContainingTypeName}>(component,\r\n (x, controlTemplate) => x.{_propertyInfo.Name} = controlTemplate)";
}
else if (IsDataTemplate)
{
// new DataTemplatePropertyHandler<MC.ItemsView>(component, (view, valueElement) => view.dataTemplate = dataTemplate)
var dataTemplateHandlerName = GetTypeNameAndAddNamespace("BlazorBindings.Maui.Elements.Handlers", "DataTemplatePropertyHandler");
return $"new {dataTemplateHandlerName}<{MauiContainingTypeName}>(component,\r\n (x, dataTemplate) => x.{_propertyInfo.Name} = dataTemplate)";
}
else if (type.IsGenericType && type.ConstructedFrom.SpecialType == SpecialType.System_Collections_Generic_IList_T)
{
// new ListContentPropertyHandler<MC.Page, MC.ToolbarItem>(page => page.ToolbarItems)
var itemTypeName = GetTypeNameAndAddNamespace(type.TypeArguments[0]);
Expand All @@ -58,8 +74,21 @@ private string GetContentHandler()

public string RenderContentProperty()
{
// RenderTreeBuilderHelper.AddContentProperty(builder, sequence++, typeof(ContentPage), ChildContent);
return $"\r\n RenderTreeBuilderHelper.AddContentProperty(builder, sequence++, typeof({ComponentName}), {ComponentPropertyName});";
if (IsControlTemplate)
{
// RenderTreeBuilderHelper.AddControlTemplateProperty(builder, sequence++, typeof(TemplatedView), ControlTemplate);
return $"\r\n RenderTreeBuilderHelper.AddControlTemplateProperty(builder, sequence++, typeof({ComponentName}), {ComponentPropertyName});";
}
else if (IsDataTemplate)
{
// RenderTreeBuilderHelper.AddDataTemplateProperty(builder, sequence++, typeof(ItemsView<T>), ItemTemplate);
return $"\r\n RenderTreeBuilderHelper.AddDataTemplateProperty(builder, sequence++, typeof({ComponentName}), {ComponentPropertyName});";
}
else
{
// RenderTreeBuilderHelper.AddContentProperty(builder, sequence++, typeof(ContentPage), ChildContent);
return $"\r\n RenderTreeBuilderHelper.AddContentProperty(builder, sequence++, typeof({ComponentName}), {ComponentPropertyName});";
}
}

internal static GeneratedPropertyInfo[] GetContentProperties(Compilation compilation, GeneratedComponentInfo componentInfo, IList<UsingStatement> usings)
Expand Down
2 changes: 1 addition & 1 deletion src/BlazorBindings.Maui/Elements/Border.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public partial class Border : View
static Border()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<Border>(nameof(ChildContent),
_ => new ContentPropertyHandler<MC.Border>((x, value) => x.Content = (MC.View)value));
(renderer, parent, component) => new ContentPropertyHandler<MC.Border>((x, value) => x.Content = (MC.View)value));
RegisterAdditionalHandlers();
}

Expand Down
2 changes: 1 addition & 1 deletion src/BlazorBindings.Maui/Elements/ContentPage.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public partial class ContentPage : TemplatedPage
static ContentPage()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<ContentPage>(nameof(ChildContent),
_ => new ContentPropertyHandler<MC.ContentPage>((x, value) => x.Content = (MC.View)value));
(renderer, parent, component) => new ContentPropertyHandler<MC.ContentPage>((x, value) => x.Content = (MC.View)value));
RegisterAdditionalHandlers();
}

Expand Down
2 changes: 1 addition & 1 deletion src/BlazorBindings.Maui/Elements/ContentView.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public partial class ContentView : TemplatedView
static ContentView()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<ContentView>(nameof(ChildContent),
_ => new ContentPropertyHandler<MC.ContentView>((x, value) => x.Content = (MC.View)value));
(renderer, parent, component) => new ContentPropertyHandler<MC.ContentView>((x, value) => x.Content = (MC.View)value));
RegisterAdditionalHandlers();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System.Collections.Generic;
using MC = Microsoft.Maui.Controls;

namespace BlazorBindings.Maui.Elements.DataTemplates
{
#pragma warning disable CA1812 // Avoid uninstantiated internal classes. Class is used as generic parameter.
internal class ControlTemplateItemsComponent : ComponentBase
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, ElementName);

foreach (var itemRoot in _itemRoots)
{
builder.OpenComponent<InitializedVerticalStackLayout>(1);

builder.AddAttribute(2, nameof(InitializedVerticalStackLayout.NativeControl), itemRoot);
builder.AddAttribute(3, "ChildContent", (RenderFragment)(builder =>
{
Template.Invoke(builder);
}));

builder.CloseComponent();
}

builder.CloseElement();
}

// ElementName is parametrized so that component would be handled by appropriate handler.
[Parameter] public string ElementName { get; set; }
[Parameter] public RenderFragment Template { get; set; }

private readonly List<MC.VerticalStackLayout> _itemRoots = new();

public MC.View AddTemplateRoot()
{
var templateRoot = new MC.VerticalStackLayout();
_itemRoots.Add(templateRoot);
StateHasChanged();

return templateRoot;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Maui.Controls;

namespace BlazorBindings.Maui.Elements.DataTemplates
{
internal class MbbControlTemplate : ControlTemplate
{
public MbbControlTemplate(ControlTemplateItemsComponent controlTemplateItemsContainer)
: base(controlTemplateItemsContainer.AddTemplateRoot)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@ public MbbDataTemplate(DataTemplateItemsComponent<T> dataTemplateItemsContainer)
{
}
}

internal class MbbDataTemplate : MC.DataTemplate
{
// There's not much of a difference between non-generic DataTemplate and ControlTemplate.
public MbbDataTemplate(ControlTemplateItemsComponent dataTemplateItemsContainer)
: base(dataTemplateItemsContainer.AddTemplateRoot)
{
}
}
}
4 changes: 2 additions & 2 deletions src/BlazorBindings.Maui/Elements/FlyoutPage.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public partial class FlyoutPage : Page
static FlyoutPage()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<FlyoutPage>(nameof(Detail),
_ => new ContentPropertyHandler<MC.FlyoutPage>((x, value) => x.Detail = (MC.Page)value));
(renderer, parent, component) => new ContentPropertyHandler<MC.FlyoutPage>((x, value) => x.Detail = (MC.Page)value));
ElementHandlerRegistry.RegisterPropertyContentHandler<FlyoutPage>(nameof(Flyout),
_ => new ContentPropertyHandler<MC.FlyoutPage>((x, value) => x.Flyout = (MC.Page)value));
(renderer, parent, component) => new ContentPropertyHandler<MC.FlyoutPage>((x, value) => x.Flyout = (MC.Page)value));
RegisterAdditionalHandlers();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using BlazorBindings.Core;
using BlazorBindings.Maui.Elements.DataTemplates;
using Microsoft.AspNetCore.Components;
using Microsoft.Maui.Controls;
using System;
using MC = Microsoft.Maui.Controls;

namespace BlazorBindings.Maui.Elements.Handlers
{
public class ControlTemplatePropertyHandler<TElementType> : IMauiContainerElementHandler, INonChildContainerElement
{
private readonly ControlTemplateItemsComponent _controlTemplateItemsComponent;
private readonly Action<TElementType, ControlTemplate> _setPropertyAction;

public ControlTemplatePropertyHandler(IComponent controlTemplateItemsComponent, Action<TElementType, ControlTemplate> setPropertyAction)
{
_controlTemplateItemsComponent = (ControlTemplateItemsComponent)controlTemplateItemsComponent;
_setPropertyAction = setPropertyAction;
}

public void SetParent(object parentElement)
{
var parent = (TElementType)parentElement;
var controlTemplate = new MbbControlTemplate(_controlTemplateItemsComponent);
_setPropertyAction(parent, controlTemplate);
}

public void Remove()
{
// Because this Handler is used internally only, this method is no-op.
}

// Because this is a 'fake' element, all matters related to physical trees
// should be no-ops.

void IMauiContainerElementHandler.AddChild(MC.Element child, int physicalSiblingIndex) { }

void IMauiContainerElementHandler.RemoveChild(MC.Element child) { }

int IMauiContainerElementHandler.GetChildIndex(MC.Element child) => -1;

object IElementHandler.TargetElement => null;
void IElementHandler.ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName) { }

MC.Element IMauiElementHandler.ElementControl => null;
bool IMauiElementHandler.IsParented() => false;

void IMauiElementHandler.SetParent(MC.Element parent)
{
// This should never get called. Instead, INonChildContainerElement.SetParent() implemented
// in this class should get called.
throw new NotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@

namespace BlazorBindings.Maui.Elements.Handlers
{
public class DataTemplatePropertyHandler<TElementType, TItemType> : IMauiContainerElementHandler, INonChildContainerElement
public class DataTemplatePropertyHandler<TElementType> : IMauiContainerElementHandler, INonChildContainerElement
{
private readonly DataTemplateItemsComponent<TItemType> _dataTemplateItemsComponent;
private readonly ControlTemplateItemsComponent _controlTemplateItemsComponent;
private readonly Action<TElementType, DataTemplate> _setPropertyAction;

public DataTemplatePropertyHandler(IComponent dataTemplateItemsComponent, Action<TElementType, DataTemplate> setPropertyAction)
public DataTemplatePropertyHandler(IComponent controlTemplateItemsComponent, Action<TElementType, DataTemplate> setPropertyAction)
{
_dataTemplateItemsComponent = (DataTemplateItemsComponent<TItemType>)dataTemplateItemsComponent;
_controlTemplateItemsComponent = (ControlTemplateItemsComponent)controlTemplateItemsComponent;
_setPropertyAction = setPropertyAction;
}

public void SetParent(object parentElement)
{
var parent = (TElementType)parentElement;
var dataTemplate = new MbbDataTemplate<TItemType>(_dataTemplateItemsComponent);
var dataTemplate = new MbbDataTemplate(_controlTemplateItemsComponent);
_setPropertyAction(parent, dataTemplate);
}

Expand Down
Loading

0 comments on commit 60c59c6

Please sign in to comment.