首页 新闻 搜索 专区 学院

WPF自定义控件问题

0
悬赏园豆:100 [已解决问题] 解决于 2022-01-11 15:15

我自定义的一个控件,使用这个控件的模块需要在子节点中设置一些对象及其属性
比如一个曲线图,有左右两个纵坐标,我希望做好之后,在使用这个控件的 xaml 中会这样写
<s:MyChartLine>
<s:MyChartLine.LeftAxis MaxValue="1000" MinValue="-200" Background="Red" />
<s:MyChartLine.RightAxis MaxValue="200" MinValue="-200" Background="Blue" />
</s:MyChartLine>

纵坐标是一个类:
public class YAxis {
public double MaxValue{ get; set;}
public double MinValue{ get; set; }
public Brush Background { get; set; }
}

应该要怎么做?

血狼一族的主页 血狼一族 | 初学一级 | 园豆:73
提问于:2021-07-05 20:36
< >
分享
最佳答案
0
//ChartControl Memory Leak
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Threading;
using L.Bussiness.Services;
using L.Bussiness.画布图元s.读显s.Parts.Charts;
using L.Bussiness.设置面板s;
using L.CommonCtrls;
using L.CommonCtrls.Converters;
using L.LModules.Utils;
using L.LModules.ViewUtils;
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Definitions.Series;
using LiveCharts.Wpf;
using LiveCharts.Wpf.Charts.Base;
using PropertyChanged;
using PropertyTools.DataAnnotations;
using PropertyTools.Wpf;

namespace L.Bussiness.画布图元s.读显s
{

    public class Primitive图表读取器3 : PrimitivePathBindBase
    {
        public class ChartLineBean : IMetric
        {
            public string DevAddress { get; set; }

            [Filed()]
            [AutoUpdateText]
            [System.ComponentModel.Category("曲线|基本设置")]
            [System.ComponentModel.DisplayName("颜色设置")]
            public Color Color { get; set; } = Colors.Transparent;

            [Filed()]
            [AutoUpdateText]
            [System.ComponentModel.Category("曲线|基本设置")]
            [System.ComponentModel.DisplayName("曲率0-1")]
            public bool LineSmoothness { get; set; }

            [Filed()]
            public string Path { get; set; }

            [Filed()]
            public RwModel RwModel { get; set; }

            public IConvertible Value { get; set; }

            public ISeriesView SeriesView { get; set; }
        }
        private DispatcherTimer _dispatcherTimerInterval;

        private DispatcherTimer _dispatcherTimerChart;

        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(10)]
        [HeaderPlacement(HeaderPlacement.Above)]
        [CtrlType(CtrlCategory.ChartList)]
        [Height(160)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("绑定列表")]
        [System.ComponentModel.Description("部分支持多个.")]
        public override ObservableCollection<IMetric> Metrics { get; } = new ObservableCollection<IMetric>();

