Change how root elements are created and how app content is parented

- Renderers no longer create the root control. All they do it *add* a given component to a given parent. The platform-specific code is in charge of creating the root component.
- This now avoids creating unnecessary wrappers (e.g. BlazorContentViewWrapper) that muddy the native control hierarchy
This commit is contained in:
Eilon Lipton 2019-09-16 16:01:15 -07:00
Родитель fe3413a55f
Коммит 2c6cd47653
13 изменённых файлов: 105 добавлений и 82 удалений

Просмотреть файл

@ -23,13 +23,6 @@ namespace Blaxamarin.Framework
//MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
protected override IFormsControlHandler CreateRootControl()
{
var rootContent = new BlazorContentViewWrapper();
ContentPage.Content = (View)rootContent.Element;
return rootContent;
}
protected override NativeControlManager<IFormsControlHandler> CreateNativeControlManager()
{
return new BlaxamarinNativeControlManager();

Просмотреть файл

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Blaxamarin.Framework.Elements;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
@ -8,16 +9,40 @@ namespace Blaxamarin.Framework
{
public static class BlaxamarinServiceProviderExtensions
{
public static ContentPage GetComponentContentPage<TComponent>(this IServiceProvider services) where TComponent : IComponent
/// <summary>
/// Creates a component of type <typeparamref name="TComponent"/> and adds it as a child of <paramref name="parent"/>.
/// </summary>
/// <typeparam name="TComponent"></typeparam>
/// <param name="services"></param>
/// <param name="parent"></param>
public static void AddComponent<TComponent>(this IServiceProvider services, Element parent) where TComponent : IComponent
{
var renderer = new BlaxamarinRenderer(services, services.GetRequiredService<ILoggerFactory>());
var result = renderer.Dispatcher.InvokeAsync(async () =>
if (parent is null)
{
await renderer.AddComponent<TComponent>();
throw new ArgumentNullException(nameof(parent));
}
return renderer.ContentPage;
var renderer = new BlaxamarinRenderer(services, services.GetRequiredService<ILoggerFactory>());
renderer.Dispatcher.InvokeAsync(async () =>
{
await renderer.AddComponent<TComponent>(new ElementWrapper(parent));
});
return result.GetAwaiter().GetResult();
}
private sealed class ElementWrapper : IFormsControlHandler
{
public ElementWrapper(Element element)
{
Element = element ?? throw new ArgumentNullException(nameof(element));
}
public Element Element { get; }
public object NativeControl => Element;
public void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
{
FormsComponentBase.ApplyAttribute(Element, attributeEventHandlerId, attributeName, attributeValue, attributeEventUpdatesAttributeName);
}
}
}
}

Просмотреть файл

@ -1,17 +0,0 @@
using Xamarin.Forms;
namespace Blaxamarin.Framework
{
internal class BlazorContentViewWrapper : ContentView, IFormsControlHandler
{
public object NativeControl => this;
public Element Element => this;
// TODO: Need to think about whether this method is needed. There's no component for this element, so when
// would this get called?
public void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
{
Elements.FormsComponentBase.ApplyAttribute(this, attributeEventHandlerId, attributeName, attributeValue, attributeEventUpdatesAttributeName);
}
}
}

Просмотреть файл

@ -23,7 +23,7 @@ namespace BlaxamarinSample
})
.Build();
MainPage = host.Services.GetComponentContentPage<TodoApp>();
host.Services.AddComponent<TodoApp>(parent: this);
}
protected override void OnStart()

Просмотреть файл

@ -1,4 +1,5 @@
using Microsoft.Extensions.Hosting;
using BlinForms.Framework.Controls;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@ -12,21 +13,21 @@ namespace BlinForms.Framework
/// <summary>
/// An implementation of <see cref="IHostedService"/> that controls the lifetime of a BlinForms application.
/// When this service starts, it loads the main form registered by
/// <see cref="BlinFormsServiceCollectionExtensions.AddBlinFormsMainForm{TComponent}(Microsoft.Extensions.DependencyInjection.IServiceCollection)"/>.
/// <see cref="BlinFormsServiceCollectionExtensions.AddRootFormContent{TComponent}(Microsoft.Extensions.DependencyInjection.IServiceCollection)"/>.
/// The service will request that the application stops when the main form is closed.
/// This service will invoke all instances of <see cref="IBlinFormsStartup"/> that are registered in the
/// container. The order of the startup instances is not guaranteed.
/// </summary>
public class BlinFormsHostedService : IHostedService
{
private readonly IBlinFormsMainFormType _blinFormsMainForm;
private readonly IBlinFormsRootFormContent _blinFormsMainForm;
private readonly ILoggerFactory _loggerFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IEnumerable<IBlinFormsStartup> _blinFormsStartups;
public BlinFormsHostedService(
IBlinFormsMainFormType blinFormsMainForm,
IBlinFormsRootFormContent blinFormsMainForm,
ILoggerFactory loggerFactory,
IServiceProvider serviceProvider,
IHostApplicationLifetime hostApplicationLifetime,
@ -50,11 +51,13 @@ namespace BlinForms.Framework
Application.SetCompatibleTextRenderingDefault(false);
var renderer = new BlinFormsRenderer(_serviceProvider, _loggerFactory);
await renderer.Dispatcher.InvokeAsync(() =>
await renderer.Dispatcher.InvokeAsync(async () =>
{
renderer.AddComponent(_blinFormsMainForm.MainFormType);
var rootForm = renderer.RootForm;
var rootForm = new RootForm();
rootForm.FormClosed += OnRootFormFormClosed;
await renderer.AddComponent(_blinFormsMainForm.RootFormContentType, new ControlWrapper(rootForm));
Application.Run(rootForm);
});
}
@ -69,5 +72,22 @@ namespace BlinForms.Framework
{
return Task.CompletedTask;
}
private sealed class ControlWrapper : IWindowsFormsControlHandler
{
public ControlWrapper(Control control)
{
Control = control ?? throw new ArgumentNullException(nameof(control));
}
public Control Control { get; }
public object NativeControl => Control;
public void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
{
FormsComponentBase.ApplyAttribute(Control, attributeEventHandlerId, attributeName, attributeValue, attributeEventUpdatesAttributeName);
}
}
}
}

Просмотреть файл

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Components;
using System;
namespace BlinForms.Framework
{
public class BlinFormsMainFormType<TComponent> : IBlinFormsMainFormType
where TComponent : IComponent
{
public BlinFormsMainFormType()
{
MainFormType = typeof(TComponent);
}
public Type MainFormType { get; }
}
}

Просмотреть файл

@ -12,18 +12,11 @@ namespace BlinForms.Framework
{
}
public RootForm RootForm { get; } = new RootForm();
protected override void HandleException(Exception exception)
{
MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
protected override IWindowsFormsControlHandler CreateRootControl()
{
return RootForm;
}
protected override NativeControlManager<IWindowsFormsControlHandler> CreateNativeControlManager()
{
return new BlinFormsNativeControlManager();

Просмотреть файл

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
using System;
namespace BlinForms.Framework
{
public class BlinFormsRootFormContent<TComponent> : IBlinFormsRootFormContent
where TComponent : IComponent
{
public BlinFormsRootFormContent()
{
RootFormContentType = typeof(TComponent);
}
public Type RootFormContentType { get; }
}
}

Просмотреть файл

@ -13,7 +13,7 @@ namespace BlinForms.Framework
/// <typeparam name="TComponent"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddBlinFormsMainForm<TComponent>(this IServiceCollection services)
public static IServiceCollection AddRootFormContent<TComponent>(this IServiceCollection services)
where TComponent : class, IComponent
{
if (services is null)
@ -21,7 +21,7 @@ namespace BlinForms.Framework
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton<IBlinFormsMainFormType, BlinFormsMainFormType<TComponent>>();
services.AddSingleton<IBlinFormsRootFormContent, BlinFormsRootFormContent<TComponent>>();
return services;
}

Просмотреть файл

@ -1,9 +0,0 @@
using System;
namespace BlinForms.Framework
{
public interface IBlinFormsMainFormType
{
Type MainFormType { get; }
}
}

Просмотреть файл

@ -0,0 +1,9 @@
using System;
namespace BlinForms.Framework
{
public interface IBlinFormsRootFormContent
{
Type RootFormContentType { get; }
}
}

Просмотреть файл

@ -19,8 +19,8 @@ namespace BlinFormsSample
services.AddSingleton<AppState>();
services.AddSingleton<IBlinFormsStartup, TodoAppStartup>();
// Configure main form to load at startup
services.AddBlinFormsMainForm<TodoApp>();
// Register root form content
services.AddRootFormContent<TodoApp>();
})
.Build()
.RunAsync();

Просмотреть файл

@ -27,18 +27,29 @@ namespace Emblazon
public override Dispatcher Dispatcher { get; }
= Dispatcher.CreateDefault();
public async Task AddComponent<TComponent>() where TComponent : IComponent
/// <summary>
/// Creates a component of type <typeparamref name="TComponent"/> and adds it as a child of <paramref name="parent"/>.
/// </summary>
/// <typeparam name="TComponent"></typeparam>
/// <param name="parent"></param>
/// <returns></returns>
public async Task AddComponent<TComponent>(TComponentHandler parent) where TComponent : IComponent
{
await AddComponent(typeof(TComponent));
await AddComponent(typeof(TComponent), parent);
}
public async Task AddComponent(Type componentType)
/// <summary>
/// Creates a component of type <paramref name="componentType"/> and adds it as a child of <paramref name="parent"/>.
/// </summary>
/// <param name="componentType"></param>
/// <param name="parent"></param>
/// <returns></returns>
public async Task AddComponent(Type componentType, TComponentHandler parent)
{
var component = InstantiateComponent(componentType);
var componentId = AssignRootComponentId(component);
var rootControl = CreateRootControl();
var rootAdapter = new EmblazonAdapter<TComponentHandler>(this, closestPhysicalParent: rootControl, knownTargetControl: rootControl)
var rootAdapter = new EmblazonAdapter<TComponentHandler>(this, closestPhysicalParent: parent, knownTargetControl: parent)
{
Name = "RootAdapter"
};
@ -106,7 +117,5 @@ namespace Emblazon
_componentIdToAdapter[componentId] = result;
return result;
}
protected abstract TComponentHandler CreateRootControl();
}
}