Razor测试
This commit is contained in:
parent
bfa77fe9c3
commit
9423dc2d1d
37
CPF.Razor/CPF.Razor.csproj
Normal file
37
CPF.Razor/CPF.Razor.csproj
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>9.0</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Core\ElementHandlerFactory.cs" />
|
||||||
|
<Compile Remove="Core\ElementHandlerFactoryContext.cs" />
|
||||||
|
<Compile Remove="Core\ElementHandlerRegistry.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--<ItemGroup>
|
||||||
|
<Compile Remove="Core\**" />
|
||||||
|
<EmbeddedResource Remove="Core\**" />
|
||||||
|
<None Remove="Core\**" />
|
||||||
|
</ItemGroup>-->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudioEng.MicroBuild.Core" Version="0.4.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CPF\CPF.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
40
CPF.Razor/Controls/Element.cs
Normal file
40
CPF.Razor/Controls/Element.cs
Normal file
@ -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<T> : NativeControlComponentBase<T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
CPF.Razor/Controls/Panel.cs
Normal file
50
CPF.Razor/Controls/Panel.cs
Normal file
@ -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<CPF.Controls.Panel>
|
||||||
|
{
|
||||||
|
//static Panel()
|
||||||
|
//{
|
||||||
|
// ElementHandlerRegistry.RegisterElementHandler<Panel>();
|
||||||
|
//}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
CPF.Razor/Controls/TestElement.cs
Normal file
153
CPF.Razor/Controls/TestElement.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
CPF.Razor/Core/AttributesBuilder.cs
Normal file
44
CPF.Razor/Core/AttributesBuilder.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
CPF.Razor/Core/ElementHandlerFactory.cs
Normal file
22
CPF.Razor/Core/ElementHandlerFactory.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CPF.BlazorBindings
|
||||||
|
{
|
||||||
|
internal class ElementHandlerFactory
|
||||||
|
{
|
||||||
|
private readonly Func<NativeComponentRenderer, IElementHandler, IElementHandler> _callback;
|
||||||
|
|
||||||
|
public ElementHandlerFactory(Func<NativeComponentRenderer, IElementHandler, IElementHandler> callback)
|
||||||
|
{
|
||||||
|
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IElementHandler CreateElementHandler(ElementHandlerFactoryContext context)
|
||||||
|
{
|
||||||
|
return _callback(context.Renderer, context.ParentHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
CPF.Razor/Core/ElementHandlerFactoryContext.cs
Normal file
20
CPF.Razor/Core/ElementHandlerFactoryContext.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
31
CPF.Razor/Core/ElementHandlerRegistry.cs
Normal file
31
CPF.Razor/Core/ElementHandlerRegistry.cs
Normal file
@ -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<string, ElementHandlerFactory> ElementHandlers { get; }
|
||||||
|
// = new Dictionary<string, ElementHandlerFactory>();
|
||||||
|
|
||||||
|
//public static void RegisterElementHandler<TComponent>(
|
||||||
|
// Func<NativeComponentRenderer, IElementHandler, IElementHandler> factory) where TComponent : NativeControlComponentBase
|
||||||
|
//{
|
||||||
|
// ElementHandlers.Add(typeof(TComponent).FullName, new ElementHandlerFactory(factory));
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public static void RegisterElementHandler<TComponent>(
|
||||||
|
// Func<NativeComponentRenderer, IElementHandler> factory) where TComponent : NativeControlComponentBase
|
||||||
|
//{
|
||||||
|
// ElementHandlers.Add(typeof(TComponent).FullName, new ElementHandlerFactory((renderer, _) => factory(renderer)));
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public static void RegisterElementHandler<TComponent, TControlHandler>() where TComponent : NativeControlComponentBase where TControlHandler : class, IElementHandler, new()
|
||||||
|
//{
|
||||||
|
// RegisterElementHandler<TComponent>((_, __) => new TControlHandler());
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
20
CPF.Razor/Core/ElementManager.cs
Normal file
20
CPF.Razor/Core/ElementManager.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
54
CPF.Razor/Core/ElementManagerOfElementType.cs
Normal file
54
CPF.Razor/Core/ElementManagerOfElementType.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility intermediate class to make it easier to strongly-type a derived <see cref="ElementManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TElementType"></typeparam>
|
||||||
|
public abstract class ElementManager<TElementType> : 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);
|
||||||
|
}
|
||||||
|
}
|
27
CPF.Razor/Core/IElementHandler.cs
Normal file
27
CPF.Razor/Core/IElementHandler.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a container for native element.
|
||||||
|
/// </summary>
|
||||||
|
public interface IElementHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets an attribute named <paramref name="attributeName"/> on the <see cref="TargetElement"/> represented by
|
||||||
|
/// this handler to value <paramref name="attributeValue"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="attributeEventHandlerId"></param>
|
||||||
|
/// <param name="attributeName"></param>
|
||||||
|
/// <param name="attributeValue"></param>
|
||||||
|
/// <param name="attributeEventUpdatesAttributeName"></param>
|
||||||
|
void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
object TargetElement { get; }
|
||||||
|
}
|
||||||
|
}
|
18
CPF.Razor/Core/IHandleChildContentText.cs
Normal file
18
CPF.Razor/Core/IHandleChildContentText.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a mechanism for an <see cref="IElementHandler"/> to accept inline text.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHandleChildContentText
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called to process inline text found in a component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">the index of the string within a group of text strings.</param>
|
||||||
|
/// <param name="text">The text to handle. This text may contain whitespace at the start and end of the string.</param>
|
||||||
|
void HandleText(int index, string text);
|
||||||
|
}
|
||||||
|
}
|
16
CPF.Razor/Core/INonChildContainerElement.cs
Normal file
16
CPF.Razor/Core/INonChildContainerElement.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning disable CA1040 // Avoid empty interfaces
|
||||||
|
public interface INonChildContainerElement
|
||||||
|
#pragma warning restore CA1040 // Avoid empty interfaces
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
465
CPF.Razor/Core/NativeComponentAdapter.cs
Normal file
465
CPF.Razor/Core/NativeComponentAdapter.cs
Normal file
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a "shadow" item that Blazor uses to map changes into the live native UI tree.
|
||||||
|
/// </summary>
|
||||||
|
[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<NativeComponentAdapter> Children { get; } = new List<NativeComponentAdapter>();
|
||||||
|
|
||||||
|
private readonly IElementHandler _closestPhysicalParent;
|
||||||
|
private IElementHandler _targetElement;
|
||||||
|
private IComponent _targetComponent;
|
||||||
|
|
||||||
|
public NativeComponentRenderer Renderer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for debugging purposes.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; internal set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{nameof(NativeComponentAdapter)}: Name={Name ?? "<?>"}, Target={_targetElement?.GetType().Name ?? "<None>"}, #Children={Children.Count}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyEdits(int componentId, ArrayBuilderSegment<RenderTreeEdit> edits, ArrayRange<RenderTreeFrame> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// <code>
|
||||||
|
/// * 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
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The index at which the native element should be inserted into within the parent. It returns -1 as a failure mode.</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
CPF.Razor/Core/NativeComponentRenderer.cs
Normal file
134
CPF.Razor/Core/NativeComponentRenderer.cs
Normal file
@ -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<int, NativeComponentAdapter> _componentIdToAdapter = new Dictionary<int, NativeComponentAdapter>();
|
||||||
|
private ElementManager _elementManager;
|
||||||
|
private readonly Dictionary<ulong, Action<ulong>> _eventRegistrations = new Dictionary<ulong, Action<ulong>>();
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a component of type <typeparamref name="TComponent"/> and adds it as a child of <paramref name="parent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TComponent"></typeparam>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task AddComponent<TComponent>(IElementHandler parent) where TComponent : IComponent
|
||||||
|
{
|
||||||
|
await AddComponent(typeof(TComponent), parent).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a component of type <paramref name="componentType"/> and adds it as a child of <paramref name="parent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="componentType"></param>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task AddComponent(Type componentType, 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<ulong> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
CPF.Razor/Core/NativeControlComponentBase.cs
Normal file
68
CPF.Razor/Core/NativeControlComponentBase.cs
Normal file
@ -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<T> : 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public static class ServiceCollectionAdditionalServicesExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Copies service descriptors from one service collection to another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The destination service collection to which the additional services will be added.</param>
|
||||||
|
/// <param name="additionalServices">The list of additional services to add.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
CPF.Razor/Core/TextSpanContainer.cs
Normal file
51
CPF.Razor/Core/TextSpanContainer.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CPF.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for types that accept inline text spans. This type collects text spans
|
||||||
|
/// and returns the string represented by the contained text spans.
|
||||||
|
/// </summary>
|
||||||
|
public class TextSpanContainer
|
||||||
|
{
|
||||||
|
private readonly List<string> _textSpans = new List<string>();
|
||||||
|
|
||||||
|
public TextSpanContainer(bool trimWhitespace = true)
|
||||||
|
{
|
||||||
|
TrimWhitespace = trimWhitespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrimWhitespace { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the text spans with the new text at the new index and returns the new
|
||||||
|
/// string represented by the contained text spans.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
CPF.Razor/CpfDispatcher.cs
Normal file
54
CPF.Razor/CpfDispatcher.cs
Normal file
@ -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<Task> workItem)
|
||||||
|
{
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
var task = Task.CompletedTask;
|
||||||
|
CPF.Threading.Dispatcher.MainThread.Invoke(() => { task = workItem(); });
|
||||||
|
return task;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
|
||||||
|
{
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
TResult result = default;
|
||||||
|
CPF.Threading.Dispatcher.MainThread.Invoke(() => { result = workItem(); });
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
|
||||||
|
{
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
TResult result = default;
|
||||||
|
CPF.Threading.Dispatcher.MainThread.Invoke(async () => { result = await workItem(); });
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
CPF.Razor/CpfElementManager.cs
Normal file
78
CPF.Razor/CpfElementManager.cs
Normal file
@ -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<ICpfElementHandler>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
CPF.Razor/CpfExtensions.cs
Normal file
41
CPF.Razor/CpfExtensions.cs
Normal file
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a component of type <typeparamref name="TComponent"/> and adds it as a child of <paramref name="parent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TComponent"></typeparam>
|
||||||
|
/// <param name="host"></param>
|
||||||
|
/// <param name="parent"></param>
|
||||||
|
public static void AddComponent<TComponent>(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<ILoggerFactory>());
|
||||||
|
|
||||||
|
//// 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<TComponent>(new ElementHandler(renderer, parent)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
CPF.Razor/CpfHost.cs
Normal file
38
CPF.Razor/CpfHost.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
CPF.Razor/CpfRenderer.cs
Normal file
29
CPF.Razor/CpfRenderer.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
64
CPF.Razor/ElementHandler.cs
Normal file
64
CPF.Razor/ElementHandler.cs
Normal file
@ -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<ulong> setId, Action<ulong> clearId)
|
||||||
|
//{
|
||||||
|
// RegisteredEvents[eventName] = new EventRegistration(eventName, setId, clearId);
|
||||||
|
//}
|
||||||
|
//private Dictionary<string, EventRegistration> RegisteredEvents { get; } = new Dictionary<string, EventRegistration>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
12
CPF.Razor/ICpfElementHandler.cs
Normal file
12
CPF.Razor/ICpfElementHandler.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CPF.Demo", "CPF_Demo\CPF.De
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "蓝图重制版", "蓝图重制版\蓝图重制版.csproj", "{003E155A-8C40-41AF-A796-ED17E729E013}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "蓝图重制版", "蓝图重制版\蓝图重制版.csproj", "{003E155A-8C40-41AF-A796-ED17E729E013}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||||
Private\SharedVSIX\SharedVSIX.projitems*{db53e8d7-dfb6-48eb-a7b6-d1cf762acb9b}*SharedItemsImports = 4
|
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}.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.ActiveCfg = 类库d|Any CPU
|
||||||
{003E155A-8C40-41AF-A796-ED17E729E013}.类库d|Any CPU.Build.0 = 类库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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -241,4 +257,7 @@ Global
|
|||||||
{F34CFFEE-546F-490E-A76A-2792840B284D} = {2B729C46-7592-425A-87E9-D769A94881F7}
|
{F34CFFEE-546F-490E-A76A-2792840B284D} = {2B729C46-7592-425A-87E9-D769A94881F7}
|
||||||
{DF526631-D060-47F2-AFD4-62C6CEA2FE9A} = {2B729C46-7592-425A-87E9-D769A94881F7}
|
{DF526631-D060-47F2-AFD4-62C6CEA2FE9A} = {2B729C46-7592-425A-87E9-D769A94881F7}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {16FB883C-167C-4E1A-B311-6D74452A3CD6}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
35
CpfRazorSample/CpfRazorSample.csproj
Normal file
35
CpfRazorSample/CpfRazorSample.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<ApplicationIcon />
|
||||||
|
<StartupObject />
|
||||||
|
<LangVersion>9.0</LangVersion>
|
||||||
|
<RazorLangVersion>3.0</RazorLangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefineConstants />
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Stylesheet1.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Stylesheet1.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CPF.Linux\CPF.Linux.csproj" />
|
||||||
|
<ProjectReference Include="..\CPF.Mac\CPF.Mac.csproj" />
|
||||||
|
<ProjectReference Include="..\CPF.Razor\CPF.Razor.csproj" />
|
||||||
|
<ProjectReference Include="..\CPF.Skia\CPF.Skia.csproj" />
|
||||||
|
<ProjectReference Include="..\CPF.Windows\CPF.Windows.csproj" />
|
||||||
|
<ProjectReference Include="..\CPF\CPF.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
34
CpfRazorSample/Program.cs
Normal file
34
CpfRazorSample/Program.cs
Normal file
@ -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<AppState>();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var window = new CPF.Controls.Window();
|
||||||
|
host.AddComponent<Test>(window);
|
||||||
|
Application.Run(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
502
CpfRazorSample/Stylesheet1.css
Normal file
502
CpfRazorSample/Stylesheet1.css
Normal file
@ -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;
|
||||||
|
}
|
7
CpfRazorSample/Test.razor
Normal file
7
CpfRazorSample/Test.razor
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
<Panel Background="#f00" Width="100" Height="100">
|
||||||
|
<Panel Background="#00f" Width="20" Height="30"></Panel>
|
||||||
|
<Panel Background="#0ff" Width="20" Height="30" MarginLeft="0"></Panel>
|
||||||
|
</Panel>
|
||||||
|
@*<Button></Button>*@
|
||||||
|
@*<TestElement Test="123"></TestElement>*@
|
52
CpfRazorSample/Window1.cs
Normal file
52
CpfRazorSample/Window1.cs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
3
CpfRazorSample/_Imports.razor
Normal file
3
CpfRazorSample/_Imports.razor
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@using CPF.Razor.Controls
|
||||||
|
@using CpfRazorSample
|
||||||
|
@*@using CPF.Controls*@
|
Loading…
x
Reference in New Issue
Block a user