        [Filed(true)]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(3560)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("动画效果")]
        [System.ComponentModel.Description("会影响性能.")]
        public virtual bool CanAnimations
        {
            get => _canAnimations;
            set
            {
                _canAnimations = value;
                if (ChartCtrl != null)
                {
                    ChartCtrl.DisableAnimations = (!_canAnimations);
                }
            }
        }

        private int _tickInterval = 1;
        [Filed(true)]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(5520)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("周期(秒)")]
        [System.ComponentModel.Description("至多点数.")]
        public int TickInterval
        {
            get => _tickInterval;
            set
            {
                if (value < 1 || value > 7200)
                {
                    this.GetWindowScada()?.MsgWarn("超出范围,范围1-7200");
                    return;
                }
                _tickInterval = value;
                _dispatcherTimerInterval.Interval = TimeSpan.FromSeconds(_tickInterval);
            }
        }

        private int _maxPointsCount = 5;
        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(5620)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("至多周期数")]
        [System.ComponentModel.Description("至多点数.")]
        public int LimitPoints
        {
            get => _maxPointsCount;
            set
            {
                if (value < 3 || value > 200)
                {
                    this.GetWindowScada()?.MsgWarn("超出范围,范围3-200");
                    return;
                }
                _maxPointsCount = value;
            }
        }

        //private int _xLines = 5;
        //[Filed(true)]
        //[DoNotCheckEquality]
        //[AutoUpdateText]
        //[SortIndex(5720)]
        //[System.ComponentModel.Category("图表相关|基本")]
        //[System.ComponentModel.DisplayName("X栅格数")]
        //[System.ComponentModel.Description("X栅格数.")]
        //public int XLines
        //{
        //    get => _xLines;
        //    set
        //    {
        //        if (value < 3 || value > 20)
        //        {
        //            this.GetWindowScada()?.Alert("超出范围,范围3-50");
        //            return;
        //        }
        //        _xLines = value;
        //        if (AxisX?.Separator != null)
        //        {
        //            AxisX.Separator.IsEnabled = true;
        //            AxisX.Separator.Step = TimeSpan.FromSeconds(TickInterval * LimitPoints).Ticks / (double)_xLines;
        //            AxisX.Separator.IsEnabled = false;
        //        }
        //        //AxisX.Separator.Step = TimeSpan.FromSeconds(_maxPointsCount * _tickInterval).Ticks;

        //    }
        //}

        //private int _yLines = 5;
        //[Filed(true)]
        //[DoNotCheckEquality]
        //[AutoUpdateText]
        //[SortIndex(5820)]
        //[System.ComponentModel.Category("图表相关|基本")]
        //[System.ComponentModel.DisplayName("Y格线数")]
        //[System.ComponentModel.Description("Y格线数.")]
        //public int YLines
        //{
        //    get => _yLines;
        //    set
        //    {
        //        if (value < 3 || value > 20)
        //        {
        //            this.GetWindowScada()?.Alert("超出范围,范围3-20");
        //            return;
        //        }
        //        _yLines = value;
        //    }
        //}

        private Color _xColor = ThemeDefine.Default.Text0.ToArgbColor();
        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(6580)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("X栅线色")]
        [System.ComponentModel.Description("背景颜色.")]
        public virtual Color XColor
        {
            get => _xColor;
            set
            {
                _xColor = value;
                if (AxisX?.Separator != null) { AxisX.Separator.Stroke = new SolidColorBrush(value); }
            }
        }

        private Color _xLabelColor = ThemeDefine.Default.Text0.ToArgbColor();
        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(6570)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("X轴字色")]
        [System.ComponentModel.Description("背景颜色.")]
        public virtual Color XLabelColor
        {
            get => _xLabelColor;
            set
            {
                _xLabelColor = value;
                if (AxisX != null) AxisX.Foreground = new SolidColorBrush(value);
            }
        }

        private double _xLabelAngle = 0;
        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [Slidable(0, 360)]
        [SortIndex(6590)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("X字角度")]
        [System.ComponentModel.Description("背景颜色.")]
        public double XLabelAngle
        {
            get => _xLabelAngle;
            set
            {
                if (value < 0 || value > 360)
                {
                    this.GetWindowScada()?.MsgWarn("超出范围,范围3-20");
                    return;
                }
                _xLabelAngle = value;
                if (AxisX != null) AxisX.LabelsRotation = _xLabelAngle;
            }
        }

        private Color _yColor = ThemeDefine.Default.Text0.ToArgbColor();
        [Filed(true)]
        [AutoUpdateText]
        [SortIndex(6780)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("Y栅线色")]
        [System.ComponentModel.Description("背景颜色.")]
        public virtual Color YColor
        {
            get => _yColor;
            set
            {
                _yColor = value;
                if (AxisY?.Separator != null) { AxisY.Separator.Stroke = new SolidColorBrush(value); }
            }
        }

        private Color _yLabelColor = ThemeDefine.Default.Text0.ToArgbColor();
        [Filed]
        [DoNotCheckEquality]
        [AutoUpdateText]
        [SortIndex(6790)]
        [System.ComponentModel.Category("图表相关|基本")]
        [System.ComponentModel.DisplayName("Y轴字色")]
        [System.ComponentModel.Description("背景颜色.")]
        public virtual Color YLabelColor
        {
            get => _yLabelColor;
            set
            {
                _yLabelColor = value;
                if (AxisY != null) AxisY.Foreground = new SolidColorBrush(value);
            }
        }

        public CartesianChartEx ChartCtrl { get; }
        private AxisEx AxisX { get; }
        private Axis AxisY { get; }

        private const string __XFORMAT = "hh:mm:ss";
        public Primitive图表读取器3()
        {
            ChartCtrl = new CartesianChartEx();
            //ChartCtrl.DisableAnimations = true;
            //ChartCtrl.AnimationsSpeed = TimeSpan.FromMilliseconds(999);
            //LiveCharts.Wpf.Components.ChartUpdater updater = (LiveCharts.Wpf.Components.ChartUpdater)ChartCtrl.Model.Updater;

            AxisX = new AxisEx() { DisableAnimations = true, LabelsRotation = 0, LabelFormatter = d => new DateTime((long)(d < 0 ? 0 : d)).ToString(__XFORMAT) };
            AxisX.ShowLabels = true;
            AxisX.IsEnabled = false;
            //AxisX.IsMerged = true;
            AxisX.Unit = 10.0;
            AxisX.Separator.StrokeThickness = 1.0;
            var now = RpcHttp.Current.GetTimeNowByServerBase();
            //AxisX.Labels = Enumerable.Range(0, 5).Select(t => now.Add(TimeSpan.FromSeconds(t)).ToString(__XFORMAT)).ToList();
            ChartCtrl.AxisX = new AxesCollection() { AxisX };

            AxisY = new Axis() { DisableAnimations = true, LabelsRotation = 0, LabelFormatter = d => d.ToString() };
            AxisY.Separator.StrokeThickness = 1.0;
            ChartCtrl.AxisY = new AxesCollection() { AxisY };

            ChartCtrl.Series = new SeriesCollection();

            WidthEx = 300;
            HeightEx = 200;
            CanAnimations = false;
            CornerRadius = new CornerRadius(0);
            BackgroundEx = 0xff0b3869.ToArgbColor();
            WhenMeticsChanged = WhenMeticsChangedMethod;

            _dispatcherTimerChart = new DispatcherTimer
            {
                Interval = TimeSpan.FromMilliseconds(999.0)
            };
            _dispatcherTimerInterval = new DispatcherTimer
            {
                Interval = TimeSpan.FromSeconds(_tickInterval)
            };

            Loaded += OnLoaded;
            Unloaded += OnUnloaded;
            //Mappers.Xy<DateTimePoint>().X()
            //var mapper1 = Mappers.Xy<TickPoint>()
            //    .X((value, index) => value.Time.Ticks)
            //    .Y(value => value.Value.GetHashCode());
            //LiveCharts.Charting.For<TickPoint>(mapper1, SeriesOrientation.Horizontal);
        }
        protected override void OnInitContent(ContentPresenter content)
        {
            content.Content = ChartCtrl;
            LimitPoints = LimitPoints;
            TickInterval = TickInterval;
            CanAnimations = CanAnimations;

            XColor = XColor;
            //XLines = XLines;//dependence on LimitPoints and TickInterval
            XLabelColor = XLabelColor;
            XLabelAngle = XLabelAngle;
            YColor = YColor;
            YLabelColor = YLabelColor;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            _dispatcherTimerChart.Tick += DispatcherTimerOnTick;
            _dispatcherTimerChart.Start();

            _dispatcherTimerInterval.Tick += DispatcherTimerIntervalOnTick;
            _dispatcherTimerInterval.Start();
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            //var updater = (LiveCharts.Wpf.Components.ChartUpdater)ChartCtrl.Model.Updater;
            //updater.Timer.Tick -= updater.OnTimerOnTick;
            //updater.Timer.Stop();
            //updater.Timer.IsEnabled = false;
            //updater.Timer = null;
            //var updater = ChartCtrl.Model.Updater;

            //foreach (var seriesView in ChartCtrl.Series)
            //{
            //    if (seriesView.Values is IDisposable values)
            //        values.Dispose();
            //}

            _dispatcherTimerChart.Tick -= DispatcherTimerOnTick;
            _dispatcherTimerChart.Stop();

            _dispatcherTimerInterval.Tick -= DispatcherTimerIntervalOnTick;
            _dispatcherTimerInterval.Stop();

            ChartCtrl.Series.ForEach(t =>
            {
                t.Values.Clear();
                t.Values.CollectGarbage(t);
            });
        }

        readonly Dictionary<string, IConvertible> _pathValueLast = new Dictionary<string, IConvertible>();
        private void DispatcherTimerIntervalOnTick(object sender, EventArgs e)
        {
            var now = RpcHttp.Current.GetTimeNowByServerBase();
            Metrics.ToList().ForEach(t =>
           {
               var bean = t.AsType<ChartLineBean>();
               if (bean?.SeriesView == null) return;
               if (_pathValueLast.ContainsKey(t.Path))
               {
                   var val = _pathValueLast[t.Path];
                   if (val == null) return;
                   bean.SeriesView.Values?.Add(new DateTimePoint(now, val.ToDouble(null)));
               }
               while (bean.SeriesView?.Values?.Count > LimitPoints) bean.SeriesView?.Values?.RemoveAt(0);
           });
        }

        private void DispatcherTimerOnTick(object sender, EventArgs e)
        {
            ChartCtrl.Update(false, true);
        }

        private int _indexColorUsage = 0;
        private bool _canAnimations;

        private void WhenMeticsChangedMethod(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (IMetric metric in e.NewItems)
                    {
                        var beanMetricAndSeries = metric.AsType<ChartLineBean>();
                        if (beanMetricAndSeries == null) continue;

                        var series = new LineSeries()
                        {
                            Values = new ChartValues<DateTimePoint>() { new DateTimePoint() },
                            Title = metric.Path,
                            PointGeometrySize = 4,
                        };
                        series.DataContext = beanMetricAndSeries;
                        series.Values.Initialize(series);
                        //Enumerable.Range(1,3).ToList().ForEach(t=> series.Values.Add(new DateTimePoint()));
                        series.SetBinding(LineSeries.LineSmoothnessProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.LineSmoothness)) { Converter = new BooleanToDoubleConverter(), Mode = BindingMode.TwoWay });
                        beanMetricAndSeries.SeriesView = series;

                        if (beanMetricAndSeries.Color == Colors.Transparent)
                        {
                            beanMetricAndSeries.Color = ChartSourceEx.Colors[_indexColorUsage % Chart.Colors.Count];
                            _indexColorUsage++;
                        }
                        series.SetBinding(LineSeries.StrokeProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.Color)) { Converter = new ColorToBrushConverter(), Mode = BindingMode.TwoWay });

                        ChartCtrl.Series.Add(series);
                        while (series.Values.Count > 0) series.Values.RemoveAt(0);
                        //ChartCtrl.Update(true, true);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (IMetric metric in e.OldItems)
                    {
                        var beanMetricAndSeries = metric.AsType<ChartLineBean>();
                        if (beanMetricAndSeries == null) continue;
                        ChartCtrl.Series.Remove(beanMetricAndSeries.SeriesView);
                        beanMetricAndSeries.SeriesView = null;

                    }
                    break;
            }

            ChartCtrl.Update(false, true);
            lock (_pathValueLast)
            {
                _pathValueLast.Clear();
                Metrics.ToList().ForEach(t =>
                {
                    _pathValueLast.Add(t.Path, t.Value);
                });
            }
        }

        protected override void InsertMetric(ObservableCollection<IMetric> metrics, IMetric metric)
        {
            //base.InsertMetric(metrics, metric);
            var metircAndSeries = new ChartLineBean() { Path = metric.Path, RwModel = metric.RwModel, Value = metric.Value };
            metrics.Add(metircAndSeries);
        }

        public override void OnValueChanged(IPathValue metric)
        {
            if (_pathValueLast.ContainsKey(metric.Path)) _pathValueLast[metric.Path] = metric.Value;

            //var metircAndSeries = Metrics.FirstOrDefault(t => t.Path == metric.Path)?.AsType<Bean>();
            //if (metircAndSeries == null) return;
            //metircAndSeries.SeriesView?.Values?.Add(new DateTimePoint(InternalRestClient.Current.GetTimeNowByServerBase(), metric.Value.ToDouble(null)));
            //while (metircAndSeries.SeriesView?.Values?.Count > LimitPoints) metircAndSeries.SeriesView?.Values?.RemoveAt(0);
        }

        public void Dispose()
        {
            ChartCtrl.Series.Clear();
        }

        ~Primitive图表读取器3()
        {
            _dispatcherTimerInterval?.Stop();
            _dispatcherTimerInterval = null;

            _dispatcherTimerChart?.Stop();
            _dispatcherTimerChart = null;
        }
    }
}

