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

Design time serialization of custom type property

所以基本上我有一个自定义 UserControl 包含 Label 对象的私有数组,我希望能够从外部独占访问它们的 Text 属性。

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

为了尝试,我在表单中添加了我的控件并编辑了 属性 的值。所有这些都工作正常,直到我关闭并重新打开设计器并且标签取回它们的默认值。

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

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

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

在使用我的编辑器修改 属性 之后在设计时添加。 我误会了什么 and/or 做错了什么?

自定义类型

[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, 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; }

编辑:

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

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

说明 Label 类型未标记为 [Serializable]

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

我尝试用

替换该行
object codeObject = new CodeObject();

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

我(再一次)假设没有任何事情发生,因为我刚刚创建的 CodeObject 和文件之间没有任何关系(除非该关系是在 [=36= 返回后建立的) ] 方法 ?).

您可能会说,我对 CodeDom 的东西很陌生,所以我应该如何正确地创建这个对象?

编辑 2:

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

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

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

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

所以我缺少控件和 属性 的名称来实现我的最终目标。

我会回答我自己的 post 以概括我在完成后所做的修复工作。

我设法使序列化工作如我所愿,所以我将回顾一下我对最初发布的代码所做的更改。

首先,我的 属性 自定义类型需要一组以便编辑器能够修改。

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

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

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

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;
    }
}

回顾一下我在 class 中所做的更改:

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

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

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

编辑:

在对序列化程序进行全面测试后,我意识到在打开包含我的自定义控件的表单的设计视图时重建包含 LabeltextCollection 类型的程序集时,标签的文本返回到它们的默认值.

原因是 LabeltextCollection 类型的 属性 无法序列化,因为在那种情况下条件 value is LabelTextCollection 为假,因为两个 [=] 之间存在差异22=] 来自不同程序集版本的类型。

为了解决这个问题,我删除了对该类型的任何直接引用并访问了我需要通过 Type class.

调用的方法

这让我得到了以下序列化程序代码:

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, "FromArray", arrayCreation);

        codeObject.Add(methodInvoke);

        return codeObject;
    }
}

您仍然可以使用 Type.Name 测试 value 的类型,但由于我的序列化程序只管理单一类型,因此在我的情况下不需要。