自定义类型属性的设计时序列化

问题描述

因此,基本上我有一个自定义UserControl,其中包含Label个对象的私有数组,并且我希望能够从外部专门访问其Text属性

因此,我添加一个属性,其类型为LabelTextCollectionIEnumerable的实现,并且将我的Label数组作为其内部列表。此外,我添加UITypeEditor的实现以允许从Windows窗体设计器进行编辑。

要尝试一下,我以表格形式添加了控件并编辑了属性的值。在关闭并重新打开设计器,并且标签恢复其认值之前,所有这些操作均正常。

环顾四周之后,似乎必须添加CodeDomSerializer的实现,以允许我的类型在设计时成功序列化到{Form}.Designer.cs文件中。我先尝试序列化注释行以对其进行测试,但未生成任何代码

我的最终目标是要有这样的一行

this.{controlName}.Titles.FromArray(new string[] { "Whatever" } )

在使用我的编辑器修改属性后的设计时添加我有什么误解和/或做错了什么?

自定义类型

[DesignerSerializer(typeof(LabelTextCollectionSerializer),typeof(CodeDomSerializer))]
public class LabelTextCollection : IEnumerable<string>,IEnumerable
{
    private Label[] labels;

    public LabelTextCollection(Label[] labels)
    {
        this.labels = labels;
    }

    public void SetLabels(Label[] labels)
    {
        this.labels = labels;
    }

    public IEnumerator<string> GetEnumerator()
    {
        return new LabelTextEnum(labels);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return new LabelTextEnum(labels);
    }

    public string this[int index]
    {
        get { return labels[index].Text; }
        set { labels[index].Text = value; }
    }

    public override string ToString()
    {
        if (labels.Length == 0) return string.Empty;
        else
        {
            StringBuilder sb = new StringBuilder("{ ");
            foreach (string label in this)
            {
                sb.Append(label);
                if (label == this.Last()) sb.Append(" }");
                else sb.Append(",");
            }
            return sb.ToString();
        }
    }

    public string[] ToArray()
    {
        string[] arr = new string[labels.Length];
        for (int i = 0; i < labels.Length; i++) arr[i] = labels[i].Text;
        return arr;
    }

    public void FromArray(string[] arr)
    {
        for(int i = 0; i < arr.Length; i++)
        {
            if (i >= labels.Length) break;
            else labels[i].Text = arr[i];
        }
    }

    public class LabelTextEnum : IEnumerator<string>,IEnumerator
    {
        private readonly Label[] labels;
        private int position = -1;

        public LabelTextEnum(Label[] labels)
        {
            this.labels = labels;
        }

        public object Current
        {
            get
            {
                try
                {
                    return labels[position].Text;
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        string IEnumerator<string>.Current { get { return (string)Current; } }

        public void dispose()
        {
            return;
        }

        public bool MoveNext()
        {
            return ++position < labels.Length;
        }

        public void Reset()
        {
            position = -1;
        }
    }
}

类型编辑器

public class LabelTextCollectionEditor : UITypeEditor
{
    IWindowsFormsEditorService _service;
    IComponentChangeService _changeService;