该控件有内存泄漏问题,上述代码可用,但已未用。

收获园豆:50
花飘水流兮 | 专家六级 |园豆:13267 | 2021-07-06 09:57

其实控件我已经写好并测试通过了,功能基本满足需求
但是在使用这个控件时,放到窗体的 xaml 不方便,不能象WPF自带的控件那样可以嵌套写信息
比如我定义了两个纵坐标,想设置不同的属性,我希望调用时可以这样写:
<c:MyChartLine x:Name="chartLine1">
<LeftAxis Color="Black" MaxValue="900" MinValue="-100" />
<rightAxis Color="Blue" MaxValue="100" MinValue="-50" />
</c:MyChartLine>

但现在不知道怎么做成这种嵌套设置,所以只能这样写
<c:MyChartLine x:Name="chartLine1" MaxLeftAxis="900" MinLeftAxis="-100" LeftAxisColor="Black" ...... />

血狼一族 | 园豆:73 (初学一级) | 2021-07-06 10:20

@血狼一族: 写个派生类就搞定了

花飘水流兮 | 园豆:13267 (专家六级) | 2021-07-06 13:22

@花飘水流兮:
什么派生类

血狼一族 | 园豆:73 (初学一级) | 2021-07-06 13:56

@血狼一族: 

class CartesianChartEx:CartesianChart
{
      需要什么属性 自己加;
}
花飘水流兮 | 园豆:13267 (专家六级) | 2021-07-06 15:17

