diff --git a/CPF.Razor/CPF.Razor.csproj b/CPF.Razor/CPF.Razor.csproj
new file mode 100644
index 0000000..449f4b6
--- /dev/null
+++ b/CPF.Razor/CPF.Razor.csproj
@@ -0,0 +1,37 @@
+
+
+
+ netstandard2.0
+ 9.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/CPF.Razor/Controls/Element.cs b/CPF.Razor/Controls/Element.cs
new file mode 100644
index 0000000..fd6fa35
--- /dev/null
+++ b/CPF.Razor/Controls/Element.cs
@@ -0,0 +1,40 @@
+using Microsoft.AspNetCore.Components;
+//using Microsoft.MobileBlazorBindings.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CPF.Razor.Controls
+{
+ public abstract class Element : NativeControlComponentBase where T : UIElement, new()
+ {
+ [Parameter] public string MarginLeft { get; set; }
+ [Parameter] public string MarginTop { get; set; }
+ [Parameter] public string Width { get; set; }
+ [Parameter] public string Height { get; set; }
+
+ //public CPF.UIElement NativeControl => ((ICpfElementHandler)ElementHandler).Element;
+
+ protected override void RenderAttributes(AttributesBuilder builder)
+ {
+ base.RenderAttributes(builder);
+
+ if (MarginLeft != null)
+ {
+ builder.AddAttribute(nameof(MarginLeft), MarginLeft);
+ }
+ if (MarginTop != null)
+ {
+ builder.AddAttribute(nameof(MarginTop), MarginTop);
+ }
+ if (Height != null)
+ {
+ builder.AddAttribute(nameof(Height), Height);
+ }
+ if (Width != null)
+ {
+ builder.AddAttribute(nameof(Width), Width);
+ }
+ }
+ }
+}
diff --git a/CPF.Razor/Controls/Panel.cs b/CPF.Razor/Controls/Panel.cs
new file mode 100644
index 0000000..a49199f
--- /dev/null
+++ b/CPF.Razor/Controls/Panel.cs
@@ -0,0 +1,50 @@
+using Microsoft.AspNetCore.Components;
+//using Microsoft.MobileBlazorBindings.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CPF.Razor.Controls
+{
+ public partial class Panel : Element
+ {
+ //static Panel()
+ //{
+ // ElementHandlerRegistry.RegisterElementHandler();
+ //}
+
+ [Parameter] public string Background { get; set; }
+
+#pragma warning disable CA1721 // Property names should not match get methods
+ [Parameter] public RenderFragment ChildContent { get; set; }
+#pragma warning restore CA1721 // Property names should not match get methods
+ protected override void RenderAttributes(AttributesBuilder builder)
+ {
+ base.RenderAttributes(builder);
+
+ if (Background != null)
+ {
+ builder.AddAttribute(nameof(Background), Background);
+ }
+ }
+ protected override RenderFragment GetChildContent() => ChildContent;
+
+ public override void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
+ {
+ //switch (attributeName)
+ //{
+ // //case nameof(AutoScroll):
+ // // AutoScroll = AttributeHelper.GetBool(attributeValue);
+ // // break;
+ // default:
+
+ // break;
+ //}
+ var p = Element.GetPropertyMetadata(attributeName);
+ if (p != null)
+ {
+ Element.SetValue(attributeValue.ConvertTo(p.PropertyType), attributeName);
+ }
+ }
+ }
+}
diff --git a/CPF.Razor/Controls/TestElement.cs b/CPF.Razor/Controls/TestElement.cs
new file mode 100644
index 0000000..c24aa36
--- /dev/null
+++ b/CPF.Razor/Controls/TestElement.cs
@@ -0,0 +1,153 @@
+using Microsoft.AspNetCore.Components;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CPF.Razor.Controls
+{
+ public class TestElement : Microsoft.AspNetCore.Components.IComponent, IHandleEvent, IHandleAfterRender, ICustomTypeDescriptor
+ {
+ [Parameter]
+ public string Test { get; set; }
+
+ public void Attach(RenderHandle renderHandle)
+ {
+ throw new NotImplementedException();
+ }
+
+ public AttributeCollection GetAttributes()
+ {
+ throw new NotImplementedException();
+ }
+
+ public string GetClassName()
+ {
+ return "TestElement";
+ }
+
+ public string GetComponentName()
+ {
+ return "GetComponentName";
+ }
+
+ public TypeConverter GetConverter()
+ {
+ throw new NotImplementedException();
+ }
+
+ public EventDescriptor GetDefaultEvent()
+ {
+ throw new NotImplementedException();
+ }
+
+ public PropertyDescriptor GetDefaultProperty()
+ {
+ throw new NotImplementedException();
+ }
+
+ public object GetEditor(Type editorBaseType)
+ {
+ throw new NotImplementedException();
+ }
+
+ public EventDescriptorCollection GetEvents()
+ {
+ throw new NotImplementedException();
+ }
+
+ public EventDescriptorCollection GetEvents(Attribute[] attributes)
+ {
+ throw new NotImplementedException();
+ }
+
+ public PropertyDescriptorCollection GetProperties()
+ {
+ return new PropertyDescriptorCollection(new CpfPropertyDescriptor[] { new CpfPropertyDescriptor("Pro1", false, typeof(string), null) });
+ }
+
+ public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object GetPropertyOwner(PropertyDescriptor pd)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task OnAfterRenderAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetParametersAsync(ParameterView parameters)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ class CpfPropertyDescriptor : PropertyDescriptor
+ {
+ public CpfPropertyDescriptor(string name, bool readOnly, Type type, Attribute[] attributes) : base(name, attributes)
+ {
+ isreadonly = readOnly;
+ pType = type;
+ }
+ public string FileTypes { get; set; }
+ public bool IsAttached { get; set; }
+ public bool IsDependency { get; set; }
+
+ bool isreadonly;
+ Type pType;
+ public override Type ComponentType => typeof(TestElement);
+
+ public override bool IsReadOnly => isreadonly;
+
+ public override Type PropertyType => pType;
+
+ public override bool CanResetValue(object component)
+ {
+ return false;
+ }
+
+ public override object GetValue(object component)
+ {
+ if (component is UIElement element)
+ {
+ return element.GetPropretyValue(Name);
+ }
+ return null;
+ }
+
+ public override void ResetValue(object component)
+ {
+ //if (component is UIElement element)
+ //{
+ // element.ResetValue(Name);
+ //}
+ }
+
+ public override void SetValue(object component, object value)
+ {
+ if (component is UIElement element)
+ {
+ element.SetPropretyValue(Name, value);
+ }
+ }
+
+ public override bool ShouldSerializeValue(object component)
+ {
+ return false;
+ }
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+}
diff --git a/CPF.Razor/Core/AttributesBuilder.cs b/CPF.Razor/Core/AttributesBuilder.cs
new file mode 100644
index 0000000..159649f
--- /dev/null
+++ b/CPF.Razor/Core/AttributesBuilder.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace CPF.Razor
+{
+ // This wraps a RenderTreeBuilder in such a way that consumers
+ // can only call the desired AddAttribute method, can't supply
+ // sequence numbers, and can't leak the instance outside their
+ // position in the call stack.
+
+#pragma warning disable CA1815 // Override equals and operator equals on value types; these instances are never compared
+ public readonly ref struct AttributesBuilder
+#pragma warning restore CA1815 // Override equals and operator equals on value types
+ {
+ private readonly RenderTreeBuilder _underlyingBuilder;
+
+ public AttributesBuilder(RenderTreeBuilder underlyingBuilder)
+ {
+ _underlyingBuilder = underlyingBuilder;
+ }
+
+ public void AddAttribute(string name, object value)
+ {
+ // Using a fixed sequence number is allowed for attribute frames,
+ // and causes the diff algorithm to use a dictionary to match old
+ // and new values.
+ _underlyingBuilder.AddAttribute(0, name, value);
+ }
+
+ public void AddAttribute(string name, bool value)
+ {
+ // Using a fixed sequence number is allowed for attribute frames,
+ // and causes the diff algorithm to use a dictionary to match old
+ // and new values.
+
+ // bool values are converted to ints (which later become strings) to ensure that
+ // all values are always rendered, not only 'true' values. This ensures that the
+ // element handlers will see all property changes and can handle them as needed.
+ _underlyingBuilder.AddAttribute(0, name, value ? 1 : 0);
+ }
+ }
+}
diff --git a/CPF.Razor/Core/ElementHandlerFactory.cs b/CPF.Razor/Core/ElementHandlerFactory.cs
new file mode 100644
index 0000000..3876ca8
--- /dev/null
+++ b/CPF.Razor/Core/ElementHandlerFactory.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+
+namespace CPF.BlazorBindings
+{
+ internal class ElementHandlerFactory
+ {
+ private readonly Func _callback;
+
+ public ElementHandlerFactory(Func callback)
+ {
+ _callback = callback ?? throw new ArgumentNullException(nameof(callback));
+ }
+
+ public IElementHandler CreateElementHandler(ElementHandlerFactoryContext context)
+ {
+ return _callback(context.Renderer, context.ParentHandler);
+ }
+ }
+}
diff --git a/CPF.Razor/Core/ElementHandlerFactoryContext.cs b/CPF.Razor/Core/ElementHandlerFactoryContext.cs
new file mode 100644
index 0000000..98a6607
--- /dev/null
+++ b/CPF.Razor/Core/ElementHandlerFactoryContext.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+
+namespace CPF.BlazorBindings
+{
+ internal class ElementHandlerFactoryContext
+ {
+ public ElementHandlerFactoryContext(NativeComponentRenderer renderer, IElementHandler parentHandler)
+ {
+ Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
+ ParentHandler = parentHandler;
+ }
+
+ public IElementHandler ParentHandler { get; }
+
+ public NativeComponentRenderer Renderer { get; }
+ }
+}
diff --git a/CPF.Razor/Core/ElementHandlerRegistry.cs b/CPF.Razor/Core/ElementHandlerRegistry.cs
new file mode 100644
index 0000000..e26dfec
--- /dev/null
+++ b/CPF.Razor/Core/ElementHandlerRegistry.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+namespace CPF.BlazorBindings
+{
+ public static class ElementHandlerRegistry
+ {
+ //internal static Dictionary ElementHandlers { get; }
+ // = new Dictionary();
+
+ //public static void RegisterElementHandler(
+ // Func factory) where TComponent : NativeControlComponentBase
+ //{
+ // ElementHandlers.Add(typeof(TComponent).FullName, new ElementHandlerFactory(factory));
+ //}
+
+ //public static void RegisterElementHandler(
+ // Func factory) where TComponent : NativeControlComponentBase
+ //{
+ // ElementHandlers.Add(typeof(TComponent).FullName, new ElementHandlerFactory((renderer, _) => factory(renderer)));
+ //}
+
+ //public static void RegisterElementHandler() where TComponent : NativeControlComponentBase where TControlHandler : class, IElementHandler, new()
+ //{
+ // RegisterElementHandler((_, __) => new TControlHandler());
+ //}
+ }
+}
diff --git a/CPF.Razor/Core/ElementManager.cs b/CPF.Razor/Core/ElementManager.cs
new file mode 100644
index 0000000..3f7050e
--- /dev/null
+++ b/CPF.Razor/Core/ElementManager.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace CPF.Razor
+{
+ ///
+ /// Utilities needed by the system to manage native controls. Implementations
+ /// of native rendering systems have their own quirks in terms of dealing with
+ /// parent/child relationships, so each must implement this given the constraints
+ /// and requirements of their systems.
+ ///
+ public abstract class ElementManager
+ {
+ public abstract void AddChildElement(IElementHandler parentHandler, IElementHandler childHandler, int physicalSiblingIndex);
+ public abstract int GetPhysicalSiblingIndex(IElementHandler handler);
+ public abstract bool IsParented(IElementHandler handler);
+ public abstract bool IsParentOfChild(IElementHandler parentHandler, IElementHandler childHandler);
+ public abstract void RemoveElement(IElementHandler handler);
+ }
+}
diff --git a/CPF.Razor/Core/ElementManagerOfElementType.cs b/CPF.Razor/Core/ElementManagerOfElementType.cs
new file mode 100644
index 0000000..5aa2fc5
--- /dev/null
+++ b/CPF.Razor/Core/ElementManagerOfElementType.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+
+namespace CPF.Razor
+{
+ ///
+ /// Utility intermediate class to make it easier to strongly-type a derived .
+ ///
+ ///
+ public abstract class ElementManager : ElementManager
+ {
+ private static TElementType ConvertToType(IElementHandler elementHandler, string parameterName)
+ {
+ if (!(elementHandler is TElementType))
+ {
+ throw new ArgumentException($"Expected parameter value of type '{elementHandler.GetType().FullName}' to be convertible to type '{typeof(TElementType).FullName}'.", parameterName);
+ }
+ return (TElementType)elementHandler;
+ }
+
+ public sealed override void AddChildElement(IElementHandler parentHandler, IElementHandler childHandler, int physicalSiblingIndex)
+ {
+ AddChildElement(ConvertToType(parentHandler, nameof(parentHandler)), ConvertToType(childHandler, nameof(childHandler)), physicalSiblingIndex);
+ }
+
+ public sealed override int GetPhysicalSiblingIndex(IElementHandler handler)
+ {
+ return GetPhysicalSiblingIndex(ConvertToType(handler, nameof(handler)));
+ }
+
+ public sealed override bool IsParented(IElementHandler handler)
+ {
+ return IsParented(ConvertToType(handler, nameof(handler)));
+ }
+
+ public sealed override bool IsParentOfChild(IElementHandler parentHandler, IElementHandler childHandler)
+ {
+ return IsParentOfChild(ConvertToType(parentHandler, nameof(parentHandler)), ConvertToType(childHandler, nameof(childHandler)));
+ }
+
+ public sealed override void RemoveElement(IElementHandler handler)
+ {
+ RemoveElement(ConvertToType(handler, nameof(handler)));
+ }
+
+ protected abstract void AddChildElement(TElementType elementType1, TElementType elementType2, int physicalSiblingIndex);
+ protected abstract int GetPhysicalSiblingIndex(TElementType elementType);
+ protected abstract bool IsParented(TElementType elementType);
+ protected abstract bool IsParentOfChild(TElementType elementType1, TElementType elementType2);
+ protected abstract void RemoveElement(TElementType elementType);
+ }
+}
diff --git a/CPF.Razor/Core/IElementHandler.cs b/CPF.Razor/Core/IElementHandler.cs
new file mode 100644
index 0000000..c340f03
--- /dev/null
+++ b/CPF.Razor/Core/IElementHandler.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace CPF.Razor
+{
+ ///
+ /// Represents a container for native element.
+ ///
+ public interface IElementHandler
+ {
+ ///
+ /// Sets an attribute named on the represented by
+ /// this handler to value .
+ ///
+ ///
+ ///
+ ///
+ ///
+ void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName);
+
+ ///
+ /// The native element represented by this handler. This is often a native UI component, but can be any type
+ /// of component used by the native system.
+ ///
+ object TargetElement { get; }
+ }
+}
diff --git a/CPF.Razor/Core/IHandleChildContentText.cs b/CPF.Razor/Core/IHandleChildContentText.cs
new file mode 100644
index 0000000..a58b2bb
--- /dev/null
+++ b/CPF.Razor/Core/IHandleChildContentText.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace CPF.Razor
+{
+ ///
+ /// Defines a mechanism for an to accept inline text.
+ ///
+ public interface IHandleChildContentText
+ {
+ ///
+ /// This method is called to process inline text found in a component.
+ ///
+ /// the index of the string within a group of text strings.
+ /// The text to handle. This text may contain whitespace at the start and end of the string.
+ void HandleText(int index, string text);
+ }
+}
diff --git a/CPF.Razor/Core/INonChildContainerElement.cs b/CPF.Razor/Core/INonChildContainerElement.cs
new file mode 100644
index 0000000..e3459b5
--- /dev/null
+++ b/CPF.Razor/Core/INonChildContainerElement.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+namespace CPF.Razor
+{
+ ///
+ /// Marker interface to indicate that this element is a container of elements that are not
+ /// true children of their parent. For example, a host for elements that go in a modal dialog
+ /// are not true children of their parent.
+ ///
+#pragma warning disable CA1040 // Avoid empty interfaces
+ public interface INonChildContainerElement
+#pragma warning restore CA1040 // Avoid empty interfaces
+ {
+ }
+}
diff --git a/CPF.Razor/Core/NativeComponentAdapter.cs b/CPF.Razor/Core/NativeComponentAdapter.cs
new file mode 100644
index 0000000..1658948
--- /dev/null
+++ b/CPF.Razor/Core/NativeComponentAdapter.cs
@@ -0,0 +1,465 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.RenderTree;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+
+namespace CPF.Razor
+{
+ ///
+ /// Represents a "shadow" item that Blazor uses to map changes into the live native UI tree.
+ ///
+ [DebuggerDisplay("{DebugName}")]
+ internal sealed class NativeComponentAdapter : IDisposable
+ {
+ private static volatile int DebugInstanceCounter;
+
+ public NativeComponentAdapter(NativeComponentRenderer renderer, IElementHandler closestPhysicalParent, IElementHandler knownTargetElement = null)
+ {
+ Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
+ _closestPhysicalParent = closestPhysicalParent;
+ _targetElement = knownTargetElement;
+
+ // Assign unique counter value. This *should* all be done on one thread, but just in case, make it thread-safe.
+ _debugInstanceCounterValue = Interlocked.Increment(ref DebugInstanceCounter);
+ }
+
+ private readonly int _debugInstanceCounterValue;
+
+ private string DebugName => $"[#{_debugInstanceCounterValue}] {Name}";
+
+ public NativeComponentAdapter Parent { get; private set; }
+ public List Children { get; } = new List();
+
+ private readonly IElementHandler _closestPhysicalParent;
+ private IElementHandler _targetElement;
+ private IComponent _targetComponent;
+
+ public NativeComponentRenderer Renderer { get; }
+
+ ///
+ /// Used for debugging purposes.
+ ///
+ public string Name { get; internal set; }
+
+ public override string ToString()
+ {
+ return $"{nameof(NativeComponentAdapter)}: Name={Name ?? ">"}, Target={_targetElement?.GetType().Name ?? ""}, #Children={Children.Count}";
+ }
+
+ internal void ApplyEdits(int componentId, ArrayBuilderSegment edits, ArrayRange referenceFrames, RenderBatch batch)
+ {
+ Renderer.Dispatcher.AssertAccess();
+
+ if (edits.Count == 0)
+ {
+ // TODO: Without this check there's a NullRef in ArrayBuilderSegment? Possibly a Blazor bug?
+ return;
+ }
+
+ foreach (var edit in edits)
+ {
+ switch (edit.Type)
+ {
+ case RenderTreeEditType.PrependFrame:
+ ApplyPrependFrame(batch, componentId, edit.SiblingIndex, referenceFrames.Array, edit.ReferenceFrameIndex);
+ break;
+ case RenderTreeEditType.RemoveFrame:
+ ApplyRemoveFrame(edit.SiblingIndex);
+ break;
+ case RenderTreeEditType.SetAttribute:
+ ApplySetAttribute(ref referenceFrames.Array[edit.ReferenceFrameIndex]);
+ break;
+ case RenderTreeEditType.RemoveAttribute:
+ // TODO: See whether siblingIndex is needed here
+ ApplyRemoveAttribute(edit.RemovedAttributeName);
+ break;
+ case RenderTreeEditType.UpdateText:
+ {
+ var frame = batch.ReferenceFrames.Array[edit.ReferenceFrameIndex];
+ if (_targetElement is IHandleChildContentText handleChildContentText)
+ {
+ handleChildContentText.HandleText(edit.SiblingIndex, frame.TextContent);
+ }
+ else
+ {
+ throw new Exception("Cannot set text content on child that doesn't handle inner text content.");
+ }
+ break;
+ }
+ case RenderTreeEditType.StepIn:
+ {
+ // TODO: Need to implement this. For now it seems safe to ignore.
+ break;
+ }
+ case RenderTreeEditType.StepOut:
+ {
+ // TODO: Need to implement this. For now it seems safe to ignore.
+ break;
+ }
+ case RenderTreeEditType.UpdateMarkup:
+ {
+ var frame = batch.ReferenceFrames.Array[edit.ReferenceFrameIndex];
+ if (_targetElement is IHandleChildContentText handleChildContentText)
+ {
+ handleChildContentText.HandleText(edit.SiblingIndex, frame.MarkupContent);
+ }
+ else
+ {
+ throw new Exception("Cannot set markup content on child that doesn't handle inner text content.");
+ }
+ break;
+ }
+ case RenderTreeEditType.PermutationListEntry:
+ throw new NotImplementedException($"Not supported edit type: {edit.Type}");
+ case RenderTreeEditType.PermutationListEnd:
+ throw new NotImplementedException($"Not supported edit type: {edit.Type}");
+ default:
+ throw new NotImplementedException($"Invalid edit type: {edit.Type}");
+ }
+ }
+ }
+
+ private void ApplyRemoveFrame(int siblingIndex)
+ {
+ var childToRemove = Children[siblingIndex];
+ Children.RemoveAt(siblingIndex);
+ childToRemove.RemoveSelfAndDescendants();
+ }
+
+ private void RemoveSelfAndDescendants()
+ {
+ if (_targetElement != null)
+ {
+ // This adapter represents a physical element, so by removing it, we implicitly
+ // remove all descendants.
+ Renderer.ElementManager.RemoveElement(_targetElement);
+ }
+ else
+ {
+ // This adapter is just a container for other adapters
+ foreach (var child in Children)
+ {
+ child.RemoveSelfAndDescendants();
+ }
+ }
+ }
+
+ private void ApplySetAttribute(ref RenderTreeFrame attributeFrame)
+ {
+ if (_targetElement == null)
+ {
+ throw new InvalidOperationException($"Trying to apply attribute {attributeFrame.AttributeName} to an adapter that isn't for an element");
+ }
+
+ _targetElement.ApplyAttribute(
+ attributeFrame.AttributeEventHandlerId,
+ attributeFrame.AttributeName,
+ attributeFrame.AttributeValue,
+ attributeFrame.AttributeEventUpdatesAttributeName);
+ }
+
+ private void ApplyRemoveAttribute(string removedAttributeName)
+ {
+ if (_targetElement == null)
+ {
+ throw new InvalidOperationException($"Trying to remove attribute {removedAttributeName} to an adapter that isn't for an element");
+ }
+
+ _targetElement.ApplyAttribute(
+ attributeEventHandlerId: 0,
+ attributeName: removedAttributeName,
+ attributeValue: null,
+ attributeEventUpdatesAttributeName: null);
+ }
+
+ 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 NativeComponentAdapter
+ var childAdapter = Renderer.CreateAdapterForChildComponent(_targetElement ?? _closestPhysicalParent, frame.ComponentId);
+ childAdapter.Name = $"For: '{frame.Component.GetType().FullName}'";
+ childAdapter._targetComponent = frame.Component;
+ AddChildAdapter(siblingIndex, childAdapter);
+ return 1;
+ }
+ case RenderTreeFrameType.Region:
+ {
+ return InsertFrameRange(batch, componentId, siblingIndex, frames, frameIndex + 1, frameIndex + frame.RegionSubtreeLength);
+ }
+ case RenderTreeFrameType.Markup:
+ {
+ if (_targetElement is IHandleChildContentText handleChildContentText)
+ {
+ handleChildContentText.HandleText(siblingIndex, frame.MarkupContent);
+ }
+ else if (!string.IsNullOrWhiteSpace(frame.MarkupContent))
+ {
+ throw new NotImplementedException("Nonempty markup: " + frame.MarkupContent);
+ }
+#pragma warning disable CA2000 // Dispose objects before losing scope; adapters are disposed when they are removed from the adapter tree
+ var childAdapter = CreateAdapter(_targetElement ?? _closestPhysicalParent);
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ childAdapter.Name = $"Markup, sib#={siblingIndex}";
+ AddChildAdapter(siblingIndex, childAdapter);
+ return 1;
+ }
+ case RenderTreeFrameType.Text:
+ {
+ if (_targetElement is IHandleChildContentText handleChildContentText)
+ {
+ handleChildContentText.HandleText(siblingIndex, frame.TextContent);
+ }
+ else if (!string.IsNullOrWhiteSpace(frame.TextContent))
+ {
+ throw new NotImplementedException("Nonempty text: " + frame.TextContent);
+ }
+#pragma warning disable CA2000 // Dispose objects before losing scope; adapters are disposed when they are removed from the adapter tree
+ var childAdapter = CreateAdapter(_targetElement ?? _closestPhysicalParent);
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ childAdapter.Name = $"Text, sib#={siblingIndex}";
+ AddChildAdapter(siblingIndex, childAdapter);
+ return 1;
+ }
+ default:
+ throw new NotImplementedException($"Not supported frame type: {frame.FrameType}");
+ }
+ }
+
+ private NativeComponentAdapter CreateAdapter(IElementHandler physicalParent)
+ {
+ return new NativeComponentAdapter(Renderer, physicalParent);
+ }
+
+ private void InsertElement(int siblingIndex, RenderTreeFrame[] frames, int frameIndex, int componentId, RenderBatch batch)
+ {
+ // Elements represent native elements
+ ref var frame = ref frames[frameIndex];
+ //var elementName = frame.ElementName;
+ //var elementHandlerFactory = ElementHandlerRegistry.ElementHandlers[elementName];
+
+ var elementHandler = _targetComponent as IElementHandler;
+ //var elementHandler = elementHandlerFactory.CreateElementHandler(new ElementHandlerFactoryContext(Renderer, _closestPhysicalParent));
+
+ //if (_targetComponent is NativeControlComponentBase componentInstance)
+ //{
+ // componentInstance.SetElementReference(elementHandler);
+ //}
+
+ if (siblingIndex != 0)
+ {
+ // With the current design, we should be able to ignore sibling indices for elements,
+ // so bail out if that's not the case
+ throw new NotSupportedException($"Currently we assume all adapter elements render exactly zero or one elements. Found an element with sibling index {siblingIndex}");
+ }
+
+ // TODO: Consider in the future calling a new API to check if the elementHandler represents a native UI component:
+ // if (Renderer.ElementManager.IsNativeElement(elementHandler)) { add to UI tree }
+ // else { do something with non-native element, e.g. notify parent to handle it }
+
+ // For the location in the physical UI tree, find the last preceding-sibling adapter that has
+ // a physical descendant (if any). If there is one, we physically insert after that one. If not,
+ // we'll insert as the first child of the closest physical parent.
+ if (!Renderer.ElementManager.IsParented(elementHandler))
+ {
+ var elementIndex = GetIndexForElement();
+ Renderer.ElementManager.AddChildElement(_closestPhysicalParent, elementHandler, elementIndex);
+ }
+ _targetElement = elementHandler;
+
+ var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
+ for (var descendantIndex = frameIndex + 1; descendantIndex < endIndexExcl; descendantIndex++)
+ {
+ var candidateFrame = frames[descendantIndex];
+ if (candidateFrame.FrameType == RenderTreeFrameType.Attribute)
+ {
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Finds the sibling index to insert this adapter's element into. It walks up Parent adapters to find
+ /// an earlier sibling that has a native element, and uses that native element's physical index to determine
+ /// the location of the new element.
+ ///
+ /// * Adapter0
+ /// * Adapter1
+ /// * Adapter2
+ /// * Adapter3 (native)
+ /// * Adapter3.0 (searchOrder=2)
+ /// * Adapter3.0.0 (searchOrder=3)
+ /// * Adapter3.0.1 (native) (searchOrder=4) <-- This is the nearest earlier sibling that has a physical element)
+ /// * Adapter3.0.2
+ /// * Adapter3.1 (searchOrder=1)
+ /// * Adapter3.1.0 (searchOrder=0)
+ /// * Adapter3.1.1 (native) <-- Current adapter
+ /// * Adapter3.1.2
+ /// * Adapter3.2
+ /// * Adapter4
+ ///
+ ///
+ /// The index at which the native element should be inserted into within the parent. It returns -1 as a failure mode.
+ private int GetIndexForElement()
+ {
+ var childAdapter = this;
+ var parentAdapter = Parent;
+ while (parentAdapter != null)
+ {
+ // Walk previous siblings of this level and deep-scan them for native elements
+ var matchedEarlierSibling = GetEarlierSiblingMatch(parentAdapter, childAdapter);
+ if (matchedEarlierSibling != null)
+ {
+ if (!Renderer.ElementManager.IsParentOfChild(_closestPhysicalParent, matchedEarlierSibling._targetElement))
+ {
+ Debug.Fail($"Expected that the item found ({matchedEarlierSibling.DebugName}) with target element ({matchedEarlierSibling._targetElement.GetType().FullName}) should necessarily be an immediate child of the closest native parent ({_closestPhysicalParent.GetType().FullName}), but it wasn't...");
+ }
+
+ // If a native element was found somewhere within this sibling, the index for the new element
+ // will be 1 greater than its native index.
+ return Renderer.ElementManager.GetPhysicalSiblingIndex(matchedEarlierSibling._targetElement) + 1;
+ }
+
+ // If this level has a native element and all its relevant children have been scanned, then there's
+ // no previous sibling, so the new element to be added will be its earliest child (index=0). (There
+ // might be *later* siblings, but they are not relevant to this search.)
+ if (parentAdapter._targetElement != null)
+ {
+ Debug.Assert(parentAdapter._targetElement == _closestPhysicalParent, $"Expected that nearest parent ({parentAdapter.DebugName}) with native element ({parentAdapter._targetElement.GetType().FullName}) would have the closest physical parent ({_closestPhysicalParent.GetType().FullName}).");
+ return 0;
+ }
+
+ // If we haven't found a previous sibling with a native element or reached a native container, keep
+ // walking up the parent tree...
+ childAdapter = parentAdapter;
+ parentAdapter = parentAdapter.Parent;
+ }
+ Debug.Fail($"Expected to find a parent with a native element but found none.");
+ return -1;
+ }
+
+ private static NativeComponentAdapter GetEarlierSiblingMatch(NativeComponentAdapter parentAdapter, NativeComponentAdapter childAdapter)
+ {
+ var indexOfParentsChildAdapter = parentAdapter.Children.IndexOf(childAdapter);
+
+ for (var i = indexOfParentsChildAdapter - 1; i >= 0; i--)
+ {
+ var sibling = parentAdapter.Children[i];
+ if (sibling._targetElement is INonChildContainerElement)
+ {
+ continue;
+ }
+
+ // Deep scan this sibling adapter to find its latest and highest native element
+ var siblingWithNativeElement = sibling.GetLastDescendantWithPhysicalElement();
+ if (siblingWithNativeElement != null)
+ {
+ return siblingWithNativeElement;
+ }
+ }
+
+ // No preceding sibling has any native elements
+ return null;
+ }
+
+ private NativeComponentAdapter GetLastDescendantWithPhysicalElement()
+ {
+ if (_targetElement is INonChildContainerElement)
+ {
+ return null;
+ }
+ if (_targetElement != null)
+ {
+ // If this adapter has a target element, then this is the droid we're looking for. It can't be
+ // any children of this target element because they can't be children of this element's parent.
+ return this;
+ }
+
+ for (var i = Children.Count - 1; i >= 0; i--)
+ {
+ var child = Children[i];
+ var physicalDescendant = child.GetLastDescendantWithPhysicalElement();
+ if (physicalDescendant != null)
+ {
+ return physicalDescendant;
+ }
+ }
+
+ return null;
+ }
+
+ 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 static 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, NativeComponentAdapter 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);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_targetElement is IDisposable disposableTargetElement)
+ {
+ disposableTargetElement.Dispose();
+ }
+ }
+ }
+}
diff --git a/CPF.Razor/Core/NativeComponentRenderer.cs b/CPF.Razor/Core/NativeComponentRenderer.cs
new file mode 100644
index 0000000..1cb7beb
--- /dev/null
+++ b/CPF.Razor/Core/NativeComponentRenderer.cs
@@ -0,0 +1,134 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace CPF.Razor
+{
+ public abstract class NativeComponentRenderer : Renderer
+ {
+ private readonly Dictionary _componentIdToAdapter = new Dictionary();
+ private ElementManager _elementManager;
+ private readonly Dictionary> _eventRegistrations = new Dictionary>();
+
+
+ public NativeComponentRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
+ : base(serviceProvider, loggerFactory)
+ {
+ }
+
+ protected abstract ElementManager CreateNativeControlManager();
+
+ internal ElementManager ElementManager
+ {
+ get
+ {
+ return _elementManager ?? (_elementManager = CreateNativeControlManager());
+ }
+ }
+
+ public override Dispatcher Dispatcher { get; }
+ = Dispatcher.CreateDefault();
+
+ ///
+ /// Creates a component of type and adds it as a child of .
+ ///
+ ///
+ ///
+ ///
+ public async Task AddComponent(IElementHandler parent) where TComponent : IComponent
+ {
+ await AddComponent(typeof(TComponent), parent).ConfigureAwait(false);
+ }
+
+ ///
+ /// Creates a component of type and adds it as a child of .
+ ///
+ ///
+ ///
+ ///
+ public async Task AddComponent(Type componentType, IElementHandler parent)
+ {
+ await Dispatcher.InvokeAsync(async () =>
+ {
+ var component = InstantiateComponent(componentType);
+ var componentId = AssignRootComponentId(component);
+
+ var rootAdapter = new NativeComponentAdapter(this, closestPhysicalParent: parent, knownTargetElement: parent)
+ {
+ Name = $"RootAdapter attached to {parent.GetType().FullName}",
+ };
+
+ _componentIdToAdapter[componentId] = rootAdapter;
+
+ await RenderRootComponentAsync(componentId).ConfigureAwait(false);
+ }).ConfigureAwait(false);
+ }
+
+ 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);
+ }
+
+ var numDisposedComponents = renderBatch.DisposedComponentIDs.Count;
+ for (var i = 0; i < numDisposedComponents; i++)
+ {
+ var disposedComponentId = renderBatch.DisposedComponentIDs.Array[i];
+ if (_componentIdToAdapter.TryGetValue(disposedComponentId, out var adapter))
+ {
+ _componentIdToAdapter.Remove(disposedComponentId);
+ (adapter as IDisposable)?.Dispose();
+ }
+ }
+
+ var numDisposeEventHandlers = renderBatch.DisposedEventHandlerIDs.Count;
+ if (numDisposeEventHandlers != 0)
+ {
+ for (var i = 0; i < numDisposeEventHandlers; i++)
+ {
+ DisposeEvent(renderBatch.DisposedEventHandlerIDs.Array[i]);
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public void RegisterEvent(ulong eventHandlerId, Action unregisterCallback)
+ {
+ if (eventHandlerId == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(eventHandlerId), "Event handler ID must not be 0.");
+ }
+ if (unregisterCallback == null)
+ {
+ throw new ArgumentNullException(nameof(unregisterCallback));
+ }
+ _eventRegistrations.Add(eventHandlerId, unregisterCallback);
+ }
+
+ private void DisposeEvent(ulong eventHandlerId)
+ {
+ if (!_eventRegistrations.TryGetValue(eventHandlerId, out var unregisterCallback))
+ {
+ throw new InvalidOperationException($"Attempting to dispose unknown event handler id '{eventHandlerId}'.");
+ }
+ unregisterCallback(eventHandlerId);
+ }
+
+ internal NativeComponentAdapter CreateAdapterForChildComponent(IElementHandler physicalParent, int componentId)
+ {
+ var result = new NativeComponentAdapter(this, physicalParent);
+ _componentIdToAdapter[componentId] = result;
+ return result;
+ }
+ }
+}
diff --git a/CPF.Razor/Core/NativeControlComponentBase.cs b/CPF.Razor/Core/NativeControlComponentBase.cs
new file mode 100644
index 0000000..79f0ac9
--- /dev/null
+++ b/CPF.Razor/Core/NativeControlComponentBase.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Rendering;
+using System;
+
+namespace CPF.Razor
+{
+ public abstract class NativeControlComponentBase : ComponentBase, ICpfElementHandler where T : UIElement, new()
+ {
+ public IElementHandler ElementHandler { get; private set; }
+
+ UIElement ICpfElementHandler.Element => Element;
+
+ T element;
+ public T Element
+ {
+ get
+ {
+ if (element == null)
+ {
+ element = CreateElement();
+ }
+ return element;
+ }
+ }
+
+ public object TargetElement => Element;
+
+ //public void SetElementReference(IElementHandler elementHandler)
+ //{
+ // ElementHandler = elementHandler ?? throw new ArgumentNullException(nameof(elementHandler));
+ //}
+
+ protected override void BuildRenderTree(RenderTreeBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.OpenElement(0, GetType().FullName);
+ RenderAttributes(new AttributesBuilder(builder));
+
+ var childContent = GetChildContent();
+ if (childContent != null)
+ {
+ builder.AddContent(2, childContent);
+ }
+
+ builder.CloseElement();
+ }
+
+ protected virtual void RenderAttributes(AttributesBuilder builder)
+ {
+ }
+
+ protected virtual RenderFragment GetChildContent() => null;
+
+ public abstract void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName);
+
+ protected virtual T CreateElement()
+ {
+ return new T();
+ }
+ }
+}
diff --git a/CPF.Razor/Core/ServiceCollectionAdditionalServicesExtensions.cs b/CPF.Razor/Core/ServiceCollectionAdditionalServicesExtensions.cs
new file mode 100644
index 0000000..63c0075
--- /dev/null
+++ b/CPF.Razor/Core/ServiceCollectionAdditionalServicesExtensions.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static class ServiceCollectionAdditionalServicesExtensions
+ {
+ ///
+ /// Copies service descriptors from one service collection to another.
+ ///
+ /// The destination service collection to which the additional services will be added.
+ /// The list of additional services to add.
+ ///
+ public static IServiceCollection AddAdditionalServices(this IServiceCollection services, IServiceCollection additionalServices)
+ {
+ if (services is null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (additionalServices is null)
+ {
+ throw new ArgumentNullException(nameof(additionalServices));
+ }
+
+ foreach (var additionalService in additionalServices)
+ {
+ services.Add(additionalService);
+ }
+ return services;
+ }
+ }
+}
diff --git a/CPF.Razor/Core/TextSpanContainer.cs b/CPF.Razor/Core/TextSpanContainer.cs
new file mode 100644
index 0000000..bbb31ec
--- /dev/null
+++ b/CPF.Razor/Core/TextSpanContainer.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+namespace CPF.Razor
+{
+ ///
+ /// Helper class for types that accept inline text spans. This type collects text spans
+ /// and returns the string represented by the contained text spans.
+ ///
+ public class TextSpanContainer
+ {
+ private readonly List _textSpans = new List();
+
+ public TextSpanContainer(bool trimWhitespace = true)
+ {
+ TrimWhitespace = trimWhitespace;
+ }
+
+ public bool TrimWhitespace { get; }
+
+ ///
+ /// Updates the text spans with the new text at the new index and returns the new
+ /// string represented by the contained text spans.
+ ///
+ ///
+ ///
+ ///
+ public string GetUpdatedText(int index, string text)
+ {
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+
+ if (index >= _textSpans.Count)
+ {
+ // Expand the list to allow for the new text's index to exist
+ _textSpans.AddRange(new string[index - _textSpans.Count + 1]);
+ }
+ _textSpans[index] = text;
+
+ var allText = string.Join(string.Empty, _textSpans);
+ return TrimWhitespace
+ ? allText?.Trim()
+ : allText;
+ }
+ }
+}
diff --git a/CPF.Razor/CpfDispatcher.cs b/CPF.Razor/CpfDispatcher.cs
new file mode 100644
index 0000000..f5a3e6c
--- /dev/null
+++ b/CPF.Razor/CpfDispatcher.cs
@@ -0,0 +1,54 @@
+using Microsoft.AspNetCore.Components;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CPF.Razor
+{
+ public class CpfDispatcher : Dispatcher
+ {
+ public override bool CheckAccess()
+ {
+ return CPF.Threading.Dispatcher.MainThread.CheckAccess();
+ }
+
+ public override Task InvokeAsync(Action workItem)
+ {
+ return Task.Run(() =>
+ {
+ CPF.Threading.Dispatcher.MainThread.Invoke(workItem);
+ });
+ }
+
+ public override Task InvokeAsync(Func workItem)
+ {
+ return Task.Run(() =>
+ {
+ var task = Task.CompletedTask;
+ CPF.Threading.Dispatcher.MainThread.Invoke(() => { task = workItem(); });
+ return task;
+ });
+ }
+
+ public override Task InvokeAsync(Func workItem)
+ {
+ return Task.Run(() =>
+ {
+ TResult result = default;
+ CPF.Threading.Dispatcher.MainThread.Invoke(() => { result = workItem(); });
+ return result;
+ });
+ }
+
+ public override Task InvokeAsync(Func> workItem)
+ {
+ return Task.Run(() =>
+ {
+ TResult result = default;
+ CPF.Threading.Dispatcher.MainThread.Invoke(async () => { result = await workItem(); });
+ return result;
+ });
+ }
+ }
+}
diff --git a/CPF.Razor/CpfElementManager.cs b/CPF.Razor/CpfElementManager.cs
new file mode 100644
index 0000000..9dab84c
--- /dev/null
+++ b/CPF.Razor/CpfElementManager.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+//using Microsoft.MobileBlazorBindings.Core;
+using System;
+using System.Diagnostics;
+
+namespace CPF.Razor
+{
+ internal class CpfElementManager : ElementManager
+ {
+ protected override bool IsParented(ICpfElementHandler handler)
+ {
+ return handler.Element.Parent != null;
+ }
+
+ protected override void AddChildElement(
+ ICpfElementHandler parentHandler,
+ ICpfElementHandler childHandler,
+ int physicalSiblingIndex)
+ {
+ if (parentHandler.Element is CPF.Controls.Panel panel)
+ {
+ if (physicalSiblingIndex <= panel.Children.Count)
+ {
+ panel.Children.Insert(physicalSiblingIndex, childHandler.Element);
+ }
+ else
+ {
+ //Debug.WriteLine($"WARNING: {nameof(AddChildElement)} called with {nameof(physicalSiblingIndex)}={physicalSiblingIndex}, but parentControl.Controls.Count={parentHandler.Control.Controls.Count}");
+ panel.Children.Add(childHandler.Element);
+ }
+ }
+ else if (parentHandler.Element is CPF.Controls.Window win)
+ {
+ if (physicalSiblingIndex <= win.Children.Count)
+ {
+ win.Children.Insert(physicalSiblingIndex, childHandler.Element);
+ }
+ else
+ {
+ win.Children.Add(childHandler.Element);
+ }
+ }
+ else if (parentHandler.Element is CPF.Controls.ContentControl contentControl)
+ {
+ contentControl.Content = childHandler.Element;
+ }
+ else
+ {
+ Debug.Fail("未实现添加控件");
+ }
+ }
+
+ protected override int GetPhysicalSiblingIndex(
+ ICpfElementHandler handler)
+ {
+ return (handler.Element.Parent as CPF.Controls.Panel).Children.IndexOf(handler.Element);
+ }
+
+ protected override void RemoveElement(ICpfElementHandler handler)
+ {
+ if (handler.Element.Parent is CPF.Controls.Panel panel)
+ {
+ panel.Children.Remove(handler.Element);
+ }
+ else
+ {
+ Debug.Fail("未实现移除控件");
+ }
+ }
+
+ protected override bool IsParentOfChild(ICpfElementHandler parentHandler, ICpfElementHandler childHandler)
+ {
+ return childHandler.Element.Parent == parentHandler.Element;
+ }
+ }
+}
diff --git a/CPF.Razor/CpfExtensions.cs b/CPF.Razor/CpfExtensions.cs
new file mode 100644
index 0000000..c852625
--- /dev/null
+++ b/CPF.Razor/CpfExtensions.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System;
+
+namespace CPF.Razor
+{
+ public static class CpfExtensions
+ {
+ ///
+ /// Creates a component of type and adds it as a child of .
+ ///
+ ///
+ ///
+ ///
+ public static void AddComponent(this IHost host, CPF.UIElement parent) where TComponent : IComponent
+ {
+ if (host is null)
+ {
+ throw new ArgumentNullException(nameof(host));
+ }
+
+ if (parent is null)
+ {
+ throw new ArgumentNullException(nameof(parent));
+ }
+
+ var services = host.Services;
+ var renderer = new CpfRenderer(services, services.GetRequiredService());
+
+ //// TODO: This call is an async call, but is called as "fire-and-forget," which is not ideal.
+ //// We need to figure out how to get Xamarin.Forms to run this startup code asynchronously, which
+ //// is how this method should be called.
+ renderer.AddComponent(new ElementHandler(renderer, parent)).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/CPF.Razor/CpfHost.cs b/CPF.Razor/CpfHost.cs
new file mode 100644
index 0000000..8c32046
--- /dev/null
+++ b/CPF.Razor/CpfHost.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using System.IO;
+
+namespace CPF.Razor
+{
+ public static class CpfHost
+ {
+ public static IHostBuilder CreateDefaultBuilder()
+ {
+ // Inspired by Microsoft.Extensions.Hosting.Host, which can be seen here:
+ // https://github.com/dotnet/extensions/blob/master/src/Hosting/Hosting/src/Host.cs
+ // But slightly modified to work on all of Android, iOS, and UWP.
+
+ var builder = new HostBuilder();
+
+ builder.UseContentRoot(Directory.GetCurrentDirectory());
+
+ builder.ConfigureLogging((hostingContext, logging) =>
+ {
+ logging.AddConsole(configure => configure.DisableColors = true);
+ logging.AddDebug();
+ logging.AddEventSourceLogger();
+ })
+ .UseDefaultServiceProvider((context, options) =>
+ {
+ var isDevelopment = context.HostingEnvironment.IsDevelopment();
+ options.ValidateScopes = isDevelopment;
+ options.ValidateOnBuild = isDevelopment;
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/CPF.Razor/CpfRenderer.cs b/CPF.Razor/CpfRenderer.cs
new file mode 100644
index 0000000..6970d4b
--- /dev/null
+++ b/CPF.Razor/CpfRenderer.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.Logging;
+using System;
+using Microsoft.AspNetCore.Components;
+using System.Diagnostics;
+//using Microsoft.MobileBlazorBindings.Core;
+
+namespace CPF.Razor
+{
+ public class CpfRenderer : NativeComponentRenderer
+ {
+ public CpfRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
+ : base(serviceProvider, loggerFactory)
+ {
+ }
+
+ protected override void HandleException(Exception exception)
+ {
+ //MessageBox.Show(exception?.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ Debug.WriteLine(exception?.Message);
+ }
+
+ protected override ElementManager CreateNativeControlManager()
+ {
+ return new CpfElementManager();
+ }
+
+ public override Dispatcher Dispatcher => new CpfDispatcher();
+ }
+}
diff --git a/CPF.Razor/ElementHandler.cs b/CPF.Razor/ElementHandler.cs
new file mode 100644
index 0000000..f6975b0
--- /dev/null
+++ b/CPF.Razor/ElementHandler.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CPF.Razor
+{
+ public class ElementHandler : ICpfElementHandler
+ {
+ public ElementHandler(NativeComponentRenderer renderer, CPF.UIElement elementControl)
+ {
+ Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
+ Element = elementControl ?? throw new ArgumentNullException(nameof(elementControl));
+ }
+
+ //protected void RegisterEvent(string eventName, Action setId, Action clearId)
+ //{
+ // RegisteredEvents[eventName] = new EventRegistration(eventName, setId, clearId);
+ //}
+ //private Dictionary RegisteredEvents { get; } = new Dictionary();
+
+ public NativeComponentRenderer Renderer { get; }
+ public CPF.UIElement Element { get; }
+ public object TargetElement => Element;
+
+ public virtual void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
+ {
+ //switch (attributeName)
+ //{
+ // case nameof(XF.Element.AutomationId):
+ // ElementControl.AutomationId = (string)attributeValue;
+ // break;
+ // case nameof(XF.Element.ClassId):
+ // ElementControl.ClassId = (string)attributeValue;
+ // break;
+ // case nameof(XF.Element.StyleId):
+ // ElementControl.StyleId = (string)attributeValue;
+ // break;
+ // default:
+ // if (!TryRegisterEvent(attributeName, attributeEventHandlerId))
+ // {
+ // throw new NotImplementedException($"{GetType().FullName} doesn't recognize attribute '{attributeName}'");
+ // }
+ // break;
+ //}
+ var p = Element.GetPropertyMetadata(attributeName);
+ if (p != null)
+ {
+ Element.SetValue(attributeValue.ConvertTo(p.PropertyType), attributeName);
+ }
+ }
+
+ //private bool TryRegisterEvent(string eventName, ulong eventHandlerId)
+ //{
+ // if (RegisteredEvents.TryGetValue(eventName, out var eventRegistration))
+ // {
+ // Renderer.RegisterEvent(eventHandlerId, eventRegistration.ClearId);
+ // eventRegistration.SetId(eventHandlerId);
+
+ // return true;
+ // }
+ // return false;
+ //}
+ }
+}
diff --git a/CPF.Razor/ICpfElementHandler.cs b/CPF.Razor/ICpfElementHandler.cs
new file mode 100644
index 0000000..403728f
--- /dev/null
+++ b/CPF.Razor/ICpfElementHandler.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+//using Microsoft.MobileBlazorBindings.Core;
+
+namespace CPF.Razor
+{
+ public interface ICpfElementHandler : IElementHandler
+ {
+ UIElement Element { get; }
+ }
+}
diff --git a/ConsoleApp1.sln b/ConsoleApp1.sln
index 1901bb3..f0d8f66 100644
--- a/ConsoleApp1.sln
+++ b/ConsoleApp1.sln
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CPF.Demo", "CPF_Demo\CPF.De
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "蓝图重制版", "蓝图重制版\蓝图重制版.csproj", "{003E155A-8C40-41AF-A796-ED17E729E013}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CPF.Razor", "CPF.Razor\CPF.Razor.csproj", "{87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CpfRazorSample", "CpfRazorSample\CpfRazorSample.csproj", "{25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Private\SharedVSIX\SharedVSIX.projitems*{db53e8d7-dfb6-48eb-a7b6-d1cf762acb9b}*SharedItemsImports = 4
@@ -224,6 +228,18 @@ Global
{003E155A-8C40-41AF-A796-ED17E729E013}.Release|Any CPU.Build.0 = Release|Any CPU
{003E155A-8C40-41AF-A796-ED17E729E013}.类库d|Any CPU.ActiveCfg = 类库d|Any CPU
{003E155A-8C40-41AF-A796-ED17E729E013}.类库d|Any CPU.Build.0 = 类库d|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.类库d|Any CPU.ActiveCfg = Debug|Any CPU
+ {87E1ED0A-BFBF-4F5E-9FDF-5EAFE48DD719}.类库d|Any CPU.Build.0 = Debug|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.类库d|Any CPU.ActiveCfg = Debug|Any CPU
+ {25A4EE47-F5BD-4F1E-B143-3E3B50C5AC2A}.类库d|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -241,4 +257,7 @@ Global
{F34CFFEE-546F-490E-A76A-2792840B284D} = {2B729C46-7592-425A-87E9-D769A94881F7}
{DF526631-D060-47F2-AFD4-62C6CEA2FE9A} = {2B729C46-7592-425A-87E9-D769A94881F7}
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {16FB883C-167C-4E1A-B311-6D74452A3CD6}
+ EndGlobalSection
EndGlobal
diff --git a/CpfRazorSample/CpfRazorSample.csproj b/CpfRazorSample/CpfRazorSample.csproj
new file mode 100644
index 0000000..f7ea705
--- /dev/null
+++ b/CpfRazorSample/CpfRazorSample.csproj
@@ -0,0 +1,35 @@
+
+
+
+ WinExe
+ netcoreapp3.0
+
+
+ 9.0
+ 3.0
+
+
+
+ true
+
+ AnyCPU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CpfRazorSample/Program.cs b/CpfRazorSample/Program.cs
new file mode 100644
index 0000000..5786464
--- /dev/null
+++ b/CpfRazorSample/Program.cs
@@ -0,0 +1,34 @@
+using CPF.Platform;
+using CPF.Skia;
+using CPF.Windows;
+using Microsoft.Extensions.Hosting;
+using System;
+using CPF.Razor;
+
+namespace CpfRazorSample
+{
+ class Program
+ {
+ [STAThread]
+ static void Main(string[] args)
+ {
+ Application.Initialize(
+ (OperatingSystemType.Windows, new WindowsPlatform(), new SkiaDrawingFactory())
+ , (OperatingSystemType.OSX, new CPF.Mac.MacPlatform(), new SkiaDrawingFactory())//如果需要支持Mac才需要
+ , (OperatingSystemType.Linux, new CPF.Linux.LinuxPlatform(), new SkiaDrawingFactory())//如果需要支持Linux才需要
+ );
+
+ var host = Host.CreateDefaultBuilder()
+ .ConfigureServices((hostContext, services) =>
+ {
+ // Register app-specific services
+ //services.AddSingleton();
+ })
+ .Build();
+
+ var window = new CPF.Controls.Window();
+ host.AddComponent(window);
+ Application.Run(window);
+ }
+ }
+}
diff --git a/CpfRazorSample/Stylesheet1.css b/CpfRazorSample/Stylesheet1.css
new file mode 100644
index 0000000..d895ab6
--- /dev/null
+++ b/CpfRazorSample/Stylesheet1.css
@@ -0,0 +1,502 @@
+/*@font-face {
+ font-family: '微软雅黑';
+ src: url('res://ConsoleApp1/msyh.ttc');
+}*/ /*加载字体*/
+
+/** {
+ FontFamily: 微软雅黑;
+}*/
+
+@media windows {
+ * {
+ FontFamily: '微软雅黑'; /*不同系统的字体不同,自己根据情况改或者使用内嵌字体*/
+ }
+}
+
+@media osx {
+ * {
+ FontFamily: '苹方-简';
+ }
+}
+
+@media linux {
+ * {
+ FontFamily: '文泉驿正黑';
+ }
+}
+/*设置窗体标题栏背景颜色圆角*/
+/*#caption {
+ IsAntiAlias: true;
+ Background: #2c2c2c;
+ CornerRadius:5,5,0,0;
+ Height:30;
+}
+#frame {
+ CornerRadius: 5;
+ IsAntiAlias: true;
+}*/
+Button {
+ BorderFill: #DCDFE6;
+ IsAntiAlias: True;
+ CornerRadius: 4,4,4,4;
+ Background: #FFFFFF;
+}
+
+ Button[IsMouseOver=true] {
+ BorderFill: rgb(198,226,255);
+ Background: rgb(236,245,255);
+ Foreground: rgb(64,158,255);
+ }
+
+ Button[IsPressed=true] {
+ BorderFill: rgb(58,142,230);
+ }
+
+ Button.primary {
+ BorderFill: rgb(64,158,255);
+ CornerRadius: 4,4,4,4;
+ Background: rgb(64,158,255);
+ Foreground: #FFFFFF;
+ }
+
+ Button.primary[IsMouseOver=true] {
+ BorderFill: rgb(102,177,255);
+ Background: rgb(102,177,255);
+ Foreground: #FFFFFF;
+ }
+
+ Button.primary[IsPressed=true] {
+ BorderFill: rgb(58,142,230);
+ Background: rgb(58,142,230);
+ }
+
+ Button.success {
+ BorderFill: rgb(103,194,58);
+ CornerRadius: 4,4,4,4;
+ Background: rgb(103,194,58);
+ Foreground: #FFFFFF;
+ }
+
+ Button.success[IsMouseOver=true] {
+ BorderFill: rgb(133,206,97);
+ Background: rgb(133,206,97);
+ Foreground: #FFFFFF;
+ }
+
+ Button.success[IsPressed=true] {
+ BorderFill: rgb(93,175,52);
+ Background: rgb(93,175,52);
+ }
+
+ Button.danger {
+ BorderFill: rgb(245,108,108);
+ Background: rgb(245,108,108);
+ CornerRadius: 4,4,4,4;
+ Foreground: #FFFFFF;
+ }
+
+ Button.danger[IsMouseOver=true] {
+ BorderFill: rgb(247,137,137);
+ Background: rgb(247,137,137);
+ Foreground: #FFFFFF;
+ }
+
+ Button.danger[IsPressed=true] {
+ BorderFill: rgb(221,97,97);
+ Background: rgb(221,97,97);
+ }
+
+#dialogClose {
+ BorderFill: null;
+}
+
+TextBox, .textBox, DatePicker {
+ Background: #fff;
+ IsAntiAlias: true;
+ BorderFill: #DCDFE6;
+ CornerRadius: 4,4,4,4;
+ BorderStroke: 1;
+}
+
+ .groupPanel TextBox, DatePicker TextBox, .textBox TextBox {
+ BorderStroke: 0;
+ }
+
+ .textBox[IsKeyboardFocusWithin=true] {
+ BorderFill: #1E9FFF;
+ }
+
+.singleLine { /*单行文本框*/
+ AcceptsReturn: false;
+ HScrollBarVisibility: Hidden;
+ VScrollBarVisibility: Hidden;
+}
+
+ .singleLine #contentPresenter {
+ Padding: 3;
+ }
+
+.multiline { /*多行文本框*/
+}
+
+
+.slotLeft {
+ CornerRadius: 0,4,4,0;
+ BorderFill: #DCDFE6;
+ Background: #F5F7FA;
+ BorderThickness: 1,0,0,0;
+ BorderType: BorderThickness;
+ MarginTop: 0;
+ MarginBottom: 0;
+ MarginRight: 0;
+}
+
+RadioButton #radioButtonBorder {
+ StrokeFill: rgb(220,223,230);
+}
+
+RadioButton[IsChecked=true] #radioButtonBorder {
+ StrokeFill: #1E9FFF;
+ Fill: #1E9FFF;
+}
+
+RadioButton #optionMark {
+ StrokeFill: #1E9FFF;
+ Fill: #fff;
+}
+
+CheckBox #indeterminateMark {
+ Fill: #1E9FFF;
+}
+
+CheckBox #checkBoxBorder {
+ Background: #fff;
+ BorderFill: rgb(220,223,230);
+}
+
+CheckBox[IsChecked=true] #checkBoxBorder {
+ Background: #1E9FFF;
+ BorderFill: #1E9FFF;
+}
+
+CheckBox Polyline {
+ StrokeFill: #fff;
+}
+
+.radioGroup {
+ BorderType: BorderThickness;
+ BorderFill: rgb(220,223,230);
+ BorderThickness: 1,1,0,1;
+}
+
+ .radioGroup RadioButton {
+ BorderType: BorderThickness;
+ BorderFill: rgb(220,223,230);
+ BorderThickness: 0,0,1,0;
+ }
+
+ .radioGroup RadioButton #markPanel {
+ Visibility: Collapsed;
+ }
+
+ .radioGroup RadioButton TextBlock {
+ Margin: 5;
+ }
+
+ .radioGroup RadioButton[IsChecked=true] {
+ Background: rgb(64,158,255);
+ Foreground: #fff;
+ }
+
+.error {
+ Foreground: #f00;
+ Visibility: Collapsed;
+}
+
+ .error[DesignMode=true] {
+ Visibility: Visible;
+ }
+
+.twoLine[AttachedExtenstions.IsError=true] .error {
+ Visibility: Visible;
+}
+
+.twoLine[AttachedExtenstions.IsError=true] .textBox {
+ BorderFill: #f00;
+}
+
+ScrollBar {
+ Background: null;
+}
+
+ ScrollBar Thumb {
+ IsAntiAlias: true;
+ CornerRadius: 5;
+ }
+
+ ScrollBar[Orientation=Horizontal] {
+ Height: 12;
+ }
+
+ ScrollBar[Orientation=Vertical] {
+ Width: 12;
+ }
+
+ ScrollBar #PART_LineUpButton, ScrollBar #PART_LineDownButton {
+ Visibility: Collapsed;
+ }
+
+ComboBox {
+ Background: #fff;
+ IsAntiAlias: true;
+ BorderFill: #DCDFE6;
+ CornerRadius: 4,4,4,4;
+ BorderStroke: 1;
+}
+
+ ComboBox[IsKeyboardFocusWithin=true] {
+ BorderFill: #1E9FFF;
+ }
+
+#DropDownPanel TextBlock {
+ MarginLeft: 5;
+ MarginTop: 2;
+ MarginBottom: 2;
+}
+
+#dropDownBorder {
+ ShadowBlur: 2;
+ ShadowColor: rgba(0, 0, 0, 0.4);
+ BorderStroke: 0;
+}
+
+#DropDownPanel[IsMouseOver=false] ScrollBar {
+ Visibility: Collapsed;
+}
+
+#DropDownPanel ScrollBar[Orientation=Horizontal] {
+ Height: 10;
+}
+
+#DropDownPanel ScrollBar[Orientation=Vertical] {
+ Width: 10;
+}
+
+Slider {
+ IsAntiAlias: true;
+}
+
+ Slider Thumb {
+ IsAntiAlias: true;
+ Width: 16;
+ Height: 16;
+ CornerRadius: 7;
+ BorderFill: rgb(64,158,255);
+ BorderStroke: 2;
+ Background: #fff;
+ ZIndex: 1;
+ }
+
+
+ Slider Thumb[IsMouseOver=true] {
+ animation-name: sliderMouseOver;
+ animation-duration: 0.1s;
+ animation-iteration-count: 1;
+ animation-fill-mode: forwards;
+ }
+
+ Slider #TrackBackground {
+ CornerRadius: 2;
+ Background: rgb(228,231,237);
+ BorderStroke: 0;
+ }
+
+ Slider #decreaseRepeatButton {
+ Background: rgb(64,158,255);
+ CornerRadius: 2;
+ }
+
+ Slider[Orientation=Horizontal] #decreaseRepeatButton {
+ Height: 4;
+ MarginLeft: 5;
+ }
+
+ Slider[Orientation=Vertical] #decreaseRepeatButton {
+ Width: 4;
+ MarginBottom: 5;
+ }
+
+ProgressBar {
+ CornerRadius: 5;
+ IsAntiAlias: true;
+ BorderFill: null;
+ Background: rgb(235,238,245);
+}
+
+ ProgressBar #Indicator, ProgressBar #Animation {
+ CornerRadius: 5;
+ }
+
+NumericUpDown {
+ Background: #fff;
+ IsAntiAlias: true;
+ BorderFill: #DCDFE6;
+ CornerRadius: 4,4,4,4;
+ BorderStroke: 1;
+}
+
+ NumericUpDown RepeatButton {
+ Width: 20;
+ Background: rgb(245,247,250);
+ }
+
+ NumericUpDown #decreaseBtn {
+ CornerRadius: 4,0,0,4;
+ }
+
+ NumericUpDown #increaseBtn {
+ CornerRadius: 0,4,4,0;
+ }
+
+ NumericUpDown #textBoxBorder {
+ BorderFill: #DCDFE6;
+ }
+
+ NumericUpDown TextBox {
+ BorderStroke: 0;
+ }
+
+.widget {
+ IsAntiAlias: true;
+ BorderFill: #DCDFE6;
+ CornerRadius: 4,4,4,4;
+ BorderStroke: 1;
+}
+
+.widgetHead {
+ Background: linear-gradient(0 0,0 100%,#F7F7F7 0,#F0F0F0 1);
+ BorderType: BorderThickness;
+ BorderThickness: 0,0,0,1;
+ BorderFill: #DCDFE6;
+}
+
+DataGrid {
+ Foreground: #7a7a7a;
+}
+
+DataGridCellTemplate, DataGridRow, DataGrid, .DataGridCell {
+ BorderFill: rgb(235,238,245);
+}
+
+DataGridRow {
+ Height: 36;
+}
+
+ DataGridRow[IsMouseOver=true] {
+ Background: rgb(245,247,250);
+ }
+
+ DataGridRow[IsSelected=true] {
+ Background: rgb(245,247,250);
+ }
+
+DataGridColumnTemplate {
+ Height: 38;
+ FontSize: 15;
+ FontStyle: Bold;
+ Background: #fff;
+ BorderFill: rgb(235,238,245);
+}
+
+TabControl #headBorder {
+ Background: #fff;
+}
+
+TabItem > Border {
+ BorderThickness: 0,0,0,2;
+}
+
+TabItem[IsSelected=true] > Border {
+ BorderFill: #1E9FFF;
+}
+
+TabItem[IsSelected=true] {
+ Foreground: #1E9FFF;
+}
+
+TabControl[TabStripPlacement=Left] TabItem, TabControl[TabStripPlacement=Right] TabItem {
+ Width: 100%;
+ BorderType: BorderThickness;
+ BorderThickness: 0,0,0,1;
+ BorderFill: #e8e8e8;
+}
+
+ TabControl[TabStripPlacement=Left] TabItem TextBlock {
+ MarginRight: 0;
+ }
+
+ TabControl[TabStripPlacement=Left] TabItem > Border {
+ Width: 100%;
+ }
+
+TabControl[TabStripPlacement=Left] #headerPanel, TabControl[TabStripPlacement=Right] #headerPanel {
+ Width: 100;
+ Background: rgb(245,247,250);
+}
+
+.closeBtn[IsMouseOver=true] {
+ Fill: #171717;
+}
+
+.placeholder {
+ IsHitTestVisible: false;
+ Foreground: "192,196,204";
+ Visibility: Collapsed;
+}
+
+.textBox[AttachedExtenstions.IsEmpty=true] .placeholder {
+ Visibility: Visible;
+}
+
+.loginBox TextBox, .loginBox .placeholder {
+ FontSize: 16;
+}
+
+.loginBox CheckBox {
+ Foreground: #757575;
+}
+
+.searchBox Button {
+ CornerRadius: 0,4,4,0,
+}
+
+#MenuPop #menuPanel > Border, #MenuPop ContextMenu > Border {
+ Background: #fff;
+ ShadowColor: rgba(0, 0, 0, 0.4);
+}
+
+#MenuPop MenuItem[IsMouseOver=true] {
+ Background: #DCDFE6;
+}
+
+ListBoxItem {
+ Width: 100%;
+}
+
+#dialogClose, #dialogClose[IsMouseOver=true] {
+ Background: null;
+ BorderFill: null;
+}
+
+ #dialogClose[IsPressed=true] {
+ Background: null;
+ BorderFill: null;
+ }
+
+ #dialogClose Line {
+ StrokeFill: 218,218,218;
+ }
+
+ #dialogClose[IsMouseOver=true] Line {
+ StrokeFill: #fff;
+ }
diff --git a/CpfRazorSample/Test.razor b/CpfRazorSample/Test.razor
new file mode 100644
index 0000000..9483d06
--- /dev/null
+++ b/CpfRazorSample/Test.razor
@@ -0,0 +1,7 @@
+
+
+
+
+
+@**@
+@**@
\ No newline at end of file
diff --git a/CpfRazorSample/Window1.cs b/CpfRazorSample/Window1.cs
new file mode 100644
index 0000000..c668042
--- /dev/null
+++ b/CpfRazorSample/Window1.cs
@@ -0,0 +1,52 @@
+using CPF;
+using CPF.Animation;
+using CPF.Charts;
+using CPF.Controls;
+using CPF.Drawing;
+using CPF.Shapes;
+using CPF.Styling;
+using CPF.Svg;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace CpfRazorSample
+{
+ public class Window1 : Window
+ {
+ protected override void InitializeComponent()
+ {
+ LoadStyleFile("res://CpfRazorSample/Stylesheet1.css");//加载样式文件,文件需要设置为内嵌资源
+
+ Title = "标题";
+ Width = 500;
+ Height = 400;
+ Background = null;
+ Children.Add(new WindowFrame(this, new Panel
+ {
+ Width = "100%",
+ Height = "100%",
+ Children =
+ {
+ new Button{ Content="按钮" }
+ }
+ }));
+
+ if (!DesignMode)//设计模式下不执行,也可以用#if !DesignMode
+ {
+
+ }
+ }
+
+#if !DesignMode //用户代码写到这里,设计器下不执行,防止设计器出错
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+
+ }
+ //用户代码
+
+#endif
+ }
+}
diff --git a/CpfRazorSample/_Imports.razor b/CpfRazorSample/_Imports.razor
new file mode 100644
index 0000000..afab265
--- /dev/null
+++ b/CpfRazorSample/_Imports.razor
@@ -0,0 +1,3 @@
+@using CPF.Razor.Controls
+@using CpfRazorSample
+@*@using CPF.Controls*@
\ No newline at end of file