Factor .NET Standard components of BlinForms into new Emblazon library
- Emblazon library is .NET Standard 2.0 - BlinForms remains .NET Core App 3.0 (required for WinForms) - Made things reasonably generic in Emblazon to support future native renderers (e.g. Blaxamarin)
This commit is contained in:
Родитель
84bdc4f5ca
Коммит
bbb4bb1f5c
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0-preview8.19372.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0-preview7.19362.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0-preview7.19362.4" />
|
||||
<ProjectReference Include="..\Emblazon\Emblazon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace BlinForms.Framework
|
|||
{
|
||||
public static class BlinForms
|
||||
{
|
||||
public static void Run<T>() where T: IComponent
|
||||
public static void Run<T>() where T : IComponent
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
|
|
@ -1,46 +1,29 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Emblazon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
{
|
||||
public class BlinFormsRenderer : Renderer
|
||||
public class BlinFormsRenderer : EmblazonRenderer<Control>
|
||||
{
|
||||
private readonly Dictionary<int, BlontrolAdapter> _componentIdToAdapter = new Dictionary<int, BlontrolAdapter>();
|
||||
|
||||
public BlinFormsRenderer(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider, new LoggerFactory())
|
||||
: base(serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public Form RootForm { get; private set; } = new RootForm();
|
||||
|
||||
public override Dispatcher Dispatcher { get; }
|
||||
= Dispatcher.CreateDefault();
|
||||
|
||||
public Task AddComponent<T>() where T : IComponent
|
||||
protected override void InitializeRootAdapter(EmblazonAdapter<Control> adapter)
|
||||
{
|
||||
var component = InstantiateComponent(typeof(T));
|
||||
var componentId = AssignRootComponentId(component);
|
||||
var adapter = _componentIdToAdapter[componentId] =
|
||||
new BlontrolAdapter(this)
|
||||
{
|
||||
Name = "Root BlontrolAdapter",
|
||||
// TODO: Might actually want to keep this dummy control so that Blinforms can be an island in a form. But, need
|
||||
// to figure out its default size etc. Perhaps top-level Razor class implements ITopLevel{FormSettings} interface
|
||||
// to control 'container Form' options?
|
||||
TargetControl = new Control()
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
},
|
||||
};
|
||||
// TODO: Might actually want to keep this dummy control so that Blinforms can be an island in a form. But, need
|
||||
// to figure out its default size etc. Perhaps top-level Razor class implements ITopLevel{FormSettings} interface
|
||||
// to control 'container Form' options?
|
||||
adapter.TargetControl = new Control()
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
};
|
||||
|
||||
RootForm.Controls.Add(adapter.TargetControl);
|
||||
return RenderRootComponentAsync(componentId);
|
||||
}
|
||||
|
||||
protected override void HandleException(Exception exception)
|
||||
|
@ -48,22 +31,9 @@ namespace BlinForms.Framework
|
|||
MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
protected override EmblazonAdapter<Control> CreateAdapter()
|
||||
{
|
||||
foreach (var updatedComponent in renderBatch.UpdatedComponents.Array.Take(renderBatch.UpdatedComponents.Count))
|
||||
{
|
||||
var adapter = _componentIdToAdapter[updatedComponent.ComponentId];
|
||||
adapter.ApplyEdits(updatedComponent.ComponentId, updatedComponent.Edits, renderBatch.ReferenceFrames, renderBatch);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal BlontrolAdapter CreateAdapterForChildComponent(int componentId)
|
||||
{
|
||||
var result = new BlontrolAdapter(this);
|
||||
_componentIdToAdapter[componentId] = result;
|
||||
return result;
|
||||
return new BlontrolAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,227 +1,34 @@
|
|||
using BlinForms.Framework.Controls;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
using Emblazon;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a "shadow" item that Blazor uses to map changes into the live WinForms control tree.
|
||||
/// </summary>
|
||||
public class BlontrolAdapter
|
||||
public class BlontrolAdapter : EmblazonAdapter<Control>
|
||||
{
|
||||
internal static Dictionary<string, IComponentControlFactory> KnownElements { get; }
|
||||
= new Dictionary<string, IComponentControlFactory>();
|
||||
|
||||
public BlontrolAdapter(BlinFormsRenderer renderer)
|
||||
public BlontrolAdapter()
|
||||
{
|
||||
Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
public BlontrolAdapter Parent { get; set; }
|
||||
public List<BlontrolAdapter> Children { get; } = new List<BlontrolAdapter>();
|
||||
|
||||
// TODO: Is this the right concept? Can a component have multiple WinForms controls created?
|
||||
public Control TargetControl { get; set; }
|
||||
|
||||
public BlinFormsRenderer Renderer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging purposes.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
protected override void RemoveChildControl(EmblazonAdapter<Control> child)
|
||||
{
|
||||
return $"Bladapter: Name={Name ?? "<?>"}, Target={TargetControl?.GetType().Name ?? "<?>"}, #Children={Children.Count}";
|
||||
TargetControl.Controls.Remove(child.TargetControl);
|
||||
}
|
||||
|
||||
internal void ApplyEdits(int componentId, ArrayBuilderSegment<RenderTreeEdit> edits, ArrayRange<RenderTreeFrame> referenceFrames, RenderBatch batch)
|
||||
protected override EmblazonAdapter<Control> CreateAdapter()
|
||||
{
|
||||
foreach (var edit in edits)
|
||||
{
|
||||
switch (edit.Type)
|
||||
{
|
||||
case RenderTreeEditType.PrependFrame:
|
||||
ApplyPrependFrame(batch, componentId, edit.SiblingIndex, referenceFrames.Array, edit.ReferenceFrameIndex);
|
||||
break;
|
||||
case RenderTreeEditType.SetAttribute:
|
||||
ApplySetAttribute(ref referenceFrames.Array[edit.ReferenceFrameIndex]);
|
||||
break;
|
||||
case RenderTreeEditType.RemoveFrame:
|
||||
ApplyRemoveFrame(edit.SiblingIndex);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Not supported edit type: {edit.Type}");
|
||||
}
|
||||
}
|
||||
return new BlontrolAdapter();
|
||||
}
|
||||
|
||||
private void ApplyRemoveFrame(int siblingIndex)
|
||||
protected override bool IsChildControlParented(Control nativeChild)
|
||||
{
|
||||
var childToRemove = Children[siblingIndex];
|
||||
|
||||
// If there's a target control for the child adapter, remove it from the live control tree.
|
||||
// Not all adapters have target controls; Adapters for markup/text have no associated native control.
|
||||
if (childToRemove.TargetControl != null)
|
||||
{
|
||||
TargetControl.Controls.Remove(childToRemove.TargetControl);
|
||||
}
|
||||
Children.RemoveAt(siblingIndex);
|
||||
return nativeChild.Parent != null;
|
||||
}
|
||||
|
||||
private void ApplySetAttribute(ref RenderTreeFrame attributeFrame)
|
||||
{
|
||||
var mapper = GetControlPropertyMapper(TargetControl);
|
||||
mapper.SetControlProperty(attributeFrame.AttributeEventHandlerId, attributeFrame.AttributeName, attributeFrame.AttributeValue, attributeFrame.AttributeEventUpdatesAttributeName);
|
||||
}
|
||||
|
||||
private int ApplyPrependFrame(RenderBatch batch, int componentId, int siblingIndex, RenderTreeFrame[] frames, int frameIndex)
|
||||
{
|
||||
ref var frame = ref frames[frameIndex];
|
||||
switch (frame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Element:
|
||||
{
|
||||
InsertElement(siblingIndex, frames, frameIndex, componentId, batch);
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Component:
|
||||
{
|
||||
// Components are represented by BlontrolAdapters
|
||||
var childAdapter = Renderer.CreateAdapterForChildComponent(frame.ComponentId);
|
||||
childAdapter.Name = $"For: '{frame.Component.GetType().FullName}'";
|
||||
AddChildAdapter(siblingIndex, childAdapter);
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Region:
|
||||
{
|
||||
return InsertFrameRange(batch, componentId, siblingIndex, frames, frameIndex + 1, frameIndex + frame.RegionSubtreeLength);
|
||||
}
|
||||
case RenderTreeFrameType.Markup:
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(frame.MarkupContent))
|
||||
{
|
||||
throw new NotImplementedException("Nonempty markup: " + frame.MarkupContent);
|
||||
}
|
||||
AddChildAdapter(siblingIndex, new BlontrolAdapter(Renderer) { Name = $"Dummy markup, sib#={siblingIndex}" });
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Text:
|
||||
{
|
||||
// TODO: Maybe support this for Labels for Text property, etc. ("DefaultProperty"?)
|
||||
if (!string.IsNullOrWhiteSpace(frame.TextContent))
|
||||
{
|
||||
throw new NotImplementedException("Nonempty text: " + frame.TextContent);
|
||||
}
|
||||
AddChildAdapter(siblingIndex, new BlontrolAdapter(Renderer) { Name = $"Dummy text, sib#={siblingIndex}" });
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Not supported frame type: {frame.FrameType}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertElement(int siblingIndex, RenderTreeFrame[] frames, int frameIndex, int componentId, RenderBatch batch)
|
||||
{
|
||||
// Elements represent Winforms native controls
|
||||
ref var frame = ref frames[frameIndex];
|
||||
var elementName = frame.ElementName;
|
||||
var controlFactory = KnownElements[elementName];
|
||||
var nativeControl = controlFactory.CreateControl(new ComponentControlFactoryContext(Renderer, Parent?.TargetControl));
|
||||
|
||||
TargetControl = nativeControl;
|
||||
|
||||
// TODO: Need a more reliable way to know whether the target control is already created, e.g. a return value
|
||||
// from ControlFactory.CreateControl(). Right now the check assumes that if the target control is already parented,
|
||||
// there is no need to parent it. Not an awful assumption, but looks odd.
|
||||
if (TargetControl.Parent == null)
|
||||
{
|
||||
// Add the new native control to the parent's child controls (the parent adapter is our
|
||||
// container, so the parent adapter's control is our control's container.
|
||||
AddChildControl(Parent.TargetControl, siblingIndex, TargetControl);
|
||||
}
|
||||
|
||||
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||
for (var descendantIndex = frameIndex + 1; descendantIndex < endIndexExcl; descendantIndex++)
|
||||
{
|
||||
var candidateFrame = frames[descendantIndex];
|
||||
if (candidateFrame.FrameType == RenderTreeFrameType.Attribute)
|
||||
{
|
||||
// TODO: Do smarter property setting...? Not calling <NativeControl>.ApplyAttribute(...) right now. Should it?
|
||||
ApplySetAttribute(ref candidateFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
// As soon as we see a non-attribute child, all the subsequent child frames are
|
||||
// not attributes, so bail out and insert the remnants recursively
|
||||
InsertFrameRange(batch, componentId, childIndex: 0, frames, descendantIndex, endIndexExcl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Ignoring non-controls, such as Timer Component
|
||||
|
||||
//if (element is Control elementControl)
|
||||
//{
|
||||
// AddChildControl(siblingIndex, elementControl);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Debug.WriteLine("Ignoring non-control child: " + element.GetType().FullName);
|
||||
//}
|
||||
}
|
||||
|
||||
private int InsertFrameRange(RenderBatch batch, int componentId, int childIndex, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
{
|
||||
var origChildIndex = childIndex;
|
||||
for (var index = startIndex; index < endIndexExcl; index++)
|
||||
{
|
||||
ref var frame = ref batch.ReferenceFrames.Array[index];
|
||||
var numChildrenInserted = ApplyPrependFrame(batch, componentId, childIndex, frames, index);
|
||||
childIndex += numChildrenInserted;
|
||||
|
||||
// Skip over any descendants, since they are already dealt with recursively
|
||||
index += CountDescendantFrames(frame);
|
||||
}
|
||||
|
||||
return (childIndex - origChildIndex); // Total number of children inserted
|
||||
}
|
||||
|
||||
private int CountDescendantFrames(RenderTreeFrame frame)
|
||||
{
|
||||
return frame.FrameType switch
|
||||
{
|
||||
// The following frame types have a subtree length. Other frames may use that memory slot
|
||||
// to mean something else, so we must not read it. We should consider having nominal subtypes
|
||||
// of RenderTreeFramePointer that prevent access to non-applicable fields.
|
||||
RenderTreeFrameType.Component => frame.ComponentSubtreeLength - 1,
|
||||
RenderTreeFrameType.Element => frame.ElementSubtreeLength - 1,
|
||||
RenderTreeFrameType.Region => frame.RegionSubtreeLength - 1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
private void AddChildAdapter(int siblingIndex, BlontrolAdapter childAdapter)
|
||||
{
|
||||
childAdapter.Parent = this;
|
||||
|
||||
if (siblingIndex <= Children.Count)
|
||||
{
|
||||
Children.Insert(siblingIndex, childAdapter);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"WARNING: {nameof(AddChildAdapter)} called with {nameof(siblingIndex)}={siblingIndex}, but Children.Count={Children.Count}");
|
||||
Children.Add(childAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddChildControl(Control parentControl, int siblingIndex, Control childControl)
|
||||
protected override void AddChildControl(Control parentControl, int siblingIndex, Control childControl)
|
||||
{
|
||||
if (siblingIndex <= parentControl.Controls.Count)
|
||||
{
|
||||
|
@ -236,18 +43,5 @@ namespace BlinForms.Framework
|
|||
parentControl.Controls.Add(childControl);
|
||||
}
|
||||
}
|
||||
|
||||
private static IControlPropertyMapper GetControlPropertyMapper(Control control)
|
||||
{
|
||||
// TODO: Have control-specific ones, but also need a general one for custom controls? Or maybe not needed?
|
||||
if (control is IBlazorNativeControl nativeControl)
|
||||
{
|
||||
return new NativeControlPropertyMapper(nativeControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ReflectionControlPropertyMapper(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
using System.Windows.Forms;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
{
|
||||
public class ComponentControlFactoryContext
|
||||
{
|
||||
public ComponentControlFactoryContext(BlinFormsRenderer renderer, Control parentControl)
|
||||
{
|
||||
Renderer = renderer ?? throw new System.ArgumentNullException(nameof(renderer));
|
||||
ParentControl = parentControl;
|
||||
}
|
||||
|
||||
public Control ParentControl { get; }
|
||||
public BlinFormsRenderer Renderer { get; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
{
|
||||
public class ComponentControlFactoryFunc : IComponentControlFactory
|
||||
{
|
||||
private readonly Func<BlinFormsRenderer, Control, Control> _callback;
|
||||
|
||||
public ComponentControlFactoryFunc(Func<BlinFormsRenderer, Control, Control> callback)
|
||||
{
|
||||
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
public Control CreateControl(ComponentControlFactoryContext context)
|
||||
{
|
||||
return _callback(context.Renderer, context.ParentControl);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Emblazon;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace BlinForms.Framework.Controls
|
||||
|
@ -7,7 +8,7 @@ namespace BlinForms.Framework.Controls
|
|||
{
|
||||
static Button()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Button).FullName, new ComponentControlFactoryFunc((renderer, _) => new BlazorButton(renderer)));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Button).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((renderer, _) => new BlazorButton(renderer)));
|
||||
}
|
||||
|
||||
[Parameter] public string Text { get; set; }
|
||||
|
@ -24,7 +25,7 @@ namespace BlinForms.Framework.Controls
|
|||
|
||||
class BlazorButton : System.Windows.Forms.Button, IBlazorNativeControl
|
||||
{
|
||||
public BlazorButton(BlinFormsRenderer renderer)
|
||||
public BlazorButton(EmblazonRenderer<System.Windows.Forms.Control> renderer)
|
||||
{
|
||||
Click += (s, e) =>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Emblazon;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace BlinForms.Framework.Controls
|
||||
|
@ -7,7 +8,7 @@ namespace BlinForms.Framework.Controls
|
|||
{
|
||||
static Label()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Label).FullName, new ComponentControlFactoryFunc((_, __) => new BlazorLabel()));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Label).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((_, __) => new BlazorLabel()));
|
||||
}
|
||||
|
||||
[Parameter] public string Text { get; set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Emblazon;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace BlinForms.Framework.Controls
|
||||
|
@ -7,7 +8,7 @@ namespace BlinForms.Framework.Controls
|
|||
{
|
||||
static Panel()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Panel).FullName, new ComponentControlFactoryFunc((_, __) => new BlazorPanel()));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(Panel).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((_, __) => new BlazorPanel()));
|
||||
}
|
||||
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Emblazon;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
@ -8,7 +9,7 @@ namespace BlinForms.Framework.Controls
|
|||
{
|
||||
static SplitContainer()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitContainer).FullName, new ComponentControlFactoryFunc((_, __) => new BlazorSplitContainer()));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitContainer).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((_, __) => new BlazorSplitContainer()));
|
||||
}
|
||||
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
namespace BlinForms.Framework.Controls
|
||||
using Emblazon;
|
||||
|
||||
namespace BlinForms.Framework.Controls
|
||||
{
|
||||
public class SplitterPanel1 : SplitterPanelBase
|
||||
{
|
||||
static SplitterPanel1()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitterPanel1).FullName, new ComponentControlFactoryFunc((_, parentControl) => GetSplitterPanel(parentControl, panelNumber: 1)));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitterPanel1).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((_, parentControl) => GetSplitterPanel(parentControl, panelNumber: 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
namespace BlinForms.Framework.Controls
|
||||
using Emblazon;
|
||||
|
||||
namespace BlinForms.Framework.Controls
|
||||
{
|
||||
public class SplitterPanel2 : SplitterPanelBase
|
||||
{
|
||||
static SplitterPanel2()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitterPanel2).FullName, new ComponentControlFactoryFunc((_, parentControl) => GetSplitterPanel(parentControl, panelNumber: 2)));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(SplitterPanel2).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((_, parentControl) => GetSplitterPanel(parentControl, panelNumber: 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Emblazon;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
@ -8,7 +9,7 @@ namespace BlinForms.Framework.Controls
|
|||
{
|
||||
static TextBox()
|
||||
{
|
||||
BlontrolAdapter.KnownElements.Add(typeof(TextBox).FullName, new ComponentControlFactoryFunc((renderer, _) => new BlazorTextBox(renderer)));
|
||||
BlontrolAdapter.KnownElements.Add(typeof(TextBox).FullName, new ComponentControlFactoryFunc<System.Windows.Forms.Control>((renderer, _) => new BlazorTextBox(renderer)));
|
||||
}
|
||||
|
||||
[Parameter] public string Text { get; set; }
|
||||
|
@ -43,7 +44,7 @@ namespace BlinForms.Framework.Controls
|
|||
|
||||
class BlazorTextBox : System.Windows.Forms.TextBox, IBlazorNativeControl
|
||||
{
|
||||
public BlazorTextBox(BlinFormsRenderer renderer)
|
||||
public BlazorTextBox(EmblazonRenderer<System.Windows.Forms.Control> renderer)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
using System.Windows.Forms;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
{
|
||||
public interface IComponentControlFactory
|
||||
{
|
||||
Control CreateControl(ComponentControlFactoryContext context);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlaxamarinSample.Android",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlaxamarinSample.iOS", "BlaxamarinSample\BlaxamarinSample.iOS\BlaxamarinSample.iOS.csproj", "{E966A851-1BEB-4381-B275-6F0D532CF268}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlaxamarinSample", "BlaxamarinSample\BlaxamarinSample\BlaxamarinSample.csproj", "{732D30E2-4E59-47E6-AF8A-35E2C5746D0D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlaxamarinSample", "BlaxamarinSample\BlaxamarinSample\BlaxamarinSample.csproj", "{732D30E2-4E59-47E6-AF8A-35E2C5746D0D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emblazon", "Emblazon\Emblazon.csproj", "{77F00237-2214-4B58-BF83-1D5CA6274545}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -92,6 +94,18 @@ Global
|
|||
{732D30E2-4E59-47E6-AF8A-35E2C5746D0D}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{732D30E2-4E59-47E6-AF8A-35E2C5746D0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{732D30E2-4E59-47E6-AF8A-35E2C5746D0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{77F00237-2214-4B58-BF83-1D5CA6274545}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace Emblazon
|
||||
{
|
||||
public class ComponentControlFactoryContext<TNativeComponent> where TNativeComponent : class
|
||||
{
|
||||
public ComponentControlFactoryContext(EmblazonRenderer<TNativeComponent> renderer, TNativeComponent parentControl)
|
||||
{
|
||||
Renderer = renderer ?? throw new System.ArgumentNullException(nameof(renderer));
|
||||
ParentControl = parentControl;
|
||||
}
|
||||
|
||||
public TNativeComponent ParentControl { get; }
|
||||
|
||||
public EmblazonRenderer<TNativeComponent> Renderer { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Emblazon
|
||||
{
|
||||
public class ComponentControlFactoryFunc<TNativeComponent> : IComponentControlFactory<TNativeComponent> where TNativeComponent : class
|
||||
{
|
||||
private readonly Func<EmblazonRenderer<TNativeComponent>, TNativeComponent, TNativeComponent> _callback;
|
||||
|
||||
public ComponentControlFactoryFunc(Func<EmblazonRenderer<TNativeComponent>, TNativeComponent, TNativeComponent> callback)
|
||||
{
|
||||
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
public TNativeComponent CreateControl(ComponentControlFactoryContext<TNativeComponent> context)
|
||||
{
|
||||
return _callback(context.Renderer, context.ParentControl);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0-preview8.19372.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0-preview7.19362.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0-preview7.19362.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,256 @@
|
|||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Emblazon
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a "shadow" item that Blazor uses to map changes into the live WinForms control tree.
|
||||
/// </summary>
|
||||
public abstract class EmblazonAdapter<TNativeComponent> where TNativeComponent : class
|
||||
{
|
||||
// TODO: This used to be internal, but now it's public so that BlinForms components can register themselves. How should this work?
|
||||
public static Dictionary<string, IComponentControlFactory<TNativeComponent>> KnownElements { get; }
|
||||
= new Dictionary<string, IComponentControlFactory<TNativeComponent>>();
|
||||
|
||||
public EmblazonAdapter()
|
||||
{
|
||||
}
|
||||
|
||||
public EmblazonAdapter<TNativeComponent> Parent { get; set; }
|
||||
public List<EmblazonAdapter<TNativeComponent>> Children { get; } = new List<EmblazonAdapter<TNativeComponent>>();
|
||||
|
||||
// TODO: Is this the right concept? Can a component have multiple WinForms controls created?
|
||||
public TNativeComponent TargetControl { get; set; }
|
||||
|
||||
public EmblazonRenderer<TNativeComponent> Renderer { get; private set; }
|
||||
|
||||
internal void SetRenderer(EmblazonRenderer<TNativeComponent> renderer)
|
||||
{
|
||||
Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging purposes.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"EmblazonAdapter: Name={Name ?? "<?>"}, Target={TargetControl?.GetType().Name ?? "<?>"}, #Children={Children.Count}";
|
||||
}
|
||||
|
||||
internal void ApplyEdits(int componentId, ArrayBuilderSegment<RenderTreeEdit> edits, ArrayRange<RenderTreeFrame> referenceFrames, RenderBatch batch)
|
||||
{
|
||||
foreach (var edit in edits)
|
||||
{
|
||||
switch (edit.Type)
|
||||
{
|
||||
case RenderTreeEditType.PrependFrame:
|
||||
ApplyPrependFrame(batch, componentId, edit.SiblingIndex, referenceFrames.Array, edit.ReferenceFrameIndex);
|
||||
break;
|
||||
case RenderTreeEditType.SetAttribute:
|
||||
ApplySetAttribute(ref referenceFrames.Array[edit.ReferenceFrameIndex]);
|
||||
break;
|
||||
case RenderTreeEditType.RemoveFrame:
|
||||
ApplyRemoveFrame(edit.SiblingIndex);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Not supported edit type: {edit.Type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRemoveFrame(int siblingIndex)
|
||||
{
|
||||
var childToRemove = Children[siblingIndex];
|
||||
|
||||
// If there's a target control for the child adapter, remove it from the live control tree.
|
||||
// Not all adapters have target controls; Adapters for markup/text have no associated native control.
|
||||
if (childToRemove.TargetControl != null)
|
||||
{
|
||||
RemoveChildControl(childToRemove);
|
||||
}
|
||||
Children.RemoveAt(siblingIndex);
|
||||
}
|
||||
|
||||
protected abstract void RemoveChildControl(EmblazonAdapter<TNativeComponent> child);
|
||||
|
||||
private void ApplySetAttribute(ref RenderTreeFrame attributeFrame)
|
||||
{
|
||||
var mapper = GetControlPropertyMapper(TargetControl);
|
||||
mapper.SetControlProperty(attributeFrame.AttributeEventHandlerId, attributeFrame.AttributeName, attributeFrame.AttributeValue, attributeFrame.AttributeEventUpdatesAttributeName);
|
||||
}
|
||||
|
||||
private int ApplyPrependFrame(RenderBatch batch, int componentId, int siblingIndex, RenderTreeFrame[] frames, int frameIndex)
|
||||
{
|
||||
ref var frame = ref frames[frameIndex];
|
||||
switch (frame.FrameType)
|
||||
{
|
||||
case RenderTreeFrameType.Element:
|
||||
{
|
||||
InsertElement(siblingIndex, frames, frameIndex, componentId, batch);
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Component:
|
||||
{
|
||||
// Components are represented by BlontrolAdapters
|
||||
var childAdapter = Renderer.CreateAdapterForChildComponent(frame.ComponentId);
|
||||
childAdapter.Name = $"For: '{frame.Component.GetType().FullName}'";
|
||||
AddChildAdapter(siblingIndex, childAdapter);
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Region:
|
||||
{
|
||||
return InsertFrameRange(batch, componentId, siblingIndex, frames, frameIndex + 1, frameIndex + frame.RegionSubtreeLength);
|
||||
}
|
||||
case RenderTreeFrameType.Markup:
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(frame.MarkupContent))
|
||||
{
|
||||
throw new NotImplementedException("Nonempty markup: " + frame.MarkupContent);
|
||||
}
|
||||
var childAdapter = CreateAdapter();
|
||||
childAdapter.Name = $"Dummy markup, sib#={siblingIndex}";
|
||||
childAdapter.SetRenderer(Renderer);
|
||||
AddChildAdapter(siblingIndex, childAdapter);
|
||||
return 1;
|
||||
}
|
||||
case RenderTreeFrameType.Text:
|
||||
{
|
||||
// TODO: Maybe support this for Labels for Text property, etc. ("DefaultProperty"?)
|
||||
if (!string.IsNullOrWhiteSpace(frame.TextContent))
|
||||
{
|
||||
throw new NotImplementedException("Nonempty text: " + frame.TextContent);
|
||||
}
|
||||
var childAdapter = CreateAdapter();
|
||||
childAdapter.Name = $"Dummy text, sib#={siblingIndex}";
|
||||
childAdapter.SetRenderer(Renderer);
|
||||
AddChildAdapter(siblingIndex, childAdapter);
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException($"Not supported frame type: {frame.FrameType}");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract EmblazonAdapter<TNativeComponent> CreateAdapter();
|
||||
|
||||
protected abstract bool IsChildControlParented(TNativeComponent nativeChild);
|
||||
|
||||
private void InsertElement(int siblingIndex, RenderTreeFrame[] frames, int frameIndex, int componentId, RenderBatch batch)
|
||||
{
|
||||
// Elements represent Winforms native controls
|
||||
ref var frame = ref frames[frameIndex];
|
||||
var elementName = frame.ElementName;
|
||||
var controlFactory = KnownElements[elementName];
|
||||
var nativeControl = controlFactory.CreateControl(new ComponentControlFactoryContext<TNativeComponent>(Renderer, Parent?.TargetControl));
|
||||
|
||||
TargetControl = nativeControl;
|
||||
|
||||
// TODO: Need a more reliable way to know whether the target control is already created, e.g. a return value
|
||||
// from ControlFactory.CreateControl(). Right now the check assumes that if the target control is already parented,
|
||||
// there is no need to parent it. Not an awful assumption, but looks odd.
|
||||
if (!IsChildControlParented(TargetControl))
|
||||
{
|
||||
// Add the new native control to the parent's child controls (the parent adapter is our
|
||||
// container, so the parent adapter's control is our control's container.
|
||||
AddChildControl(Parent.TargetControl, siblingIndex, TargetControl);
|
||||
}
|
||||
|
||||
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||
for (var descendantIndex = frameIndex + 1; descendantIndex < endIndexExcl; descendantIndex++)
|
||||
{
|
||||
var candidateFrame = frames[descendantIndex];
|
||||
if (candidateFrame.FrameType == RenderTreeFrameType.Attribute)
|
||||
{
|
||||
// TODO: Do smarter property setting...? Not calling <NativeControl>.ApplyAttribute(...) right now. Should it?
|
||||
ApplySetAttribute(ref candidateFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
// As soon as we see a non-attribute child, all the subsequent child frames are
|
||||
// not attributes, so bail out and insert the remnants recursively
|
||||
InsertFrameRange(batch, componentId, childIndex: 0, frames, descendantIndex, endIndexExcl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// Ignoring non-controls, such as Timer Component
|
||||
|
||||
//if (element is Control elementControl)
|
||||
//{
|
||||
// AddChildControl(siblingIndex, elementControl);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Debug.WriteLine("Ignoring non-control child: " + element.GetType().FullName);
|
||||
//}
|
||||
}
|
||||
|
||||
private int InsertFrameRange(RenderBatch batch, int componentId, int childIndex, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
{
|
||||
var origChildIndex = childIndex;
|
||||
for (var index = startIndex; index < endIndexExcl; index++)
|
||||
{
|
||||
ref var frame = ref batch.ReferenceFrames.Array[index];
|
||||
var numChildrenInserted = ApplyPrependFrame(batch, componentId, childIndex, frames, index);
|
||||
childIndex += numChildrenInserted;
|
||||
|
||||
// Skip over any descendants, since they are already dealt with recursively
|
||||
index += CountDescendantFrames(frame);
|
||||
}
|
||||
|
||||
return (childIndex - origChildIndex); // Total number of children inserted
|
||||
}
|
||||
|
||||
private int CountDescendantFrames(RenderTreeFrame frame)
|
||||
{
|
||||
switch (frame.FrameType)
|
||||
{
|
||||
// The following frame types have a subtree length. Other frames may use that memory slot
|
||||
// to mean something else, so we must not read it. We should consider having nominal subtypes
|
||||
// of RenderTreeFramePointer that prevent access to non-applicable fields.
|
||||
case RenderTreeFrameType.Component:
|
||||
return frame.ComponentSubtreeLength - 1;
|
||||
case RenderTreeFrameType.Element: return frame.ElementSubtreeLength - 1;
|
||||
case RenderTreeFrameType.Region: return frame.RegionSubtreeLength - 1;
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
private void AddChildAdapter(int siblingIndex, EmblazonAdapter<TNativeComponent> childAdapter)
|
||||
{
|
||||
childAdapter.Parent = this;
|
||||
|
||||
if (siblingIndex <= Children.Count)
|
||||
{
|
||||
Children.Insert(siblingIndex, childAdapter);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"WARNING: {nameof(AddChildAdapter)} called with {nameof(siblingIndex)}={siblingIndex}, but Children.Count={Children.Count}");
|
||||
Children.Add(childAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void AddChildControl(TNativeComponent parentControl, int siblingIndex, TNativeComponent childControl);
|
||||
|
||||
private static IControlPropertyMapper GetControlPropertyMapper(TNativeComponent control)
|
||||
{
|
||||
// TODO: Have control-specific ones, but also need a general one for custom controls? Or maybe not needed?
|
||||
if (control is IBlazorNativeControl nativeControl)
|
||||
{
|
||||
return new NativeControlPropertyMapper(nativeControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ReflectionControlPropertyMapper(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emblazon
|
||||
{
|
||||
public abstract class EmblazonRenderer<TNativeComponent> : Renderer where TNativeComponent : class
|
||||
{
|
||||
private readonly Dictionary<int, EmblazonAdapter<TNativeComponent>> _componentIdToAdapter = new Dictionary<int, EmblazonAdapter<TNativeComponent>>();
|
||||
|
||||
public EmblazonRenderer(IServiceProvider serviceProvider)
|
||||
: base(serviceProvider, new LoggerFactory())
|
||||
{
|
||||
}
|
||||
|
||||
public override Dispatcher Dispatcher { get; }
|
||||
= Dispatcher.CreateDefault();
|
||||
|
||||
|
||||
public Task AddComponent<T>() where T : IComponent
|
||||
{
|
||||
var component = InstantiateComponent(typeof(T));
|
||||
var componentId = AssignRootComponentId(component);
|
||||
var adapter = CreateAdapter();
|
||||
adapter.Name = "Root BlontrolAdapter";
|
||||
adapter.SetRenderer(this);
|
||||
|
||||
InitializeRootAdapter(adapter);
|
||||
|
||||
_componentIdToAdapter[componentId] = adapter;
|
||||
return RenderRootComponentAsync(componentId);
|
||||
}
|
||||
|
||||
protected abstract void InitializeRootAdapter(EmblazonAdapter<TNativeComponent> adapter);
|
||||
|
||||
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
|
||||
{
|
||||
foreach (var updatedComponent in renderBatch.UpdatedComponents.Array.Take(renderBatch.UpdatedComponents.Count))
|
||||
{
|
||||
var adapter = _componentIdToAdapter[updatedComponent.ComponentId];
|
||||
adapter.ApplyEdits(updatedComponent.ComponentId, updatedComponent.Edits, renderBatch.ReferenceFrames, renderBatch);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal EmblazonAdapter<TNativeComponent> CreateAdapterForChildComponent(int componentId)
|
||||
{
|
||||
var result = CreateAdapter();
|
||||
result.SetRenderer(this);
|
||||
_componentIdToAdapter[componentId] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract EmblazonAdapter<TNativeComponent> CreateAdapter();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace BlinForms.Framework.Controls
|
||||
namespace Emblazon
|
||||
{
|
||||
internal interface IBlazorNativeControl
|
||||
public interface IBlazorNativeControl
|
||||
{
|
||||
void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Emblazon
|
||||
{
|
||||
public interface IComponentControlFactory<TNativeComponent> where TNativeComponent : class
|
||||
{
|
||||
TNativeComponent CreateControl(ComponentControlFactoryContext<TNativeComponent> context);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace BlinForms.Framework
|
||||
namespace Emblazon
|
||||
{
|
||||
internal interface IControlPropertyMapper
|
||||
{
|
|
@ -1,6 +1,4 @@
|
|||
using BlinForms.Framework.Controls;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
namespace Emblazon
|
||||
{
|
||||
internal class NativeControlPropertyMapper : IControlPropertyMapper
|
||||
{
|
|
@ -1,43 +1,42 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BlinForms.Framework
|
||||
namespace Emblazon
|
||||
{
|
||||
internal class ReflectionControlPropertyMapper : IControlPropertyMapper
|
||||
{
|
||||
private readonly Control _control;
|
||||
private readonly object _component;
|
||||
|
||||
public ReflectionControlPropertyMapper(Control control)
|
||||
public ReflectionControlPropertyMapper(object component)
|
||||
{
|
||||
_control = control;
|
||||
_component = component;
|
||||
}
|
||||
|
||||
public void SetControlProperty(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
|
||||
{
|
||||
// TODO: Figure out how to wire up events, e.g. OnClick
|
||||
var propertyInfo = _control.GetType().GetProperty(attributeName);
|
||||
var propertyInfo = _component.GetType().GetProperty(attributeName);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
if (propertyInfo.PropertyType == typeof(string))
|
||||
{
|
||||
propertyInfo.SetValue(_control, attributeValue);
|
||||
propertyInfo.SetValue(_component, attributeValue);
|
||||
}
|
||||
else if (propertyInfo.PropertyType == typeof(int))
|
||||
{
|
||||
propertyInfo.SetValue(_control, int.Parse((string)attributeValue));
|
||||
propertyInfo.SetValue(_component, int.Parse((string)attributeValue));
|
||||
}
|
||||
else if (propertyInfo.PropertyType == typeof(bool))
|
||||
{
|
||||
propertyInfo.SetValue(_control, attributeValue);
|
||||
propertyInfo.SetValue(_component, attributeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Unknown property type '{propertyInfo.PropertyType}' for '{attributeName}' on '{_control.GetType().FullName}'.");
|
||||
Debug.WriteLine($"Unknown property type '{propertyInfo.PropertyType}' for '{attributeName}' on '{_component.GetType().FullName}'.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Unknown property '{attributeName}' on '{_control.GetType().FullName}'. Maybe an event handler?");
|
||||
Debug.WriteLine($"Unknown property '{attributeName}' on '{_component.GetType().FullName}'. Maybe an event handler?");
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче