用 Ant Blazor 实现 PropertyGrid

目标

    做一个类似 vs2019 属性的 PropertyGrid,支持对各种属性的编辑,省去为各种类型制作编辑表单的工作。

    PropertyGrid的主要特性:

  1. 支持 bool, int, double, string, object, 集合 属性的编辑;
  2. int double 类型可设定最大值、最小值、默认值,可以给定单位;
  3. string 类型可指定编辑器为颜色;
  4. string类型可从下来列表框中选择;
  5. object, 集合 按照父子行显示,父行只读;
  6. 可以设定属性不可编辑。

Ant Blazor Table

    本案主要利用了 在Ant Design Blazor 官方文档中的Table的动态数据可展开两个特性。

方案构成

  1. Property 为各种类型 (bool, int, double, string, object, 集合) 定义的属性元数据;
  2. TypeDef 对应一种被编辑对象,包含所有可编辑Property;
  3. Editor 用来编辑TypeDef 的工具,结果保存为json格式;
  4. PropertyGrid,主角,根据TypeDef构造一个Table,每个Property占一行,遇到object或集合 Property时构造父子行。

PropertyGrid.razor 主要逻辑

    _Datas由TypeDef 中的Property集合构造,每个Property转换为一个Data,每个Data对应表的一行,行的编辑控件取决于Property的类型。

@using System.ComponentModel

<Table TItem="Data"
       DataSource="_Datas"
       TreeChildren="item=> item.Children"
       Size=@TableSize.Small
       PageSize="100"
       Bordered       
       HidePagination>
    <ChildContent>
        @if (context.Visible)
        {
            <Column TData="string">
                @context.Description
            </Column>
            <Column TData="string">
                @if (context.Editable)
                {
                    if (context.DataType == Meta.Properties.DataType.Boolean)
                    {
                        <Checkbox @bind-Value="context.BoolValue" />
                    }
                    else if (context.DataType == Meta.Properties.DataType.Int32)
                    {
                        <AntDesign.InputNumber @bind-Value="context.IntValue" Min="context.MinInt" Max="context.MaxInt"/>
                    }
                    else if (context.DataType == Meta.Properties.DataType.Double)
                    {
                        <AntDesign.InputNumber @bind-Value="context.DoubleValue" Min="context.MinDouble" Max="context.MaxDouble"/>
                    }
                    else if (context.DataType == Meta.Properties.DataType.String)
                    {
                        if (context.Editor == "color")
                        {
                            <InputGroup Compact>
                                <Input Type="color" @bind-Value="context.StringValue" style="padding:0; max-width:3rem"/>
                                <Input @bind-Value="context.StringValue" spellcheck="false"/>
                            </InputGroup>
                        }
                        else if (context.Options == null || context.Options.Count == 0)
                        {
                            <Input @bind-Value="context.StringValue" />
                        }
                        else
                        {
                            <Select @bind-Value="context.StringValue"
                                    DefaultValue=context.StringValue
                                    TItemValue="string"
                                    TItem="string">
                                <SelectOptions>
                                    @foreach (var item in context.Options)
                                    {
                                        <SelectOption TItemValue="string" TItem="string" Value=@item Label=@item />
                                    }
                                </SelectOptions>
                            </Select>
                        }
                    }
                }
                else
                {
                    @context.Value
                }
            </Column>
            <Column TData="string">
                @context.Unit
            </Column>
        }
    </ChildContent>
</Table>

Data类

    Data主要目的是合并各类property的属性,以便简化PropertyGrid.razor的绑定。

   public class Data
    {
        IProperty _Property;
        public Action<IProperty,object> ValueChanged;
        public Data(IProperty property)
        {
            _Property = property;
        }
        public string Name { get { return _Property.Name; }  }
        public DataType DataType { get { return _Property.DataType; } }
        public string Description { get { return _Property?.Description; } set { _Property.Description = value; } }
        public string Category { get { return _Property.Category; } }
        public bool Visible { get { return _Property!=null?_Property.Visible:true; } }
        public bool Editable { get { return _Property != null ? _Property.Editable : false; } }
        public string Editor 
        { 
            get
            {
                if (_Property != null && _Property is StringProperty sp)
                {
                    return sp.Editor;
                }
                return string.Empty;
            } 
        }
        public bool BoolValue 
        {
            get
            { 
                if(_Property != null && _Property.Value != null && _Property is BoolProperty)
                {
                    return (bool)_Property.Value;
                }
                return false;
            }
            set
            {
                _Property.Value = value;
                if (ValueChanged != null)
                {
                    ValueChanged.Invoke(_Property, value);
                }
            }
        }
        public int IntValue
        {
            get
            {
                if (_Property != null && _Property.Value != null && _Property is IntegerProperty)
                {
                    return (int)_Property.Value;
                }
                return 0;
            }
            set
            {
                _Property.Value = value;
                if (ValueChanged != null)
                {
                    ValueChanged.Invoke(_Property, value);
                }
            }
        }
        public double DoubleValue
        {
            get
            {
                if (_Property != null && _Property.Value != null && _Property is DoubleProperty)
                {
                    return (double)_Property.Value;
                }
                return 0;
            }
            set
            {
                _Property.Value = value;
                if (ValueChanged != null)
                {
                    ValueChanged.Invoke(_Property, value);
                }
            }
        }

        public string Formatter(double value)
        {
            int precise = 2;
            if (_Property != null && _Property is DoubleProperty dp)
            {
                precise = dp.Precise;
            }
            var str = value.ToString();
            var dotindex = str.IndexOf('.');
            if (str.Length > dotindex + precise + 1)
            {
                return value.ToString($"f{precise}");
            }
            return str;
        }

        public string StringValue
        {
            get
            {
                if (_Property != null && _Property.Value != null && _Property is StringProperty)
                {
                    return (string)_Property.Value;
                }
                return string.Empty;
            }
            set
            {
                _Property.Value = value;
                if (ValueChanged != null)
                {
                    ValueChanged.Invoke(_Property, value);
                }
            }
        }
        public string Value 
        { 
            get
            {
                if (_Property != null && _Property.Value != null && _Property is DoubleProperty dp)
                {
                    return Formatter((double)dp.Value);
                }
                return _Property?.StringValue;
            } 
        }

        public int MinInt 
        { 
            get
            {
                if (_Property != null && _Property is IntegerProperty ip && ip.MinValue != null) return ip.MinValue.Value;
                return int.MinValue;
            }
        }
        public int MaxInt
        {
            get
            {
                if (_Property != null && _Property is IntegerProperty ip && ip.MaxValue != null) return ip.MaxValue.Value;
                return int.MaxValue;
            }
        }
        public double MinDouble
        {
            get
            {
                if (_Property != null && _Property is DoubleProperty dp && dp.MinValue != null) return dp.MinValue.Value;
                return double.MinValue;
            }
        }
        public double MaxDouble
        {
            get
            {
                if (_Property != null && _Property is DoubleProperty dp && dp.MaxValue != null) return dp.MaxValue.Value;
                return double.MaxValue;
            }
        }
        public string Unit
        {
            get
            {
                if (_Property is IntegerProperty ip) return ip.Unit;
                if (_Property is DoubleProperty dp) return dp.Unit;
                return null;
            }
        }
        public List<string> Options
        {
            get
            {
                if (_Property != null && _Property is StringProperty sp)
                {
                    return sp.Options;
                }
                return null;
            }
        }

        List<Data> _Children;
        public List<Data> Children
        {
            get
            {
                if (_Children != null) return _Children;
                if(_Property is ICollectionProperty cp && cp.Children.Count > 0) 
                {
                    _Children = new List<Data>();
                    foreach(var child in cp.Children)
                    {
                        var childData = new Data(child);
                        childData.ValueChanged = ValueChanged;
                        _Children.Add(childData);
                    }
                    return _Children;
                }
                return null;
            }
        }
    }

IProperty接口

    public interface IProperty
    {
        string Name { get; set; }
        string Description { get; set; }
        DataType DataType { get; }
        string Category { get; set; }
        bool Visible { get; set; }
        bool NotNull { get; set;}
        bool Editable { get; set; }
        object Value { get; set; }
        string StringValue { get; set; }
        IProperty Clone();
    }

TypeDef Editor工具

    一个json中可以定义多个类型,每个类型可以对应一种业务对象,类型下面就是属性了。

最后,PropertyGrid运行效果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值