diff --git a/CPF.Razor/Controls/Button.cs b/CPF.Razor/Controls/Button.cs new file mode 100644 index 0000000..68aac6d --- /dev/null +++ b/CPF.Razor/Controls/Button.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; +//using Microsoft.MobileBlazorBindings.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class Button + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/CheckBox.cs b/CPF.Razor/Controls/CheckBox.cs new file mode 100644 index 0000000..5779cfa --- /dev/null +++ b/CPF.Razor/Controls/CheckBox.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; +//using Microsoft.MobileBlazorBindings.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class CheckBox + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/DockPanel.cs b/CPF.Razor/Controls/DockPanel.cs new file mode 100644 index 0000000..85db5ee --- /dev/null +++ b/CPF.Razor/Controls/DockPanel.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; +//using Microsoft.MobileBlazorBindings.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class DockPanel + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/Element.cs b/CPF.Razor/Controls/Element.cs index 5682382..2fc4c3b 100644 --- a/CPF.Razor/Controls/Element.cs +++ b/CPF.Razor/Controls/Element.cs @@ -93,6 +93,13 @@ namespace CPF.Razor.Controls return r; } + public void HandleText(int index, string text) + { + if (Element is CPF.Controls.ContentControl control) + { + control.Content = text; + } + } ////只要属性和事件自动生成就行 //[Parameter] public string Name { get; set; } diff --git a/CPF.Razor/Controls/Grid.cs b/CPF.Razor/Controls/Grid.cs new file mode 100644 index 0000000..a21e2f2 --- /dev/null +++ b/CPF.Razor/Controls/Grid.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class Grid + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/Panel.cs b/CPF.Razor/Controls/Panel.cs index 62d6351..7b2294e 100644 --- a/CPF.Razor/Controls/Panel.cs +++ b/CPF.Razor/Controls/Panel.cs @@ -10,9 +10,7 @@ namespace CPF.Razor.Controls { //[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 RenderFragment GetChildContent() => ChildContent; + protected override RenderFragment GetChild() => ChildContent; } } diff --git a/CPF.Razor/Controls/ResponsivePanel.cs b/CPF.Razor/Controls/ResponsivePanel.cs new file mode 100644 index 0000000..7ef6b7b --- /dev/null +++ b/CPF.Razor/Controls/ResponsivePanel.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class ResponsivePanel + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/StackPanel.cs b/CPF.Razor/Controls/StackPanel.cs index beec2e1..dc5d142 100644 --- a/CPF.Razor/Controls/StackPanel.cs +++ b/CPF.Razor/Controls/StackPanel.cs @@ -7,9 +7,7 @@ namespace CPF.Razor.Controls { public partial class StackPanel { -#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 RenderFragment GetChildContent() => ChildContent; + protected override RenderFragment GetChild() => ChildContent; } } diff --git a/CPF.Razor/Controls/WindowFrame.cs b/CPF.Razor/Controls/WindowFrame.cs new file mode 100644 index 0000000..8906ff8 --- /dev/null +++ b/CPF.Razor/Controls/WindowFrame.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class WindowFrame + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/WrapPanel.cs b/CPF.Razor/Controls/WrapPanel.cs new file mode 100644 index 0000000..6980be7 --- /dev/null +++ b/CPF.Razor/Controls/WrapPanel.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; +//using Microsoft.MobileBlazorBindings.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CPF.Razor.Controls +{ + public partial class WrapPanel + { + [Parameter] public RenderFragment ChildContent { get; set; } + protected override RenderFragment GetChild() => ChildContent; + } +} diff --git a/CPF.Razor/Controls/generated/Button.generated.cs b/CPF.Razor/Controls/generated/Button.generated.cs index 49ab720..0a7462b 100644 --- a/CPF.Razor/Controls/generated/Button.generated.cs +++ b/CPF.Razor/Controls/generated/Button.generated.cs @@ -13,7 +13,7 @@ namespace CPF.Razor.Controls /// /// 表示 Windows 按钮控件,该按钮对 Click 事件做出反应。 /// - public partial class Button : Element + public partial class Button : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/CheckBox.generated.cs b/CPF.Razor/Controls/generated/CheckBox.generated.cs index 4db9ff4..ba6182c 100644 --- a/CPF.Razor/Controls/generated/CheckBox.generated.cs +++ b/CPF.Razor/Controls/generated/CheckBox.generated.cs @@ -14,7 +14,7 @@ namespace CPF.Razor.Controls /// /// 表示用户可以选择和清除的控件。 /// - public partial class CheckBox : Element + public partial class CheckBox : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/Expander.generated.cs b/CPF.Razor/Controls/generated/Expander.generated.cs index 70a944b..2cc7bab 100644 --- a/CPF.Razor/Controls/generated/Expander.generated.cs +++ b/CPF.Razor/Controls/generated/Expander.generated.cs @@ -13,7 +13,7 @@ namespace CPF.Razor.Controls /// /// 表示一种控件,该控件显示具有可折叠内容显示窗口的标题。 /// - public partial class Expander : Element + public partial class Expander : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/RadioButton.generated.cs b/CPF.Razor/Controls/generated/RadioButton.generated.cs index 07a1266..d84bb24 100644 --- a/CPF.Razor/Controls/generated/RadioButton.generated.cs +++ b/CPF.Razor/Controls/generated/RadioButton.generated.cs @@ -14,7 +14,7 @@ namespace CPF.Razor.Controls /// /// 表示可由用户选择但不能清除的按钮。 可以通过单击来设置 IsChecked 的 RadioButton 属性,但只能以编程方式清除该属性。 /// - public partial class RadioButton : Element + public partial class RadioButton : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/RepeatButton.generated.cs b/CPF.Razor/Controls/generated/RepeatButton.generated.cs index 19a43d3..21891fb 100644 --- a/CPF.Razor/Controls/generated/RepeatButton.generated.cs +++ b/CPF.Razor/Controls/generated/RepeatButton.generated.cs @@ -13,7 +13,7 @@ namespace CPF.Razor.Controls /// /// 表示从按下按钮到释放按钮的时间内重复引发其 Click 事件的控件。 /// - public partial class RepeatButton : Element + public partial class RepeatButton : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/ScrollViewer.generated.cs b/CPF.Razor/Controls/generated/ScrollViewer.generated.cs index c4edf25..ad9a77e 100644 --- a/CPF.Razor/Controls/generated/ScrollViewer.generated.cs +++ b/CPF.Razor/Controls/generated/ScrollViewer.generated.cs @@ -13,7 +13,7 @@ namespace CPF.Razor.Controls /// /// 表示可包含其他可视元素的可滚动区域 /// - public partial class ScrollViewer : Element + public partial class ScrollViewer : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/Switch.generated.cs b/CPF.Razor/Controls/generated/Switch.generated.cs index 6aad4bd..1de9c26 100644 --- a/CPF.Razor/Controls/generated/Switch.generated.cs +++ b/CPF.Razor/Controls/generated/Switch.generated.cs @@ -14,7 +14,7 @@ namespace CPF.Razor.Controls /// /// 左右切换的按钮 /// - public partial class Switch : Element + public partial class Switch : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/ToggleButton.generated.cs b/CPF.Razor/Controls/generated/ToggleButton.generated.cs index 1a9807b..031b931 100644 --- a/CPF.Razor/Controls/generated/ToggleButton.generated.cs +++ b/CPF.Razor/Controls/generated/ToggleButton.generated.cs @@ -14,7 +14,7 @@ namespace CPF.Razor.Controls /// /// 可切换状态的控件基类 /// - public partial class ToggleButton : Element + public partial class ToggleButton : Element ,IHandleChildContentText { /// diff --git a/CPF.Razor/Controls/generated/WindowFrame.generated.cs b/CPF.Razor/Controls/generated/WindowFrame.generated.cs new file mode 100644 index 0000000..f884eb2 --- /dev/null +++ b/CPF.Razor/Controls/generated/WindowFrame.generated.cs @@ -0,0 +1,80 @@ +// CPF自动生成. + +using CPF; +using CPF.Controls; +using CPF.Drawing; +using CPF.Input; +using CPF.Razor; +using CPF.Shapes; +using Microsoft.AspNetCore.Components; + +namespace CPF.Razor.Controls +{ + /// + /// 通用窗体框架,包含窗体边框,系统按钮,阴影这些元素 + /// + public partial class WindowFrame : Element + { + + /// + /// 背景填充 + /// + [Parameter] public string Background { get; set; } + /// + /// 边框线条填充 + /// + [Parameter] public string BorderFill { get; set; } + /// + /// 获取或设置线条类型 + /// + [Parameter] public Stroke? BorderStroke { get; set; } + /// + /// 四周边框粗细 + /// + [Parameter] public Thickness? BorderThickness { get; set; } + /// + /// 边框类型,BorderStroke和BorderThickness + /// + [Parameter] public BorderType? BorderType { get; set; } + /// + /// 获取或设置一个值,该值表示将 Border 的角倒圆的程度。格式 一个数字或者四个数字 比如10或者 10,10,10,10 topLeft,topRight,bottomRight,bottomLeft + /// + [Parameter] public CornerRadius? CornerRadius { get; set; } + /// + /// 字体名 + /// + [Parameter] public string FontFamily { get; set; } + /// + /// 字体尺寸,点 + /// + [Parameter] public float? FontSize { get; set; } + /// + /// 字体样式 + /// + [Parameter] public FontStyles? FontStyle { get; set; } + /// + /// 控件文字的填充 + /// + [Parameter] public string Foreground { get; set; } + [Parameter] public bool? MaximizeBox { get; set; } + [Parameter] public bool? MinimizeBox { get; set; } + /// + /// 获取或设置描述 Thickness 及其子元素之间的空间量的 Border 值。格式:all或者left,top,right,bottom + /// + [Parameter] public Thickness? Padding { get; set; } + /// + /// 阴影宽度 + /// + [Parameter] public byte? ShadowBlur { get; set; } + /// + /// 显示标题栏图标 + /// + [Parameter] public bool? ShowIcon { get; set; } + /// + /// 表示一个文本修饰,它是可添加到文本的视觉装饰(如下划线)。字符串格式: overline/Underline/Strikethrough/none [width[,Solid/Dash/Dot/DashDot/DashDotDot]] [color] + /// + [Parameter] public TextDecoration? TextDecoration { get; set; } + [Parameter] public EventCallback Initialized { get; set; } + + } +} diff --git a/CPF.Razor/Core/NativeControlComponentBase.cs b/CPF.Razor/Core/NativeControlComponentBase.cs index 33e69fb..4af512a 100644 --- a/CPF.Razor/Core/NativeControlComponentBase.cs +++ b/CPF.Razor/Core/NativeControlComponentBase.cs @@ -50,7 +50,7 @@ namespace CPF.Razor builder.OpenElement(0, GetType().FullName); RenderAttributes(new AttributesBuilder(builder)); - var childContent = GetChildContent(); + var childContent = GetChild(); if (childContent != null) { builder.AddContent(2, childContent); @@ -63,7 +63,7 @@ namespace CPF.Razor { } - protected virtual RenderFragment GetChildContent() => null; + protected virtual RenderFragment GetChild() => null; public abstract void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName); diff --git a/CPF.Razor/CpfElementManager.cs b/CPF.Razor/CpfElementManager.cs index 9dab84c..e76679d 100644 --- a/CPF.Razor/CpfElementManager.cs +++ b/CPF.Razor/CpfElementManager.cs @@ -31,7 +31,7 @@ namespace CPF.Razor panel.Children.Add(childHandler.Element); } } - else if (parentHandler.Element is CPF.Controls.Window win) + else if (parentHandler.Element is CPF.Controls.View win) { if (physicalSiblingIndex <= win.Children.Count) { @@ -64,6 +64,14 @@ namespace CPF.Razor { panel.Children.Remove(handler.Element); } + else if (handler.Element.Parent is CPF.Controls.View win) + { + win.Children.Remove(handler.Element); + } + else if (handler.Element.Parent is CPF.Controls.ContentControl contentControl) + { + contentControl.Content = null; + } else { Debug.Fail("未实现移除控件"); diff --git a/CPF/Controls/WindowFrame.cs b/CPF/Controls/WindowFrame.cs index 818dd9e..50d445a 100644 --- a/CPF/Controls/WindowFrame.cs +++ b/CPF/Controls/WindowFrame.cs @@ -17,7 +17,7 @@ namespace CPF.Controls /// 通用窗体框架,包含窗体边框,系统按钮,阴影这些元素 /// [Description("通用窗体框架,包含窗体边框,系统按钮,阴影这些元素")] - public class WindowFrame : Control + public class WindowFrame : ContentControl { /// /// 通用窗体框架,包含窗体边框,系统按钮,阴影这些元素 @@ -28,11 +28,11 @@ namespace CPF.Controls public WindowFrame(IWindow window, UIElement content, params UIElement[] systemButtons) { this.window = window; - this.content = content; + this.Content = content; this.systemButtons = systemButtons; } - protected WindowFrame() { } + public WindowFrame() { } /// /// 是否显示最大化还原按钮 @@ -54,15 +54,15 @@ namespace CPF.Controls get { return window; } } - UIElement content; - /// - /// 窗体的内容 - /// - [NotCpfProperty] - public UIElement Content - { - get { return content; } - } + //UIElement content; + ///// + ///// 窗体的内容 + ///// + //[NotCpfProperty] + //public UIElement Content + //{ + // get { return content; } + //} IEnumerable systemButtons; /// @@ -96,7 +96,7 @@ namespace CPF.Controls protected override void InitializeComponent() { - + ViewFill color = "#fff"; ViewFill hoverColor = "255,255,255,40"; Width = "100%"; @@ -142,6 +142,8 @@ namespace CPF.Controls { Width = "100%", Height = "100%", + Name = "contentGrid", + PresenterFor = this, ColumnDefinitions = { new ColumnDefinition() @@ -157,6 +159,19 @@ namespace CPF.Controls } }, + Children = + { + new Border + { + Name = "contentPresenter", + Height = "100%", + Width = "100%", + BorderFill = null, + BorderStroke="0", + PresenterFor = this, + [Grid.RowIndex]=1, + } + } }); //标题栏和按钮 grid.Children.Add( @@ -237,6 +252,7 @@ namespace CPF.Controls new StackPanel { Name="controlBox", + PresenterFor=this, MarginRight=0, Height = "100%", Orientation= Orientation.Horizontal, @@ -495,10 +511,10 @@ namespace CPF.Controls } } }); - if (Content != null) - { - grid.Children.Add(Content, 0, 1); - } + //if (Content != null) + //{ + // grid.Children.Add(Content, 0, 1); + //} } protected void DoubleClickTitle() @@ -515,6 +531,25 @@ namespace CPF.Controls } } + protected override void OnAttachedToVisualTree() + { + var parent = Parent; + while (parent != null) + { + if (parent is IWindow window) + { + this.window = window; + break; + } + parent = parent.Parent; + } + if (window == null) + { + window = (IWindow)Root; + } + base.OnAttachedToVisualTree(); + } + } /// /// 通用窗体接口 diff --git a/CpfRazorSample/ComponentWrapperGenerator/CpfComponentWrapperGenerator.cs b/CpfRazorSample/ComponentWrapperGenerator/CpfComponentWrapperGenerator.cs new file mode 100644 index 0000000..1269da0 --- /dev/null +++ b/CpfRazorSample/ComponentWrapperGenerator/CpfComponentWrapperGenerator.cs @@ -0,0 +1,636 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Windows.Input; + +namespace ComponentWrapperGenerator +{ + // TODO: XML Doc Comments + +#pragma warning disable CA1724 // Type name conflicts with namespace name + public class CpfComponentWrapperGenerator +#pragma warning restore CA1724 // Type name conflicts with namespace name + { + public CpfComponentWrapperGenerator(GeneratorSettings settings) + { + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + private GeneratorSettings Settings { get; } + + public void GenerateComponentWrapper(Type typeToGenerate, string outputFolder) + { + typeToGenerate = typeToGenerate ?? throw new ArgumentNullException(nameof(typeToGenerate)); + + var propertiesToGenerate = GetPropertiesToGenerate(typeToGenerate); + + GenerateComponentFile(typeToGenerate, propertiesToGenerate, outputFolder); + //GenerateHandlerFile(typeToGenerate, propertiesToGenerate, outputFolder); + } + + private void GenerateComponentFile(Type typeToGenerate, IEnumerable propertiesToGenerate, string outputFolder) + { + var fileName = Path.Combine(outputFolder, $"{typeToGenerate.Name}.generated.cs"); + var directoryName = Path.GetDirectoryName(fileName); + if (!string.IsNullOrEmpty(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + + Console.WriteLine($"Generating component for type '{typeToGenerate.FullName}' into file '{fileName}'."); + + var componentName = typeToGenerate.Name; + //var componentHandlerName = $"{componentName}Handler"; + //var componentBaseName = GetBaseTypeOfInterest(typeToGenerate).Name; + var componentBaseName = $": Element<{typeToGenerate.FullName}>"; + if (typeToGenerate.IsSubclassOf(typeof(CPF.Controls.ContentControl))) + { + componentBaseName += " ,IHandleChildContentText"; + } + if (componentName == "UIElement") + { + componentName = "Element"; + componentBaseName = ""; + } + + // header + var headerText = Settings.FileHeader; + + // usings + var usings = new List + { + new UsingStatement { Namespace = "Microsoft.AspNetCore.Components" }, + new UsingStatement { Namespace = "CPF" }, + new UsingStatement { Namespace = "CPF.Input" }, + new UsingStatement { Namespace = "CPF.Shapes" }, + new UsingStatement { Namespace = "CPF.Razor" }, + new UsingStatement { Namespace = "CPF.Drawing" }, + new UsingStatement { Namespace = "CPF.Controls" }, + new UsingStatement { Namespace = "CPF.Razor.Controls" } + }; + + // props + var propertyDeclarationBuilder = new StringBuilder(); + if (propertiesToGenerate.Any()) + { + propertyDeclarationBuilder.AppendLine(); + } + foreach (var prop in propertiesToGenerate) + { + propertyDeclarationBuilder.Append(GetPropertyDeclaration(prop, usings)); + } + + var events = typeToGenerate.GetEvents(BindingFlags.Public | BindingFlags.Instance); + foreach (var prop in events) + { + if (typeToGenerate == typeof(CPF.UIElement) || (typeToGenerate != typeof(CPF.UIElement) && prop.DeclaringType != typeof(CPF.UIElement) && prop.DeclaringType != typeof(CPF.Visual) && prop.DeclaringType != typeof(CPF.CpfObject))) + { + propertyDeclarationBuilder.Append(GetEventDeclaration(prop, usings)); + } + } + + var propertyDeclarations = propertyDeclarationBuilder.ToString(); + + //var propertyAttributeBuilder = new StringBuilder(); + //foreach (var prop in propertiesToGenerate) + //{ + // propertyAttributeBuilder.Append(GetPropertyRenderAttribute(prop)); + //} + //var propertyAttributes = propertyAttributeBuilder.ToString(); + //var eventHandlerAttributes = ""; + + var usingsText = string.Join( + Environment.NewLine, + usings + .Distinct() + .Where(u => u.Namespace != Settings.RootNamespace) + .OrderBy(u => u.ComparableString) + .Select(u => u.UsingText)); + + var isComponentAbstract = typeToGenerate.IsAbstract; + var classModifiers = string.Empty; + if (isComponentAbstract) + { + classModifiers += "abstract "; + } + var componentHasPublicParameterlessConstructor = + typeToGenerate + .GetConstructors() + .Any(ctor => ctor.IsPublic && !ctor.GetParameters().Any()); + + var des = ""; + var d = typeToGenerate.GetCustomAttribute(); + if (d != null) + { + des = d.Description; + } + + var outputBuilder = new StringBuilder(); + outputBuilder.Append($@"{headerText} +{usingsText} + +namespace {Settings.RootNamespace} +{{ + /// + /// {des} + /// + public {classModifiers}partial class {componentName} {componentBaseName} + {{ + {propertyDeclarations} + }} +}} +"); + + File.WriteAllText(fileName, outputBuilder.ToString()); + } + + //private static readonly List DisallowedComponentPropertyTypes = new List + //{ + // typeof(XF.Button.ButtonContentLayout), // TODO: This is temporary; should be possible to add support later + // typeof(XF.ColumnDefinitionCollection), + // typeof(XF.ControlTemplate), + // typeof(XF.DataTemplate), + // typeof(XF.Element), + // typeof(XF.Font), // TODO: This is temporary; should be possible to add support later + // typeof(XF.FormattedString), + // typeof(ICommand), + // typeof(XF.Keyboard), // TODO: This is temporary; should be possible to add support later + // typeof(object), + // typeof(XF.Page), + // typeof(XF.ResourceDictionary), + // typeof(XF.RowDefinitionCollection), + // typeof(XF.ShellContent), + // typeof(XF.ShellItem), + // typeof(XF.ShellSection), + // typeof(XF.Style), // TODO: This is temporary; should be possible to add support later + // typeof(XF.IVisual), + // typeof(XF.View), + //}; + + private static string GetPropertyDeclaration(PropertyInfo prop, IList usings) + { + var propertyType = prop.PropertyType; + string propertyTypeName; + if (propertyType == typeof(IList) || propertyType == typeof(CPF.ViewFill) || propertyType == typeof(CPF.Drawing.Color) || propertyType == typeof(CPF.Drawing.Brush)) + { + // Lists of strings are special-cased because they are handled specially by the handlers as a comma-separated list + propertyTypeName = "string"; + } + else + { + propertyTypeName = GetTypeNameAndAddNamespace(propertyType, usings); + if (propertyType.IsValueType && (!propertyType.IsGenericType || propertyType.GetGenericTypeDefinition() == typeof(Nullable))) + { + propertyTypeName += "?"; + } + } + var des = ""; + var d = prop.GetCustomAttribute(); + if (d != null) + { + des = $" /// \r\n /// {d.Description}\r\n /// \r\n"; + } + return $@"{des} [Parameter] public {propertyTypeName} {GetIdentifierName(prop.Name)} {{ get; set; }} +"; + } + + private static string GetEventDeclaration(EventInfo prop, IList usings) + { + var propertyType = prop.EventHandlerType; + string propertyTypeName; + if (propertyType == typeof(EventHandler)) + { + // Lists of strings are special-cased because they are handled specially by the handlers as a comma-separated list + propertyTypeName = "EventCallback"; + } + else + { + //propertyTypeName = GetTypeNameAndAddNamespace(propertyType, usings); + //if (propertyType.IsValueType) + //{ + // propertyTypeName += "?"; + //} + propertyTypeName = $"EventCallback<{propertyType.GetGenericArguments()[0]}>"; + } + var des = ""; + var d = prop.GetCustomAttribute(); + if (d != null) + { + des = $" /// \r\n /// {d.Description}\r\n /// \r\n"; + } + return $@"{des} [Parameter] public {propertyTypeName} {GetIdentifierName(prop.Name)} {{ get; set; }} +"; + } + + private static string GetTypeNameAndAddNamespace(Type type, IList usings) + { + var typeName = GetCSharpType(type); + if (typeName != null) + { + return typeName; + } + + // Check if there's a 'using' already. If so, check if it has an alias. If not, add a new 'using'. + var namespaceAlias = string.Empty; + + var existingUsing = usings.FirstOrDefault(u => u.Namespace == type.Namespace); + if (existingUsing == null) + { + usings.Add(new UsingStatement { Namespace = type.Namespace }); + } + else + { + if (existingUsing.Alias != null) + { + namespaceAlias = existingUsing.Alias + "."; + } + } + typeName = namespaceAlias + FormatTypeName(type, usings); + return typeName; + } + + private static string FormatTypeName(Type type, IList usings) + { + if (!type.IsGenericType) + { + return type.Name; + } + var typeNameBuilder = new StringBuilder(); + typeNameBuilder.Append(type.Name.Substring(0, type.Name.IndexOf('`', StringComparison.Ordinal))); + typeNameBuilder.Append("<"); + var genericArgs = type.GetGenericArguments(); + for (int i = 0; i < genericArgs.Length; i++) + { + if (i > 0) + { + typeNameBuilder.Append(", "); + } + typeNameBuilder.Append(GetTypeNameAndAddNamespace(genericArgs[i], usings)); + + } + typeNameBuilder.Append(">"); + return typeNameBuilder.ToString(); + } + + //private static readonly Dictionary> TypeToAttributeHelperGetter = new Dictionary> + //{ + // { typeof(XF.Color), propValue => $"AttributeHelper.ColorToString({propValue})" }, + // { typeof(XF.CornerRadius), propValue => $"AttributeHelper.CornerRadiusToString({propValue})" }, + // { typeof(XF.ImageSource), propValue => $"AttributeHelper.ImageSourceToString({propValue})" }, + // { typeof(XF.LayoutOptions), propValue => $"AttributeHelper.LayoutOptionsToString({propValue})" }, + // { typeof(XF.Thickness), propValue => $"AttributeHelper.ThicknessToString({propValue})" }, + // { typeof(bool), propValue => $"{propValue}" }, + // { typeof(double), propValue => $"AttributeHelper.DoubleToString({propValue})" }, + // { typeof(float), propValue => $"AttributeHelper.SingleToString({propValue})" }, + // { typeof(int), propValue => $"{propValue}" }, + // { typeof(string), propValue => $"{propValue}" }, + // { typeof(IList), propValue => $"{propValue}" }, + //}; + + // private static string GetPropertyRenderAttribute(PropertyInfo prop) + // { + // var propValue = prop.PropertyType.IsValueType ? $"{GetIdentifierName(prop.Name)}.Value" : GetIdentifierName(prop.Name); + // var formattedValue = propValue; + // if (TypeToAttributeHelperGetter.TryGetValue(prop.PropertyType, out var formattingFunc)) + // { + // formattedValue = formattingFunc(propValue); + // } + // else if (prop.PropertyType.IsEnum) + // { + // formattedValue = $"(int){formattedValue}"; + // } + // else + // { + // // TODO: Error? + // Console.WriteLine($"WARNING: Couldn't generate attribute render for {prop.DeclaringType.Name}.{prop.Name}"); + // } + + // return $@" if ({GetIdentifierName(prop.Name)} != null) + // {{ + // builder.AddAttribute(nameof({GetIdentifierName(prop.Name)}), {formattedValue}); + // }} + //"; + // } + + private static readonly Dictionary TypeToCSharpName = new Dictionary + { + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(sbyte), "sbyte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(uint), "uint" }, + { typeof(long), "long" }, + { typeof(ulong), "ulong" }, + { typeof(object), "object" }, + { typeof(short), "short" }, + { typeof(ushort), "ushort" }, + { typeof(string), "string" }, + }; + + private static string GetCSharpType(Type propertyType) + { + return TypeToCSharpName.TryGetValue(propertyType, out var typeName) ? typeName : null; + } + + ///// + ///// Finds the next non-generic base type of the specified type. This matches the Mobile Blazor Bindings + ///// model where there is no need to represent the intermediate generic base classes because they are + ///// generally only containers and have no API functionality that needs to be generated. + ///// + ///// + ///// + //private static Type GetBaseTypeOfInterest(Type type) + //{ + // do + // { + // type = type.BaseType; + // if (!type.IsGenericType) + // { + // return type; + // } + // } + // while (type != null); + + // return null; + //} + + // private void GenerateHandlerFile(Type typeToGenerate, IEnumerable propertiesToGenerate, string outputFolder) + // { + // var fileName = Path.Combine(outputFolder, "Handlers", $"{typeToGenerate.Name}Handler.generated.cs"); + // var directoryName = Path.GetDirectoryName(fileName); + // if (!string.IsNullOrEmpty(directoryName)) + // { + // Directory.CreateDirectory(directoryName); + // } + + // Console.WriteLine($"Generating component handler for type '{typeToGenerate.FullName}' into file '{fileName}'."); + + // var componentName = typeToGenerate.Name; + // var componentVarName = char.ToLowerInvariant(componentName[0]) + componentName.Substring(1); + // var componentHandlerName = $"{componentName}Handler"; + // var componentBaseName = GetBaseTypeOfInterest(typeToGenerate).Name; + // var componentHandlerBaseName = $"{componentBaseName}Handler"; + + // // header + // var headerText = Settings.FileHeader; + + // // usings + // var usings = new List + // { + // //new UsingStatement { Namespace = "Microsoft.AspNetCore.Components" }, // Typically needed only when there are event handlers for the EventArgs types + // new UsingStatement { Namespace = "Microsoft.MobileBlazorBindings.Core" }, + // new UsingStatement { Namespace = "System" }, + // new UsingStatement { Namespace = "Xamarin.Forms", Alias = "XF" } + // }; + + // //// props + // //var propertySettersBuilder = new StringBuilder(); + // //foreach (var prop in propertiesToGenerate) + // //{ + // // propertySettersBuilder.Append(GetPropertySetAttribute(prop, usings)); + // //} + // //var propertySetters = propertySettersBuilder.ToString(); + + // var usingsText = string.Join( + // Environment.NewLine, + // usings + // .Distinct() + // .Where(u => u.Namespace != Settings.RootNamespace) + // .OrderBy(u => u.ComparableString) + // .Select(u => u.UsingText)); + + // var isComponentAbstract = typeToGenerate.IsAbstract; + // var classModifiers = string.Empty; + // if (isComponentAbstract) + // { + // classModifiers += "abstract "; + // } + + // var applyAttributesMethod = string.Empty; + // // if (!string.IsNullOrEmpty(propertySetters)) + // // { + // // applyAttributesMethod = $@" + // // public override void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName) + // // {{ + // // switch (attributeName) + // // {{ + // //{propertySetters} default: + // // base.ApplyAttribute(attributeEventHandlerId, attributeName, attributeValue, attributeEventUpdatesAttributeName); + // // break; + // // }} + // // }} + // //"; + // // } + + // var outputBuilder = new StringBuilder(); + // outputBuilder.Append($@"{headerText} + //{usingsText} + + //namespace {Settings.RootNamespace}.Handlers + //{{ + // public {classModifiers}partial class {componentHandlerName} : {componentHandlerBaseName} + // {{ + // public {componentName}Handler(NativeComponentRenderer renderer, XF.{componentName} {componentVarName}Control) : base(renderer, {componentVarName}Control) + // {{ + // {componentName}Control = {componentVarName}Control ?? throw new ArgumentNullException(nameof({componentVarName}Control)); + + // Initialize(renderer); + // }} + + // partial void Initialize(NativeComponentRenderer renderer); + + // public XF.{componentName} {componentName}Control {{ get; }} + //{applyAttributesMethod} }} + //}} + //"); + + // File.WriteAllText(fileName, outputBuilder.ToString()); + // } + + // private static string GetPropertySetAttribute(PropertyInfo prop, List usings) + // { + // // Handle null values by resetting to default value + // var resetValueParameterExpression = string.Empty; + // var bindablePropertyForProp = GetBindablePropertyForProp(prop); + // if (bindablePropertyForProp != null) + // { + // var declaredDefaultValue = bindablePropertyForProp.DefaultValue; + // var defaultValueForType = GetDefaultValueForType(prop.PropertyType); + // var needsCustomResetValue = declaredDefaultValue == null ? false : !declaredDefaultValue.Equals(defaultValueForType); + + // if (needsCustomResetValue) + // { + // var valueExpression = GetValueExpression(declaredDefaultValue, usings); + // if (string.IsNullOrEmpty(valueExpression)) + // { + // Console.WriteLine($"WARNING: Couldn't get value expression for {prop.DeclaringType.Name}.{prop.Name} of type {prop.PropertyType.FullName}."); + // } + // resetValueParameterExpression = valueExpression; + // } + // } + + // var formattedValue = string.Empty; + // if (TypeToAttributeHelperSetter.TryGetValue(prop.PropertyType, out var propValueFormat)) + // { + // var resetValueParameterExpressionAsExtraParameter = string.Empty; + // if (!string.IsNullOrEmpty(resetValueParameterExpression)) + // { + // resetValueParameterExpressionAsExtraParameter = ", " + resetValueParameterExpression; + // } + // formattedValue = string.Format(CultureInfo.InvariantCulture, propValueFormat, resetValueParameterExpressionAsExtraParameter); + // } + // else if (prop.PropertyType.IsEnum) + // { + // var resetValueParameterExpressionAsExtraParameter = string.Empty; + // if (!string.IsNullOrEmpty(resetValueParameterExpression)) + // { + // resetValueParameterExpressionAsExtraParameter = ", (int)" + resetValueParameterExpression; + // } + // var castTypeName = GetTypeNameAndAddNamespace(prop.PropertyType, usings); + // formattedValue = $"({castTypeName})AttributeHelper.GetInt(attributeValue{resetValueParameterExpressionAsExtraParameter})"; + // } + // else if (prop.PropertyType == typeof(string)) + // { + // formattedValue = + // string.IsNullOrEmpty(resetValueParameterExpression) + // ? "(string)attributeValue" + // : string.Format(CultureInfo.InvariantCulture, "(string)attributeValue ?? {0}", resetValueParameterExpression); + // } + // else + // { + // // TODO: Error? + // Console.WriteLine($"WARNING: Couldn't generate property set for {prop.DeclaringType.Name}.{prop.Name}"); + // } + + // return $@" case nameof(XF.{prop.DeclaringType.Name}.{GetIdentifierName(prop.Name)}): + // {prop.DeclaringType.Name}Control.{GetIdentifierName(prop.Name)} = {formattedValue}; + // break; + //"; + // } + + //private static string GetValueExpression(object declaredDefaultValue, List usings) + //{ + // if (declaredDefaultValue is null) + // { + // throw new ArgumentNullException(nameof(declaredDefaultValue)); + // } + + // return declaredDefaultValue switch + // { + // bool boolValue => boolValue ? "true" : "false", + // int intValue => GetIntValueExpression(intValue), + // float floatValue => floatValue.ToString("F", CultureInfo.InvariantCulture) + "f", // "Fixed-Point": https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-fixed-point-f-format-specifier + // double doubleValue => doubleValue.ToString("F", CultureInfo.InvariantCulture), // "Fixed-Point": https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-fixed-point-f-format-specifier + // Enum enumValue => GetTypeNameAndAddNamespace(enumValue.GetType(), usings) + "." + Enum.GetName(enumValue.GetType(), declaredDefaultValue), + // XF.LayoutOptions layoutOptionsValue => GetLayoutOptionsValueExpression(layoutOptionsValue), + // string stringValue => $@"""{stringValue}""", + // // TODO: More types here + // _ => null, + // }; + //} + + //private static string GetLayoutOptionsValueExpression(XF.LayoutOptions layoutOptionsValue) + //{ + // var expandSuffix = layoutOptionsValue.Expands ? "AndExpand" : string.Empty; + // return $"XF.LayoutOptions.{layoutOptionsValue.Alignment}{expandSuffix}"; + //} + + //private static string GetIntValueExpression(int intValue) + //{ + // return intValue switch + // { + // int.MinValue => "int.MinValue", + // int.MaxValue => "int.MaxValue", + // _ => intValue.ToString(CultureInfo.InvariantCulture), + // }; + //} + + //private static object GetDefaultValueForType(Type propertyType) + //{ + // if (propertyType.IsValueType) + // { + // return Activator.CreateInstance(propertyType); + // } + // return null; + //} + + //private static XF.BindableProperty GetBindablePropertyForProp(PropertyInfo prop) + //{ + // var bindablePropertyField = prop.DeclaringType.GetField(prop.Name + "Property"); + // if (bindablePropertyField == null) + // { + // return null; + // } + // return (XF.BindableProperty)bindablePropertyField.GetValue(null); + //} + + //private static readonly Dictionary TypeToAttributeHelperSetter = new Dictionary + //{ + // { typeof(XF.Color), "AttributeHelper.StringToColor((string)attributeValue{0})" }, + // { typeof(XF.CornerRadius), "AttributeHelper.StringToCornerRadius(attributeValue{0})" }, + // { typeof(XF.ImageSource), "AttributeHelper.StringToImageSource(attributeValue{0})" }, + // { typeof(XF.LayoutOptions), "AttributeHelper.StringToLayoutOptions(attributeValue{0})" }, + // { typeof(XF.Thickness), "AttributeHelper.StringToThickness(attributeValue{0})" }, + // { typeof(bool), "AttributeHelper.GetBool(attributeValue{0})" }, + // { typeof(double), "AttributeHelper.StringToDouble((string)attributeValue{0})" }, + // { typeof(float), "AttributeHelper.StringToSingle((string)attributeValue{0})" }, + // { typeof(int), "AttributeHelper.GetInt(attributeValue{0})" }, + // { typeof(IList), "AttributeHelper.GetStringList(attributeValue)" }, + //}; + + static HashSet DisallowedPropertyName = new HashSet + { + "Site" + }; + + private static IEnumerable GetPropertiesToGenerate(Type componentType) + { + var allPublicProperties = componentType.GetProperties(); + + return + allPublicProperties + .Where(HasPublicGetAndSet) + .Where(prop => componentType == typeof(CPF.UIElement) || (componentType != typeof(CPF.UIElement) && prop.DeclaringType != typeof(CPF.UIElement) && prop.DeclaringType != typeof(CPF.Visual) && prop.DeclaringType != typeof(CPF.CpfObject))) + //.Where(prop => !DisallowedComponentPropertyTypes.Contains(prop.PropertyType)) + .Where(prop => !DisallowedPropertyName.Contains(prop.Name)) + .Where(IsPropertyBrowsable) + .OrderBy(prop => prop.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private static bool HasPublicGetAndSet(PropertyInfo propInfo) + { + if (propInfo.PropertyType == typeof(CPF.UIElementTemplate) || propInfo.PropertyType.IsGenericType && propInfo.PropertyType.GetGenericTypeDefinition() == typeof(CPF.UIElementTemplate<>)) + { + return false; + } + return propInfo.GetGetMethod() != null && propInfo.GetSetMethod() != null && propInfo.GetCustomAttribute(typeof(CPF.NotCpfProperty)) == null; + } + + private static bool IsPropertyBrowsable(PropertyInfo propInfo) + { + // [EditorBrowsable(EditorBrowsableState.Never)] + var attr = (EditorBrowsableAttribute)Attribute.GetCustomAttribute(propInfo, typeof(EditorBrowsableAttribute)); + return (attr == null) || (attr.State != EditorBrowsableState.Never); + } + + private static string GetIdentifierName(string possibleIdentifier) + { + return ReservedKeywords.Contains(possibleIdentifier, StringComparer.Ordinal) + ? $"@{possibleIdentifier}" + : possibleIdentifier; + } + + private static readonly List ReservedKeywords = new List + { "class", }; + } +} diff --git a/CpfRazorSample/ComponentWrapperGenerator/GeneratorSettings.cs b/CpfRazorSample/ComponentWrapperGenerator/GeneratorSettings.cs new file mode 100644 index 0000000..fe61f62 --- /dev/null +++ b/CpfRazorSample/ComponentWrapperGenerator/GeneratorSettings.cs @@ -0,0 +1,8 @@ +namespace ComponentWrapperGenerator +{ + public class GeneratorSettings + { + public string FileHeader { get; set; } + public string RootNamespace { get; set; } + } +} diff --git a/CpfRazorSample/ComponentWrapperGenerator/UsingStatement.cs b/CpfRazorSample/ComponentWrapperGenerator/UsingStatement.cs new file mode 100644 index 0000000..eb78a32 --- /dev/null +++ b/CpfRazorSample/ComponentWrapperGenerator/UsingStatement.cs @@ -0,0 +1,12 @@ +namespace ComponentWrapperGenerator +{ + internal sealed class UsingStatement + { + public string Alias { get; set; } + public string Namespace { get; set; } + + public string ComparableString => Alias?.ToUpperInvariant() ?? Namespace?.ToUpperInvariant(); + + public string UsingText => $"using {(Alias != null ? Alias + " = " : "")}{Namespace};"; + } +} diff --git a/CpfRazorSample/CpfRazorSample.csproj b/CpfRazorSample/CpfRazorSample.csproj index f7ea705..9ca6176 100644 --- a/CpfRazorSample/CpfRazorSample.csproj +++ b/CpfRazorSample/CpfRazorSample.csproj @@ -24,8 +24,8 @@ - - + diff --git a/CpfRazorSample/Program.cs b/CpfRazorSample/Program.cs index 5786464..9c8d4b2 100644 --- a/CpfRazorSample/Program.cs +++ b/CpfRazorSample/Program.cs @@ -4,6 +4,7 @@ using CPF.Windows; using Microsoft.Extensions.Hosting; using System; using CPF.Razor; +using ComponentWrapperGenerator; namespace CpfRazorSample { @@ -14,8 +15,8 @@ namespace CpfRazorSample { 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才需要 + //, (OperatingSystemType.OSX, new CPF.Mac.MacPlatform(), new SkiaDrawingFactory())//如果需要支持Mac才需要 + //, (OperatingSystemType.Linux, new CPF.Linux.LinuxPlatform(), new SkiaDrawingFactory())//如果需要支持Linux才需要 ); var host = Host.CreateDefaultBuilder() @@ -26,9 +27,46 @@ namespace CpfRazorSample }) .Build(); - var window = new CPF.Controls.Window(); + var window = new CPF.Controls.Window { Width = 500, Height = 500, Background = null }; host.AddComponent(window); Application.Run(window); + + + } + + static void Create() + { + var settings = new GeneratorSettings + { + FileHeader = @"//CPF自动生成. + ", + RootNamespace = "CPF.Razor.Controls", + }; + + var type = typeof(CPF.UIElement); + var viewType = typeof(CPF.Controls.View); + var types = type.Assembly.GetTypes(); + CpfGenerateWrapperForType(type, settings, ""); + //CpfGenerateWrapperForType(typeof(CPF.Controls.WindowFrame), settings, ""); + foreach (var item in types) + { + if (item.IsPublic && item.IsSubclassOf(type) && !item.IsAbstract && !item.IsGenericType && !item.IsSubclassOf(viewType) && item.GetConstructor(Array.Empty()) != null) + { + var brow = item.GetCustomAttributes(typeof(System.ComponentModel.BrowsableAttribute), true); + if (brow != null && brow.Length > 0 && !(brow[0] as System.ComponentModel.BrowsableAttribute).Browsable) + { + continue; + } + CpfGenerateWrapperForType(item, settings, ""); + } + } + } + + private static void CpfGenerateWrapperForType(Type typeToGenerate, GeneratorSettings settings, string outputFolder) + { + var generator = new CpfComponentWrapperGenerator(settings); + generator.GenerateComponentWrapper(typeToGenerate, outputFolder); + Console.WriteLine(); } } } diff --git a/CpfRazorSample/Test.razor b/CpfRazorSample/Test.razor index fa2957e..3589f3f 100644 --- a/CpfRazorSample/Test.razor +++ b/CpfRazorSample/Test.razor @@ -1,18 +1,22 @@ - - - - - @if (visible) - { - - } - + + + + + @if (visible) + { + + } + + + + + @code { bool visible = false; string text = "test"; - void OnMouseDown() + void OnMouseDown(CPF.Input.MouseButtonEventArgs e) { text = "test" + visible; visible = !visible;