    public override object EditValue(ITypeDescriptorContext context,IServiceProvider provider,object value)
    {
        if (provider != null)
        {
            _service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
            _changeService = (IComponentChangeService)provider.GetService(typeof(IComponentChangeService));

            if (_service != null && _changeService != null && value is LabelTextCollection)
            {
                LabelTextCollection property = (LabelTextCollection)value;

                LabelTextCollectionForm form = new LabelTextCollectionForm() { Items = property.ToArray() };

                if (_service.ShowDialog(form) == DialogResult.OK)
                {
                    property.FromArray(form.Items);
                    value = property;
                    _changeService.OnComponentChanged(value,null,null);
                }
            }
        }

        return value;
    }

    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }
}

序列化器

public class LabelTextCollectionSerializer : CodeDomSerializer
{
    public override object Serialize(IDesignerSerializationManager manager,object value)
    {
        var baseSerializer = (CodeDomSerializer)manager.GetSerializer( typeof(LabelTextCollection).BaseType,typeof(CodeDomSerializer));
        object codeObject = baseSerializer.Serialize(manager,value);

        if (codeObject is CodeStatementCollection && value is LabelTextCollection)
        {
            var col = value as LabelTextCollection;
            var statements = (CodeStatementCollection)codeObject;
            statements.Add(new CodeCommentStatement("LabelTextCollection : " + col.ToString()));
        }

        return codeObject;
    }
}

自定义类型的属性

[Category("Appearance")]
[Editor(typeof(LabelTextCollectionEditor),typeof(UITypeEditor))]
public LabelTextCollection Titles { get; }

编辑:

我在set属性添加Titles并设置了项目以进行设计时调试,然后我意识到在行上抛出了异常

object codeObject = baseSerializer.Serialize(manager,value);

指出Label类型未标记[Serializable]

我假设基本序列化程序正在尝试向我的LabelTextCollection构造函数一个调用,并将labels字段序列化为它的参数。

我尝试将行替换为

object codeObject = new CodeObject();

摆脱了异常,但没有在designer.cs文件中写入任何内容

我(再次)假设没有任何反应,因为我刚刚创建的CodeObject文件之间没有任何关系(除非通过Serialize方法返回该关系之后建立了该关系?)。

您可能会说,关于CodeDom的东西我还很新,所以我应该如何正确地创建此对象?

编辑2:

我太笨了...我忘记了codeObject is CodeStatementCollection测试...

所以注释行写得很好,现在我要做的就是用CodeDom编写正确的行,它应该可以正常工作。

如果有人想要帮助,我目前已添加designer.cs文件中:

this.FromArray( new string[] { "TEST" } );

因此,我缺少控件名称属性名称,无法达到我的最终目标。

我将回答我自己的帖子,以概括一下完成该操作时我所做的工作。

解决方法

我设法按预期进行了序列化工作,因此我将回顾一下我最初发布的代码所做的更改。

首先,我的自定义类型属性需要一个可以由编辑器修改的集合。

[Editor(typeof(LabelTextCollectionEditor),typeof(UITypeEditor))]
public LabelTextCollection Titles { get; set; }

我错误地认为该属性的值正在更改,因为标签的文本在使用编辑器后在设计器中有效地更改了。 发生这种情况是因为编辑器可以通过使用LabelTextCollection.FromArray方法访问对内部标签数组的引用。 使用设置器,现在可以在设计时正确编辑属性。

其余的所有更改都在序列化程序中,所以我要发布整个更新的代码:

public class LabelTextCollectionSerializer : CodeDomSerializer
{
    public override object Serialize(IDesignerSerializationManager manager,object value)
    {
        CodeStatementCollection codeObject = new CodeStatementCollection();

        if (value is LabelTextCollection)
        {
            LabelTextCollection col = value as LabelTextCollection;

            // Building the new string[] {} statement with the labels' texts as parameters
            CodeExpression[] strings = new CodeExpression[col.Count()];
            for (int i = 0; i < col.Count(); i++) strings[i] = new CodePrimitiveExpression(col[i]);
            CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]),strings);

            // Building the call to the FromArray method of the currently serializing LabelTextCollection instance
            ExpressionContext context = manager.Context.Current as ExpressionContext;
            CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression,"FromArray",arrayCreation);

            codeObject.Add(methodInvoke);
        }

        return codeObject;
    }
}

回顾一下我在该课程中所做的更改:

  • 删除了对baseSerializer.Serialize方法的调用以自行管理整个序列化
  • codeObject变量初始化为new CodeStatementCollection
  • 使用CodeDom构建对LabelTextCollection.FromArray方法的调用

所有这些现在都成功地将我想要的行写入了Designer.cs文件中。

PS: 感谢@TnTinMn的帮助和朝正确方向的推动。

编辑:

在对序列化程序进行了彻底的测试之后,我意识到在重新构建包含LabeltextCollection类型的程序集并打开包含我的自定义控件的表单的设计视图时,标签的文本会恢复为默认值。 / p>

其原因是LabeltextCollection类型的属性无法序列化,因为在这种情况下条件value is LabelTextCollection为假,因为两种LabelTextCollection类型之间存在差异程序集版本。

为解决此问题,我删除了对类型的任何直接引用,并访问了需要通过Type类调用的方法。

那让我得到了以下序列化器代码:

public class LabelTextCollectionSerializer : CodeDomSerializer
{
    public override object Serialize(IDesignerSerializationManager manager,object value)
    {
        CodeStatementCollection codeObject = new CodeStatementCollection();

        // Building the new string[] {} statement with the labels' texts as parameters            
        string[] texts = value.GetType().GetMethod("ToArray").Invoke(value,null) as string[];
        CodeExpression[] strings = new CodeExpression[texts.Length];
        for (int i = 0; i < texts.Length; i++) strings[i] = new CodePrimitiveExpression(texts[i]);
        CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]),strings);

        // Building the call to the FromArray method of the currently serializing LabelTextCollection instance
        ExpressionContext context = manager.Context.Current as ExpressionContext;
        CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression,arrayCreation);

        codeObject.Add(methodInvoke);

        return codeObject;
    }
}

您仍然可以使用value测试Type.Name的类型,但是由于我的序列化程序仅管理一种类型,因此我不需要。