集合编辑器数据在设计时丢失
Collection Editor data lost at design time
我正在尝试制作一个带有 Collection<T>
作为 属性 的 WinForms 用户控件(其中 T 代表一些自定义 classes)。我已经阅读了很多关于这个主题的文章,但是我不能让它在设计时正常工作(在运行时一切正常)。更准确地说:当我单击 属性 window 中的“...”按钮时,集合编辑器显示正常,我可以添加和删除项目。但是当我单击确定按钮时没有任何反应,当我重新打开集合编辑器时,所有项目都丢失了。当我查看设计器文件时,我看到我的 属性 被分配给 null,而不是组合集合。我将向您展示最重要的代码:
用户控件:
[Browsable(true),
Description("The different steps displayed in the control."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor))]
public StepCollection Steps
{
get
{
return wizardSteps;
}
set
{
wizardSteps = value;
UpdateView(true);
}
}
StepCollection class:
public class StepCollection : System.Collections.CollectionBase
{
public StepCollection() : base() { }
public void Add(Step item) { List.Add(item); }
public void Remove(int index) { List.RemoveAt(index); }
public Step this[int index]
{
get { return (Step)List[index]; }
}
}
步骤class:
[ToolboxItem(false),
DesignTimeVisible(false),
Serializable()]
public class Step : Component
{
public Step(string name) : this(name, null, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps) : this(name, subSteps, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps, StepLayout stepLayout)
{
this.Name = name;
this.SubSteps = subSteps;
this.Layout = stepLayout;
}
// In order to provide design-time support, a default constructor without parameters is required:
public static int NEW_ITEM_ID = 1;
public Step()
: this("Step" + NEW_ITEM_ID, null, StepLayout.DEFAULT_LAYOUT)
{
NEW_ITEM_ID++;
}
// Some more properties
}
CustomCollectionEditor:
class CustomCollectionEditor : CollectionEditor
{
private ITypeDescriptorContext mContext;
public CustomCollectionEditor(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
mContext = context;
return base.EditValue(context, provider, value);
}
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(Step))
{
Step s = (Step)base.CreateInstance(itemType);
s.parentContext = mContext; // Each step needs a reference to its parentContext at design time
return s;
}
return base.CreateInstance(itemType);
}
}
我已经尝试过的东西:
- 使步骤 class 成为此处所述的组件:http://www.codeproject.com/Articles/5372/How-to-Edit-and-Persist-Collections-with-Collectio
- 将
Collection<Step>
更改为自定义集合class StepCollection
继承System.Collections.CollectionBase(在之前的代码项目文章中也有介绍)
- 按照此处所述将 DesignerSerializationVisibility 设置为 Content:Collection Editor within a User Control at Design Time 当它设置为 Visible 时,设计器将 null 分配给我的 属性 ;当它设置为 Content 时,设计器不分配任何内容。
- 我也发现了这个:How to make a UserControl with a Collection that can be edited at design time? 但 CollectionBase class 已经为我做了这个。
- 调试了很多,但由于没有异常,我真的不知道出了什么问题。当我向 collectionForm 的关闭事件添加事件侦听器时,我可以看到 EditValue 属性(collectionForm 的)仍然为空,即使我在集合编辑器中添加了几个步骤也是如此。但我也不知道这是为什么...
完成这个 post 时,我刚找到这个主题:Simplest way to edit a collection in DesignMode?
这与我遇到的问题完全相同,但是我无法使用建议的答案,因为我没有使用标准集合。
查看 CodeProject 上的这篇 Greate 文章,我测试了它们,它们都有效。
Editing Multiple Types of Objects with Collection Editor and Serializing Objects
How to Edit and Persist Collections with CollectionEditor(你提到它不起作用,但我检查了一下它确实有效)
我认为你没有应用的主要关键区别:
- 支持您的 Collection
的 PropertyChanged
- 正在为支持 InstanceDescriptor 的 Collection 项目 Class 创建 TypeConverter。
Reza Aghaei 提到的文章真的很有趣。但是,我认为我已经接近解决我的问题的更简单方法:
正如我已经注意到的,collectionForm 的 EditValue 属性 保持为空,尽管向 collection 添加了项目。现在,我不确定 collection 编辑器的 EditValue 方法内部发生了什么,但我猜它捕获了一个异常,因为我的 collection 的初始值为空(它没有在构造函数),因此返回 null 而不是创建新的 collection。通过在我的自定义 collection 编辑器 class 中进行以下更改,我得到了非常有希望的结果:
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
mContext = context;
if (value == null) value = new Collection<Step>();
Collection<Step> result = (Collection<Step>)base.EditValue(context, provider, value);
if (result != null && result.Count == 0) return null;
return result;
}
注意方法中的第二行,它为初始值分配了一个新的 Collection。通过这样做,我的 collection 得以保留并且一切正常。
我现在唯一想修复的是设计器文件的序列化。目前正在制作这样的东西:
// wizardStepsControl1
// ...
this.wizardStepsControl1.Steps.Add(this.step1);
// ...
// step1
// Initialization of step1
此代码将给出异常,因为 wizardStepsControl1.Steps 从未初始化为 collection。我想要制作的是这样的:
this.wizardStepsControl1.Steps = new Collection<Step>();
this.wizardStepsControl1.Steps.Add(step1);
// ...
更好的是,整个 collection 首先被初始化,然后分配给我的控件的步骤 属性。
我会看看我能做些什么让它工作,post 在这里进行一些更新,也许需要实现 InstanceDescriptor 或使我的自定义 Collection class 继承自 Component (因为组件总是在设计器文件中初始化)。
我知道这是一个与我的第一个问题完全不同的问题,所以也许我会为此开始一个新问题。但是,如果有人已经知道答案,那么在这里听到它会很棒!
更新:
我找到了解决问题的方法。
无法从 Component 和 CollectionBase 继承,因为 C# 不允许这样做。实施将我的自定义 collection 转换为 InstanceDescriptor 的 TypeConverter 也不起作用(我不知道为什么,我猜这是因为 Collection 的序列化方式与普通自定义 class).
但是通过创建 CodeDomSerializer
,我能够将代码添加到生成的设计器代码中。这样我就可以初始化我的 collection 如果在设计时添加了一些项目:
public class WizardStepsSerializer : CodeDomSerializer
{
/// <summary>
/// We customize the output from the default serializer here, adding
/// a comment and an extra line of code.
/// </summary>
public override object Serialize(IDesignerSerializationManager manager, object value)
{
// first, locate and invoke the default serializer for
// the ButtonArray's base class (UserControl)
//
CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(WizardStepsControl).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
// now add some custom code
//
if (codeObject is CodeStatementCollection)
{
// add a custom comment to the code.
//
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
statements.Insert(4, new CodeCommentStatement("This is a custom comment added by a custom serializer on " + DateTime.Now.ToLongDateString()));
// call a custom method.
//
CodeExpression targetObject = base.SerializeToExpression(manager, value);
WizardStepsControl wsc = (WizardStepsControl)value;
if (targetObject != null && wsc.Steps != null)
{
CodePropertyReferenceExpression leftNode = new CodePropertyReferenceExpression(targetObject, "Steps");
CodeObjectCreateExpression rightNode = new CodeObjectCreateExpression(typeof(Collection<Step>));
CodeAssignStatement initializeStepsStatement = new CodeAssignStatement(leftNode, rightNode);
statements.Insert(5, initializeStepsStatement);
}
}
// finally, return the statements that have been created
return codeObject;
}
}
通过 DesignerSerializerAttribute
将此序列化程序与我的自定义控件相关联,在设计器文件中生成以下代码:
//
// wizardStepsControl1
//
// This is a custom comment added by a custom serializer on vrijdag 4 september 2015
this.wizardStepsControl1.Steps = new System.Collections.ObjectModel.Collection<WizardUserControl.Step>();
// ...
this.wizardStepsControl1.Steps.Add(step1);
// ...
这正是我想要的。
中获取了大部分代码
我正在尝试制作一个带有 Collection<T>
作为 属性 的 WinForms 用户控件(其中 T 代表一些自定义 classes)。我已经阅读了很多关于这个主题的文章,但是我不能让它在设计时正常工作(在运行时一切正常)。更准确地说:当我单击 属性 window 中的“...”按钮时,集合编辑器显示正常,我可以添加和删除项目。但是当我单击确定按钮时没有任何反应,当我重新打开集合编辑器时,所有项目都丢失了。当我查看设计器文件时,我看到我的 属性 被分配给 null,而不是组合集合。我将向您展示最重要的代码:
用户控件:
[Browsable(true),
Description("The different steps displayed in the control."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor))]
public StepCollection Steps
{
get
{
return wizardSteps;
}
set
{
wizardSteps = value;
UpdateView(true);
}
}
StepCollection class:
public class StepCollection : System.Collections.CollectionBase
{
public StepCollection() : base() { }
public void Add(Step item) { List.Add(item); }
public void Remove(int index) { List.RemoveAt(index); }
public Step this[int index]
{
get { return (Step)List[index]; }
}
}
步骤class:
[ToolboxItem(false),
DesignTimeVisible(false),
Serializable()]
public class Step : Component
{
public Step(string name) : this(name, null, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps) : this(name, subSteps, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps, StepLayout stepLayout)
{
this.Name = name;
this.SubSteps = subSteps;
this.Layout = stepLayout;
}
// In order to provide design-time support, a default constructor without parameters is required:
public static int NEW_ITEM_ID = 1;
public Step()
: this("Step" + NEW_ITEM_ID, null, StepLayout.DEFAULT_LAYOUT)
{
NEW_ITEM_ID++;
}
// Some more properties
}
CustomCollectionEditor:
class CustomCollectionEditor : CollectionEditor
{
private ITypeDescriptorContext mContext;
public CustomCollectionEditor(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
mContext = context;
return base.EditValue(context, provider, value);
}
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(Step))
{
Step s = (Step)base.CreateInstance(itemType);
s.parentContext = mContext; // Each step needs a reference to its parentContext at design time
return s;
}
return base.CreateInstance(itemType);
}
}
我已经尝试过的东西:
- 使步骤 class 成为此处所述的组件:http://www.codeproject.com/Articles/5372/How-to-Edit-and-Persist-Collections-with-Collectio
- 将
Collection<Step>
更改为自定义集合classStepCollection
继承System.Collections.CollectionBase(在之前的代码项目文章中也有介绍) - 按照此处所述将 DesignerSerializationVisibility 设置为 Content:Collection Editor within a User Control at Design Time 当它设置为 Visible 时,设计器将 null 分配给我的 属性 ;当它设置为 Content 时,设计器不分配任何内容。
- 我也发现了这个:How to make a UserControl with a Collection that can be edited at design time? 但 CollectionBase class 已经为我做了这个。
- 调试了很多,但由于没有异常,我真的不知道出了什么问题。当我向 collectionForm 的关闭事件添加事件侦听器时,我可以看到 EditValue 属性(collectionForm 的)仍然为空,即使我在集合编辑器中添加了几个步骤也是如此。但我也不知道这是为什么...
完成这个 post 时,我刚找到这个主题:Simplest way to edit a collection in DesignMode? 这与我遇到的问题完全相同,但是我无法使用建议的答案,因为我没有使用标准集合。
查看 CodeProject 上的这篇 Greate 文章,我测试了它们,它们都有效。
Editing Multiple Types of Objects with Collection Editor and Serializing Objects
How to Edit and Persist Collections with CollectionEditor(你提到它不起作用,但我检查了一下它确实有效)
我认为你没有应用的主要关键区别:
- 支持您的 Collection 的 PropertyChanged
- 正在为支持 InstanceDescriptor 的 Collection 项目 Class 创建 TypeConverter。
Reza Aghaei 提到的文章真的很有趣。但是,我认为我已经接近解决我的问题的更简单方法:
正如我已经注意到的,collectionForm 的 EditValue 属性 保持为空,尽管向 collection 添加了项目。现在,我不确定 collection 编辑器的 EditValue 方法内部发生了什么,但我猜它捕获了一个异常,因为我的 collection 的初始值为空(它没有在构造函数),因此返回 null 而不是创建新的 collection。通过在我的自定义 collection 编辑器 class 中进行以下更改,我得到了非常有希望的结果:
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
mContext = context;
if (value == null) value = new Collection<Step>();
Collection<Step> result = (Collection<Step>)base.EditValue(context, provider, value);
if (result != null && result.Count == 0) return null;
return result;
}
注意方法中的第二行,它为初始值分配了一个新的 Collection。通过这样做,我的 collection 得以保留并且一切正常。
我现在唯一想修复的是设计器文件的序列化。目前正在制作这样的东西:
// wizardStepsControl1
// ...
this.wizardStepsControl1.Steps.Add(this.step1);
// ...
// step1
// Initialization of step1
此代码将给出异常,因为 wizardStepsControl1.Steps 从未初始化为 collection。我想要制作的是这样的:
this.wizardStepsControl1.Steps = new Collection<Step>();
this.wizardStepsControl1.Steps.Add(step1);
// ...
更好的是,整个 collection 首先被初始化,然后分配给我的控件的步骤 属性。 我会看看我能做些什么让它工作,post 在这里进行一些更新,也许需要实现 InstanceDescriptor 或使我的自定义 Collection class 继承自 Component (因为组件总是在设计器文件中初始化)。
我知道这是一个与我的第一个问题完全不同的问题,所以也许我会为此开始一个新问题。但是,如果有人已经知道答案,那么在这里听到它会很棒!
更新: 我找到了解决问题的方法。
无法从 Component 和 CollectionBase 继承,因为 C# 不允许这样做。实施将我的自定义 collection 转换为 InstanceDescriptor 的 TypeConverter 也不起作用(我不知道为什么,我猜这是因为 Collection 的序列化方式与普通自定义 class).
但是通过创建 CodeDomSerializer
,我能够将代码添加到生成的设计器代码中。这样我就可以初始化我的 collection 如果在设计时添加了一些项目:
public class WizardStepsSerializer : CodeDomSerializer
{
/// <summary>
/// We customize the output from the default serializer here, adding
/// a comment and an extra line of code.
/// </summary>
public override object Serialize(IDesignerSerializationManager manager, object value)
{
// first, locate and invoke the default serializer for
// the ButtonArray's base class (UserControl)
//
CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(WizardStepsControl).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
// now add some custom code
//
if (codeObject is CodeStatementCollection)
{
// add a custom comment to the code.
//
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
statements.Insert(4, new CodeCommentStatement("This is a custom comment added by a custom serializer on " + DateTime.Now.ToLongDateString()));
// call a custom method.
//
CodeExpression targetObject = base.SerializeToExpression(manager, value);
WizardStepsControl wsc = (WizardStepsControl)value;
if (targetObject != null && wsc.Steps != null)
{
CodePropertyReferenceExpression leftNode = new CodePropertyReferenceExpression(targetObject, "Steps");
CodeObjectCreateExpression rightNode = new CodeObjectCreateExpression(typeof(Collection<Step>));
CodeAssignStatement initializeStepsStatement = new CodeAssignStatement(leftNode, rightNode);
statements.Insert(5, initializeStepsStatement);
}
}
// finally, return the statements that have been created
return codeObject;
}
}
通过 DesignerSerializerAttribute
将此序列化程序与我的自定义控件相关联,在设计器文件中生成以下代码:
//
// wizardStepsControl1
//
// This is a custom comment added by a custom serializer on vrijdag 4 september 2015
this.wizardStepsControl1.Steps = new System.Collections.ObjectModel.Collection<WizardUserControl.Step>();
// ...
this.wizardStepsControl1.Steps.Add(step1);
// ...
这正是我想要的。
中获取了大部分代码