自定义类型的设计时间序列化 属性
Design time serialization of custom type property
所以基本上我有一个自定义 UserControl
包含 Label
对象的私有数组,我希望能够从外部独占访问它们的 Text
属性。
因此,我添加了一个 属性,类型 LabelTextCollection
是 IEnumerable
的一个实现,并将我的 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
的类型,但由于我的序列化程序只管理单一类型,因此在我的情况下不需要。
所以基本上我有一个自定义 UserControl
包含 Label
对象的私有数组,我希望能够从外部独占访问它们的 Text
属性。
因此,我添加了一个 属性,类型 LabelTextCollection
是 IEnumerable
的一个实现,并将我的 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
的类型,但由于我的序列化程序只管理单一类型,因此在我的情况下不需要。