您老人家根本没弄明白我想问的是啥

血狼一族 | 园豆:73 (初学一级) | 2021-07-06 16:37

@血狼一族: 貌似看反了~;

 不过道理是一样的,至于实现可能需要些更多而已。

class AxisEx:Axis{需要什么属性 自己加;}

如果Chart才是他控件的参数,顶多再自行设置Axis.MaxValue时,去设置Chart的对应属性。

再次说一下,该控件内存泄漏,作者已经不维护,消耗比较大。

推荐 xaml只做基础视图结构,不要做业务逻辑设置;

如绑定推荐:series.SetBinding(LineSeries.StrokeProperty, new Binding(ClassEx.GetPropertyName<ChartLineBean>(t => t.Color)) { Converter = new ColorToBrushConverter(), Mode = BindingMode.TwoWay });

 这样可以编译时报错,Bean变更也不至于不能智能替换。

花飘水流兮 | 园豆:13267 (专家六级) | 2021-07-06 17:16

@花飘水流兮:
我强调一下,我不是想知道怎么做图表控件,我的图表控件已经做好了并且应用到项目里去了。控件是完全自己一点点写出来的,基本上满足项目需要。我拿图表控件说事,只是举个例子说明我想知道的方法而已。

但是由于一堆参数要设置,象左右两个纵坐标轴都要设置最大值、最小值、刻度大小、颜色等,还有横坐标轴也有一堆信息要设置,现在在调用控件时,我只能写成类似:
<local:ChartLine LeftYAxis_MaxValue="100" LeftYAxis_MinValue="-100" LeftYAxis_Scale="20" RightYAxis_MaxValue="200" RightYAxis_MinValue="-100" RightYAxis_Scale="30" XAxis_Range="1000" />

