如何在StripLines集合(轴)(序列化)中识别StripLine?

时间:2019-07-09 19:01:47

标签: c# winforms mschart

我正在写一种MsCharts设计器。 -设计图,ChartAreas,系列,... -通过标准的System.Windows.Forms.DataVisualization.Charting.Chart.Chart.ChartSerializer

保存对象

我希望用户能够向轴添加多个带状线。 我正在尝试在Axis的StripLines集合内标识一个StripLine。

StripLine的Name属性是只读的(get,没有设置)。 我看不到实际设置Name属性的方法。 我不明白这有什么用?

我本来要使用StripLine的Tag属性,但可惜Tag属性没有序列化。 注意:

如果我编辑序列化图表并将Tag =“ AStripLine”添加到元素,然后通过Chart.ChartSerializer加载,则Tag =值实际上就在其中。

如果我通过Chart保存/序列化图表。ChartSerializer标记未保存。

任何帮助/想法都将不胜感激。

2 个答案:

答案 0 :(得分:1)

Tag属性的类型为object,并用内部属性修饰,该属性指示序列化程序不要序列化Tag属性。因此,行为是预期的。

但是,由于序列化程序依赖于TypeDescriptor,因此您可以以不同的方式为TypeDescriptor类描述StripLine属性创建新的Tag,例如:

  • 使其可在属性网格中浏览
  • 使其在属性网格中可编辑
  • 使其可序列化以用于图表序列化器

因此它会使用以下格式正确地序列化和反序列化它,例如:

<StripLine Text="text1" Tag="1" />

还在运行时在属性网格中显示它:

enter image description here

您需要创建以下类:

  • StripLineTypeDescriptionProvider:帮助为StripLine注册新的类型描述符
  • StripLineTypeDescriptor:描述类型的属性,并允许您更改Tag属性的行为。在此类中,我们重写GetProperties并将Tag属性替换为修改后的属性描述符,该属性描述符告诉序列化程序对Tag进行序列化,并告诉属性网格对其进行显示并使其可编辑。 / li>
  • MyPropertyDescriptor:帮助我们指定Tag属性的新类型。您可以决定将其设置为字符串,int或什至是复杂类型。类型可以在字符串之间来回转换就足够了。

然后就足以在构造器中注册StripLine的类型描述符或以以下形式加载事件:

var provider = new StripLineTypeDescriptionProvider();
TypeDescriptor.AddProvider(provider, typeof(StripLine));

实施

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
public class StripLineTypeDescriptionProvider : TypeDescriptionProvider
{
    public StripLineTypeDescriptionProvider()
       : base(TypeDescriptor.GetProvider(typeof(object))) { }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);
        return new StripLineTypeDescriptor(baseDescriptor);
    }
}
public class StripLineTypeDescriptor : CustomTypeDescriptor
{
    ICustomTypeDescriptor original;
    public StripLineTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
        : base(originalDescriptor)
    {
        original = originalDescriptor;
    }
    public override PropertyDescriptorCollection GetProperties()
    {
        return this.GetProperties(new Attribute[] { });
    }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>().ToList();
        var tag = properties.Where(x => x.Name == "Tag").FirstOrDefault();
        var tagAttributes = tag.Attributes.Cast<Attribute>()
            .Where(x => x.GetType() != typeof(BrowsableAttribute)).ToList();
        var serializationAttribute = tagAttributes.Single(
            x => x.GetType().FullName == "System.Windows.Forms.DataVisualization.Charting.Utilities.SerializationVisibilityAttribute");
        var visibility = serializationAttribute.GetType().GetField("_visibility",
            System.Reflection.BindingFlags.NonPublic |
             System.Reflection.BindingFlags.Instance);
        visibility.SetValue(serializationAttribute, Enum.Parse(visibility.FieldType, "Attribute"));
        tagAttributes.Add(new BrowsableAttribute(true));
        var newTag = new MyPropertyDescriptor(tag, tagAttributes.ToArray());
        properties.Remove(tag);
        properties.Add(newTag);
        return new PropertyDescriptorCollection(properties.ToArray());
    }
}
public class MyPropertyDescriptor : PropertyDescriptor
{
    PropertyDescriptor o;
    public MyPropertyDescriptor(PropertyDescriptor originalProperty,
        Attribute[] attributes) : base(originalProperty)
    {
        o = originalProperty;
        AttributeArray = attributes;
    }
    public override bool CanResetValue(object component)
    { return o.CanResetValue(component); }
    public override object GetValue(object component) => o.GetValue(component);
    public override void ResetValue(object component) { o.ResetValue(component); }
    public override void SetValue(object component, object value) { o.SetValue(component, value); }
    public override bool ShouldSerializeValue(object component) => true;
    public override AttributeCollection Attributes => new AttributeCollection(AttributeArray);
    public override Type ComponentType => o.ComponentType;
    public override bool IsReadOnly => false;
    public override Type PropertyType => typeof(string);
}

参考

以下是类的源代码,可帮助您了解图表序列化的工作原理:

答案 1 :(得分:0)

这确实是一个奇怪的发现!

起初我以为Names可能会以一种有用的方式自动生成,例如StripLine1StripLine2等。

但是他们都以StripLine作为Name

因此,对于您识别它们将毫无用处。

但是有Tag属性可以解决。这很容易设置为唯一字符串。.

StripLine sl = new StripLine()
    { Text = "LW" , StripWidth = 2, ForeColor = Color.Teal, Tag = "Low-Water"};

要使其对于轴AxisY唯一,可以使用以下方法:

StripLine sl = new StripLine()
    { Text = "LW" , StripWidth = 2, ForeColor = Color.Teal, 
      Tag = "Low-Water" + chart1.ChartAreas[0].AxisY.StripLines.Count };

由于Tag的类型为object,因此您可以创建一个类来保存更多信息,例如短名称和说明。

更新:我刚刚注意到您了解Tags以及如何不对其进行序列化。但是,您可以使用以下解决方法:

  • 在进行序列化之前,请遍历所有StripLines并将Text更改为:oldText +分隔符+标记字符串。
  • 反序列化后,请执行相反的操作。

作为分隔符,您可以使用制表符(\ t)或文本中不需要的其他字符(或字符串)。(垂直制表符,我的初衷是不允许使用xml实体。)

这是准备Text并重建Tags的功能:

void StripLineTagger(Chart chart, bool beforeSer)
{
    char sep = '\t';

    var axes = new List<Axis> { chart.ChartAreas[0].AxisX, chart.ChartAreas[0].AxisX2,
    chart.ChartAreas[0].AxisY, chart.ChartAreas[0].AxisY2};

    foreach (var ax in axes)
        foreach (var sl in ax.StripLines)
        {
            if (beforeSer) sl.Text = sl.Text + sep + sl.Tag.ToString();
            else
            {
                var p = sl.Text.Split(sep);
                sl.Text = p[0];
                sl.Tag = p[1];
            }
    }
}

这是未经测试的,缺少所有检查。.

更新2:

您可以添加自己的子类来替换常规的StripLines

class MyStripLine : StripLine
{
    new public string Name { get; set;  }  // looks fine butwon't get serialized
    public string ID{ get; set;  }         // gets serialized

    //..

    public MyStripLine()
    {

    }
}

可以将它们添加到StripLines集合中并按预期工作。不幸的是Name属性看起来不错,但是没有写出来。.使用另一属性(ID)很简单,我无法进行反序列化工作。