问题描述
因此,基本上我有一个自定义UserControl
,其中包含Label
个对象的私有数组,并且我希望能够从外部专门访问其Text
属性。
因此,我添加了一个属性,其类型为LabelTextCollection
是IEnumerable
的实现,并且将我的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
的类型,但是由于我的序列化程序仅管理一种类型,因此我不需要。