这样写 xaml 很 low,并且维护也不方便,代码也不好阅读
我希望在窗体中的 xaml 在调用控件时,写成这种结构化的形式:
local:ChartLine
<LeftYAxis MinValue="-100" MaxValue="100" Scale="20" />
<RighttYAxis MinValue="-100" MaxValue="200" Scale="30" />
<XAxis Range="1000" />
</local:ChartLine>

这才是我想知道的
我的项目里可能还有很多控件要做,我不想都写成前面的那各自形式,而是后面这种结构化的形式

血狼一族 | 园豆:73 (初学一级) | 2021-07-06 20:51
其他回答(1)
0

在自定义控件中,设置依赖属性,在自定义控件的xaml中,绑定其后台的依赖属性,其语法为:
<lvc:Axis MaxValue="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Max }"/> ,其中的Max就是自定义的依赖属性。在使用控件时,定义一个具有自动通知功能的属性,绑到自定义控件的自定义属性上,就可以。希望对你有所启发。

收获园豆:50
zch半缘修道半缘君 | 园豆:256 (菜鸟二级) | 2021-07-05 22:20

你好
你说的 xaml 中绑定依赖属性应该放在哪个元素里?
我这里的自定义控件全部是放在 Grid 里的, Axis 只是简单的C#类

支持(0) 反对(0) 血狼一族 | 园豆:73 (初学一级) | 2021-07-05 23:44

@血狼一族: 需要对外暴露的属性,都需要定义为依赖属性。定义依赖属性,有固定的格式,定义在控件的cs文件中。编译以后,在主窗体的xaml文件中,引用它,此时,就能直接使用你定义的依赖属性(也即你想要对外暴露的属性)。

支持(0) 反对(0) zch半缘修道半缘君 | 园豆:256 (菜鸟二级) | 2021-07-06 10:24

@HangPuWind:
你说的定义成依赖属性,是不是 DependencyProperty 那些操作?这些属性我都定义过了的。
在我的工程里,即使不定义成依赖属性,如果属性是个简单类型,也可以被暴露给调用的一方。比如
public class MyControl : UserControl {
public int MaxValue { get; set; }
}

这样在调用时,在我的VS里是可以这样写的:
<local:MyControl MaxValue="10"></local:MyControl>

但是我定义的那个依赖属性的类型本身又是一个类类型,单纯地在 xml 元素的属性里是设置不完的
public class Axis {
public double MaxValue{ get; set;}
public double MinValue { get; set; }
// 其它复杂类型
}
public MyControl : UserControl {
public static readonly DependencyProperty XAxisProperty = DependencyProperty.Register("XAxis", typeof(Axis), typeof(MyControl));
public Axis XAxis {
get{
return GetValue(XAxisProperty) as Axis;
}
set{
SetValue(AxisProperty, value);
}
}

这样,在调用时,我希望能够写成:
local:MyControl
<XAxis MaxValue="100" MinValue="-100" />
</local:MyControl>
因为,在我的这个控件里,可能还会有更多的象 Axis 这样的类类型的属性,甚至更复杂,比如 Axis 里还有属性是类类型的

支持(0) 反对(0) 血狼一族 | 园豆:73 (初学一级) | 2021-07-06 17:01

@血狼一族: 可以试试附加属性。

支持(0) 反对(0) zch半缘修道半缘君 | 园豆:256 (菜鸟二级) | 2021-07-08 14:49
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册